Procházet zdrojové kódy

feat:对接农事服务筛选功能

wangsisi před 1 týdnem
rodič
revize
54c4d34caa

binární
src/assets/img/monitor/aaa.png


+ 54 - 39
src/components/chatWindow.vue

@@ -36,11 +36,11 @@
                         </div>
 
                         <!-- 对话样式消息 -->
-                        <div v-if="msg.messageType === 'dialog'" class="dialog-message">
+                        <div v-if="msg.messageType === 'report'" class="dialog-message">
                             <template v-if="msg.content.type === 'farm_report'">
                                 <div class="report-title">{{ msg.content.title }}</div>
                                 <div class="dialog-title">{{ msg.content.content }}</div>
-                                <img src="@/assets/img/monitor/aaa.png" alt="" class="monitor-image" />
+                                <img src="https://birdseye-img.sysuimars.com/birdseye-look-mini/share-report-bg.png" alt="" class="monitor-image" />
                             </template>
                             <template v-else>
                                 <div class="dialog-title">{{ msg.content.title }}</div>
@@ -66,7 +66,7 @@
 
                 <!-- 我方消息 -->
                 <template v-else>
-                    <div class="bubble" :class="{ 'no-bubble': msg.messageType === 'image','card-bubble': msg.messageType === 'card'}">
+                    <div class="bubble" :class="{ 'no-bubble': msg.messageType === 'image','card-bubble': msg.messageType === 'card' || msg.messageType === 'report' }">
                         <!-- 文本消息 -->
                         <div v-if="msg.messageType === 'text'" class="content">{{ msg.content }}</div>
 
@@ -87,11 +87,11 @@
                         </div>
 
                         <!-- 对话样式消息 -->
-                        <div v-if="msg.messageType === 'dialog'" class="dialog-message">
-                            <template v-if="msg.content.type === 'farm_report'">
-                                <div class="report-title">{{ msg.content.title }}</div>
-                                <div class="dialog-title">{{ msg.content.content }}</div>
-                                <img src="@/assets/img/monitor/aaa.png" alt="" class="monitor-image" />
+                        <div v-if="msg.messageType === 'report'" class="dialog-message" @click="handleReportClick(msg)">
+                            <template v-if="(msg.reportType || msg.content.type) === 'farm_report'">
+                                <div class="report-title">{{ msg.title ||msg.content.title }}</div>
+                                <div class="dialog-title">这是我的果园情况,请查看~</div>
+                                <img src="https://birdseye-img.sysuimars.com/birdseye-look-mini/share-report-bg.png" alt="" class="monitor-image" />
                             </template>
                             <template v-else>
                                 <div class="dialog-title">{{ msg.content.title }}</div>
@@ -128,7 +128,7 @@
 
         <!-- 功能按钮区域 -->
         <div class="function-buttons">
-            <el-select v-model="farmVal" size="large" @change="handleFarmChange()">
+            <el-select v-model="farmVal" size="large" @change="handleFarmChange">
                 <el-option
                     v-for="item in options"
                     :key="item.id"
@@ -136,9 +136,9 @@
                     :value="item.id"
                 />
             </el-select>
-            <!-- <div v-for="(btn, index) in functionButtons" :key="index" class="function-btn" @click="btn.handler">
+            <div v-for="(btn, index) in functionButtons" :key="index" class="function-btn" @click="btn.handler">
                 <span class="btn-text">{{ btn.text }}</span>
-            </div> -->
+            </div>
         </div>
 
         <!-- 输入框区域 -->
@@ -288,6 +288,7 @@ const createSession = (targetUserId, callback) => {
 };
 
 const handleFarmChange = (e) => {
+    sessionStorage.setItem('selectedFarmId', e);
     createSession(userId.value);
     initMqtt();
 };
@@ -423,6 +424,7 @@ watch(
 );
 
 onDeactivated(() => {
+    sessionStorage.removeItem('selectedFarmId');
     mqttClient.value && mqttClient.value.client.end(true);
 });
 
@@ -502,9 +504,15 @@ const sendMessage = (message) => {
     } else if (message.messageType === "image") {
         // 按新协议:不传 content,传 image 对象
         sendMsg("image", "", { url: message.content, thumbnailUrl: message.content + resize });
-    } else if (message.messageType === "dialog") {
+    } else if (message.messageType === "report") {
         // 对话样式消息不发送到服务器,只显示在本地
-        console.log("发送对话样式消息:", message.content);
+        console.log("发送对话样式消息:", message);
+        // sendMsg('report','',{
+        //     title: message.title,
+        //     coverUrl: message.coverUrl,
+        //     cardType: message.cardType,
+        //     linkUrl: message.linkUrl
+        // });
     }else{
         sendMsg('card','',{
             title: message.title,
@@ -657,7 +665,6 @@ const functionButtons = ref([
     {
         text: "农场报告",
         handler: () => {
-            console.log("点击农场报告,农场ID:", farmVal.value);
             // 发送农场报告对话框消息
             sendFarmReportDialog();
         },
@@ -669,13 +676,13 @@ const functionButtons = ref([
             router.push(`/farm_card?farmId=${farmVal.value}`);
         },
     },
-    // {
-    //     text: '农场相册',
-    //     handler: () => {
-    //         // 跳转到农场相册页面
-    //         router.push(`/farm_photo`);
-    //     }
-    // }
+    {
+        text: '农场相册',
+        handler: () => {
+            // 跳转到农场相册页面
+            router.push(`/farm_photo`);
+        }
+    }
 ]);
 
 // 辅助函数
@@ -712,9 +719,22 @@ function getFarmList() {
         options.value = data || [];
         if (data && data.length > 0) {
             const defaultOption = data.find((item) => item.defaultOption === true);
-            if(route.query.farmId) {
+            // 优先使用 sessionStorage 中保存的农场ID(用户刚刚切换的农场)
+            const savedFarmId = sessionStorage.getItem('selectedFarmId');
+            if (savedFarmId) {
+                // 检查保存的农场是否还在当前列表中
+                const savedFarm = data.find((item) => item.id == savedFarmId);
+                if (savedFarm) {
+                    farmVal.value = Number(savedFarmId);
+                } else {
+                    // 如果保存的农场不在列表中,使用路由参数或默认农场
+                    farmVal.value = route.query.farmId ? Number(route.query.farmId) : (defaultOption ? defaultOption.id : data[0].id);
+                }
+            } else if(route.query.farmId) {
+                // 如果没有保存的农场,使用路由参数
                 farmVal.value = Number(route.query.farmId);
-            }else{
+            } else {
+                // 都没有则使用默认农场
                 farmVal.value = defaultOption ? defaultOption.id : data[0].id;
             }
         }
@@ -755,29 +775,25 @@ const sendDialogMessage = (dialogData) => {
     sendMessage(message);
 };
 
+// 点击农场报告对话框
+const handleReportClick = (msg) => {
+    console.log(msg);
+    // router.push(`/farm_report?farmId=${msg.reportId}&showFilter=true`);
+}
+
 // 发送农场报告对话框
 const sendFarmReportDialog = () => {
     const currentFarmName = options.value.find((opt) => opt.id === farmVal.value)?.name || "当前农场";
 
-    const farmReportData = {
-        type: "farm_report",
-        title: currentFarmName,
-        content: "这是我的果园情况,请查看~",
-        reportDetails: {
-            farmId: farmVal.value,
-            farmName: currentFarmName,
-            reportDate: new Date().toLocaleDateString("zh-CN"),
-            reportType: "综合报告",
-            status: "正常",
-        },
-    };
-
     const message = {
         sender: "sent",
-        messageType: "dialog",
+        messageType: "report",
         senderIcon: senderIcon.value,
         title: currentFarmName,
-        content: farmReportData,
+        coverUrl:'',
+        fileUrl:'',
+        reportId:farmVal.value,
+        reportType:'farm_report',
         time: getCurrentTime(),
     };
     sendMessage(message);
@@ -1058,7 +1074,6 @@ const sendFarmReportDialog = () => {
 .dialog-message {
     max-width: 100%;
     background: #fff !important;
-    padding: 10px;
     border-radius: 10px;
     .report-title {
         font-size: 16px;

+ 213 - 50
src/views/old_mini/agri_services/components/servicesHall.vue

@@ -6,7 +6,7 @@
         <div class="search-wrap">
             <location-search
                 class="search"
-                user-location="113.61702297075017,23.584863449735067"
+                :userLocation="userLocation"
                 @change="handleLocationChange"
             />
         </div>
@@ -21,7 +21,7 @@
             <template #header>
                 <div class="header-bar"></div>
                 <div class="select-group">
-                    <el-select class="select-item" v-model="paramsPage.distance" placeholder="距离">
+                    <el-select class="select-item" v-model="paramsPage.distance" placeholder="距离" @change="resetList">
                         <el-option
                             v-for="item in distanceOptions"
                             :key="item.value"
@@ -29,7 +29,7 @@
                             :value="item.value"
                         />
                     </el-select>
-                    <el-select class="select-item" v-model="paramsPage.serviceType" placeholder="服务类型">
+                    <el-select class="select-item" v-model="paramsPage.serviceType" placeholder="服务类型" @change="resetList">
                         <el-option
                             v-for="item in serviceTypeList"
                             :key="item.id"
@@ -40,8 +40,18 @@
                 </div>
             </template>
             <div class="hall-content">
-                <div class="farm-list" ref="cardContentRef" :style="{ height: `${cardContentHeight}px` }">
-                    <div class="task-item" v-for="item in agriculturalStoreList" :key="item.id" @click="handleDetail(item)">
+                <List
+                    v-model:loading="loading"
+                    :finished="finished"
+                    finished-text="没有更多了"
+                    :immediate-check="false"
+                    @load="onLoad"
+                    class="van-list-container"
+                    :style="{ height: `${cardContentHeight}px` }"
+                    ref="cardContentRef"
+                >
+                    <div class="farm-list">
+                        <div class="task-item" v-for="item in agriculturalStoreList" :key="item.id" @click="handleDetail(item)">
                         <div class="task-content">
                             <div class="content-t">
                                 <img class="content-img" :src="item.bgUrl || require('@/assets/img/home/nz.png')" alt="" />
@@ -95,21 +105,23 @@
                                 </div>
                             </div>
                         </div>
+                        </div>
                     </div>
-                </div>
+                </List>
             </div>
         </floating-panel>
     </div>
 </template>
 
 <script setup>
-import { FloatingPanel, Icon } from "vant";
-import { computed, nextTick, onMounted, ref,onActivated } from "vue";
+import { FloatingPanel, Icon, List } from "vant";
+import { computed, nextTick, onMounted, ref, onActivated } from "vue";
 import { useStore } from "vuex";
 import IndexMap from "../map/index";
 import LocationSearch from "@/components/pageComponents/LocationSearch.vue";
 import { Point } from "ol/geom";
 import Feature from "ol/Feature";
+import * as util from "@/common/ol_common.js";
 import { useRouter } from "vue-router";
 const router = useRouter();
 const props = defineProps({
@@ -132,6 +144,7 @@ const height = ref(anchors.value[0]);
 const indexMap = new IndexMap();
 const mapContainer = ref(null);
 const mapPoint = ref(null);
+const userLocation = localStorage.getItem("MINI_USER_LOCATION");
 
 onMounted(() => {
     mapPoint.value = store.state.home.miniUserLocationPoint;
@@ -141,14 +154,35 @@ onMounted(() => {
         indexMap.setGardenHallClickCallback((featureData,) => {
             router.push(`/expert_homepage?isStore=true&id=${featureData.id}`);
         });
+        
+        // 添加初始位置图标
+        if (indexMap.clickPointLayer && mapPoint.value) {
+            const initialPoint = util.wktCastGeom(mapPoint.value).getFirstCoordinate();
+            const point = new Feature(new Point(initialPoint));
+            indexMap.clickPointLayer.addFeature(point);
+        }
     });
-
-    getStoreList();
+    // 手动触发首次加载
+    getStoreList(false);
+    initMap();
 });
 
+const initMap = async () => {
+    const params = {
+        limit: 999,
+        page: 1,
+        type: 1,
+    };
+    const res = await VE_API.farm.getStoreList(params);
+    if(res.data.length > 0){
+        indexMap.initDataHall(res.data);
+    }
+};
+
 onActivated(() => {
     if(props.active == 1){
-        getStoreList();
+        resetList();
+        initMap();
     }
 });
 
@@ -157,20 +191,73 @@ const handleDetail = (item) => {
 };
 
 const paramsPage = ref({
-    serviceType: "",
-    distance:""
+    serviceType: "全部类型",
+    distance:"不限范围"
 })
 
 const agriculturalStoreList = ref([]);
-const getStoreList = (isInitial = true) => {
+const loading = ref(false);
+const finished = ref(false);
+const page = ref(1);
+const limit = ref(10); // 每页加载数量
+const resetTimer = ref(null); // 防抖定时器
+const currentRequestKey = ref(null); // 当前请求的唯一标识
+
+// 生成请求的唯一标识
+const generateRequestKey = (params) => {
+    return JSON.stringify({
+        point: params.point,
+        page: params.page,
+        limit: params.limit,
+        type: params.type,
+        serviceType: params.serviceType || '',
+        distance: params.distance || '',
+    });
+};
+
+// 加载列表数据
+const getStoreList = async (isLoadMore = false) => {
     const params = {
-        page: 1,
-        limit: 99,
+        point: mapPoint.value,
+        page: page.value,
+        limit: limit.value,
         type: 1,
+        ...paramsPage.value,
     };
-    VE_API.farm.getStoreList(params).then((res) => {
-        if (res.data.length) {
-            agriculturalStoreList.value = res.data.map((item) => {
+    
+    // 如果服务类型为空字符串,不传该参数
+    if (params.serviceType === "全部类型") {
+        delete params.serviceType;
+    }
+    if (params.distance === "不限范围") {
+        delete params.distance;
+    }
+    
+    // 生成请求的唯一标识
+    const requestKey = generateRequestKey(params);
+    
+    // 如果正在加载相同的请求,直接返回
+    if (loading.value && currentRequestKey.value === requestKey) {
+        return;
+    }
+    
+    // 如果正在加载,直接返回(防止不同参数的请求重叠)
+    if (loading.value) {
+        return;
+    }
+    
+    // 设置当前请求标识和加载状态
+    currentRequestKey.value = requestKey;
+    loading.value = true;
+    
+    try {
+        const res = await VE_API.farm.getStoreList(params);
+        // 获取总数和当前页数据
+        const totalCount = res.count || 0;
+        const currentData = res.data || [];
+        
+        if (currentData.length > 0) {
+            const newList = currentData.map((item) => {
                 return {
                     ...item,
                     speciesList: JSON.parse(item.serviceSpecies || "[\"--\"]"),
@@ -178,60 +265,135 @@ const getStoreList = (isInitial = true) => {
                     serviceTypeList: JSON.parse(item.serviceType || "[\"--\"]"),
                 };
             });
-            if (isInitial) {
-                indexMap.initDataHall(res.data);
+            
+            if (isLoadMore) {
+                // 加载更多时追加数据
+                agriculturalStoreList.value = [...agriculturalStoreList.value, ...newList];
+            } else {
+                // 首次加载或刷新时替换数据
+                agriculturalStoreList.value = newList;
             }
+            
+            // 使用 count 判断是否还有更多数据
+            // 如果当前已加载的数据量 >= 总数,说明没有更多数据了
+            const loadedCount = agriculturalStoreList.value.length;
+            if (totalCount > 0 && loadedCount >= totalCount) {
+                finished.value = true;
+            } else if (totalCount === 0) {
+                // 如果没有总数信息,使用原来的判断方式
+                if (currentData.length < limit.value) {
+                    finished.value = true;
+                }
+            }
+            
+        } else {
+            finished.value = true;
         }
+    } catch (error) {
+        console.error('获取列表失败:', error);
+        finished.value = true;
+    } finally {
+        // 加载完成后,设置 loading = false 告诉 List 组件加载完成
+        loading.value = false;
+        // 清除当前请求标识
+        currentRequestKey.value = null;
+    }
+};
+
+// 滚动加载更多
+const onLoad = async () => {
+    if (finished.value) return;
+    const isLoadMore = page.value > 1;
+    await getStoreList(isLoadMore);
+    // 加载完成后再增加页码
+    if (!finished.value) {
+        page.value++;
+    }
+};
+
+// 重置列表(筛选条件变化时调用)
+const resetList = () => {
+    // 清除之前的定时器
+    if (resetTimer.value) {
+        clearTimeout(resetTimer.value);
+        resetTimer.value = null;
+    }
+    
+    // 使用防抖,延迟执行,避免快速连续触发
+    resetTimer.value = setTimeout(() => {
+        // 清除当前请求标识,确保可以重新请求
+        currentRequestKey.value = null;
+        page.value = 1;
+        finished.value = false;
+        agriculturalStoreList.value = [];
+        getStoreList(false);
+        resetTimer.value = null;
+    }, 100);
+};
+
+// 更新地图位置和图标
+const updateMapLocation = (coordinateArray) => {
+    if (!indexMap || !indexMap.kmap) return;
+    
+    // 更新地图位置
+    indexMap.kmap.getView().animate({
+        center: coordinateArray,
+        zoom: 16,
+        duration: 500,
     });
+    
+    // 更新点击点图标
+    if (indexMap.clickPointLayer) {
+        indexMap.clickPointLayer.source.clear();
+        const point = new Feature(new Point(coordinateArray));
+        indexMap.clickPointLayer.addFeature(point);
+    }
 };
 
 // 处理位置搜索变化
 function handleLocationChange(data) {
     if (data && data.coordinateArray) {
+        // 搜索到新位置
         mapPoint.value = data.point;
-        // 更新地图位置
-        if (indexMap && indexMap.kmap) {
-            indexMap.kmap.getView().animate({
-                center: data.coordinateArray,
-                zoom: 16,
-                duration: 500,
-            });
-            // 更新点击点
-            if (indexMap.clickPointLayer) {
-                indexMap.clickPointLayer.source.clear();
-                const point = new Feature(new Point(data.coordinateArray));
-                indexMap.clickPointLayer.addFeature(point);
-            }
-        }
-        getStoreList(false);
+        paramsPage.value.distance = "5000";
+        updateMapLocation(data.coordinateArray);
+        resetList();
     } else {
         // 重置为初始位置
         mapPoint.value = store.state.home.miniUserLocationPoint;
-        indexMap.clickPointLayer.source.clear();
-        // 更新区县列表
-        getStoreList();
+        paramsPage.value.distance = "";
+        
+        // 获取初始位置的坐标
+        const initialPoint = util.wktCastGeom(mapPoint.value).getFirstCoordinate();
+        updateMapLocation(initialPoint);
+        
+        resetList();
+        initMap();
     }
 }
 
 const cardContentHeight = ref(245);
 
 const distanceOptions = [
-    { value: "1", label: "5km" },
-    { value: "2", label: "3km" },
-    { value: "3", label: "1km" },
+    { value: "不限范围", label: "不限范围" },
+    { value: "5000", label: "5km" },
+    { value: "3000", label: "3km" },
+    { value: "1000", label: "1km" },
 ];
 const serviceTypeList = ref([
-    {id: "", name: "全部"},
-    {id: 1, name: "嫁接"},
-    {id: 2, name: "修剪"},
-    {id: 3, name: "施肥打药"},
+    {id: "全部类型", name: "全部类型"},
+    {id: "嫁接", name: "嫁接"},
+    {id: "修剪", name: "修剪"},
+    {id: "施肥打药", name: "施肥打药"},
 ]);
 
 const cardContentRef = ref(null);
 const handleHeightChange = ({ height }) => {
     if (height === anchors.value[0]) {
         cardContentHeight.value = 245;
-        cardContentRef.value.scrollTo({ top: 0, behavior: "smooth" });
+        if (cardContentRef.value && cardContentRef.value.$el) {
+            cardContentRef.value.$el.scrollTo({ top: 0, behavior: "smooth" });
+        }
     } else if (height === anchors.value[1]) {
         cardContentHeight.value = Math.round(1 * window.innerHeight) - (tabBarHeight.value - 40) - 170;
     }
@@ -242,7 +404,7 @@ const handleOnlineChat = ({miniUserIds}) => {
 };
 
 defineExpose({
-    getStoreList,
+    getStoreList: resetList,
 });
 
 </script>
@@ -298,9 +460,10 @@ defineExpose({
     .hall-content {
         height: 100%;
         position: relative;
+        .van-list-container {
+            overflow-y: auto;
+        }
         .farm-list {
-            overflow: auto;
-            height: calc(100% - 60px);
             padding: 0 12px;
             .task-item {
                 border-top: 1px solid rgba(0, 0, 0, 0.1);

+ 26 - 3
src/views/old_mini/mine/index.vue

@@ -13,7 +13,7 @@
                         <span v-else>{{ userInfo?.name }}</span>
                         <span class="score" v-if="curRole !== 0">5.0分</span>
                     </div>
-                    <div class="user-day">这是您使用飞鸟管家的第15天</div>
+                    <div class="user-day">这是您使用飞鸟管家的第{{ daysSinceCreation }}天</div>
                 </div>
             </div>
             <div class="code-icon" v-if="curRole === 1">
@@ -67,10 +67,33 @@ const curRole = ref(Number(localStorage.getItem("SET_USER_CUR_ROLE")));
 const roles = ref(JSON.parse(store.state.app.roles));
 const userInfo = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
 
+// 计算从创建时间到现在经过的天数
+const daysSinceCreation = computed(() => {
+    if (!userInfo?.createTime) {
+        return 0;
+    }
+    try {
+        // 将创建时间字符串转换为日期对象
+        const createDate = new Date(userInfo.createTime);
+        // 获取当前日期
+        const currentDate = new Date();
+        // 将两个日期都设置为当天的 00:00:00,只比较日期部分
+        const createDateOnly = new Date(createDate.getFullYear(), createDate.getMonth(), createDate.getDate());
+        const currentDateOnly = new Date(currentDate.getFullYear(), currentDate.getMonth(), currentDate.getDate());
+        // 计算日期差(毫秒)
+        const timeDiff = currentDateOnly.getTime() - createDateOnly.getTime();
+        // 转换为天数
+        const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
+        // 加1,因为创建当天算作第1天
+        return days + 1;
+    } catch (error) {
+        console.error('计算天数失败:', error);
+        return 0;
+    }
+});
+
 // const roles = ref([0,1,2,3])
 const actions = ref([]);
-const userObj = ref({})
-const list = ref([]);
 
 // 网格项数据
 const gridItems = ref([]);