فهرست منبع

feat: 农事列表,更多农事

lxf 1 هفته پیش
والد
کامیت
08c6e28b73

+ 6 - 6
src/App.vue

@@ -26,7 +26,7 @@
         <!-- 托管农户:首页、农事档案、农事记录 -->
         <!-- <template v-if="userType == 2"> -->
             <tabbar-item replace to="/growth_report">
-                <span>{{ t("tabbar.growthReport") }}</span>
+                <span>{{ t("tabbar.situationAssessment") }}</span>
                 <template #icon="props">
                     <img
                         :src="
@@ -49,7 +49,7 @@
                     />
                 </template>
             </tabbar-item>
-            <tabbar-item replace to="/agri_record">
+            <!-- <tabbar-item replace to="/agri_record">
                 <span>{{ t("tabbar.agriRecord") }}</span>
                 <template #icon="props">
                     <img
@@ -60,15 +60,15 @@
                         "
                     />
                 </template>
-            </tabbar-item>
+            </tabbar-item> -->
             <tabbar-item replace to="/work_execute">
-                <span>{{ t("tabbar.execute") }}</span>
+                <span>{{ t("tabbar.schedule") }}</span>
                 <template #icon="props">
                     <img
                         :src="
                             props.active
-                                ? require('@/assets/img/tab_bar/farm-active.png')
-                                : require('@/assets/img/tab_bar/farm.png')
+                                ? require('@/assets/img/tab_bar/task-active.png')
+                                : require('@/assets/img/tab_bar/task.png')
                         "
                     />
                 </template>

+ 595 - 0
src/components/allGarden.vue

@@ -0,0 +1,595 @@
+<template>
+    <div class="all-garden-page">
+        <custom-header :name="t('agriFile.moreFarms')" />
+
+        <div class="page-body">
+            <div class="map-wrap">
+                <div class="map-container" ref="mapContainer"></div>
+                <div class="add-farm-btn" @click="handleAddFarm">
+                    <el-icon><Plus /></el-icon>
+                    <span>{{ t("garden.addFarm") }}</span>
+                </div>
+            </div>
+
+            <div class="farm-filter">
+                <el-input
+                    v-model="searchFarm"
+                    class="filter-search"
+                    :placeholder="t('weather.searchFarm')"
+                    :prefix-icon="Search"
+                    style="width: 106px"
+                    clearable
+                />
+                <div class="filter-select-wrap">
+                    <el-select
+                        v-model="regionVal"
+                        class="filter-select"
+                        :placeholder="t('weather.selectRegion')"
+                        clearable
+                    >
+                        <el-option
+                            v-for="item in regionOptions"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value"
+                        />
+                    </el-select>
+                    <el-select
+                        v-model="typeVal"
+                        class="filter-select"
+                        :placeholder="t('weather.selectCategory')"
+                        clearable
+                    >
+                        <el-option
+                            v-for="item in typeOptions"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value"
+                        />
+                    </el-select>
+                </div>
+            </div>
+
+            <div class="farm-list">
+                <div
+                    v-for="item in filteredFarmList"
+                    :key="item.id"
+                    class="farm-card"
+                    :class="{ 'is-current': item.isCurrent }"
+                >
+                    <div class="farm-card__header">
+                        <div class="farm-card__main">
+                            <div class="farm-card__title-row">
+                                <span class="farm-name van-ellipsis">{{ item.name }}</span>
+                                <span v-if="item.categoryLabel" class="category-tag">{{ item.categoryLabel }}</span>
+                            </div>
+                            <div class="farm-address van-ellipsis">
+                                {{ item.rawAddress || t("common.noAddress") }}
+                            </div>
+                        </div>
+                        <div
+                            v-if="item.isCurrent"
+                            class="action-btn action-btn-current"
+                        >
+                            {{ t("garden.currentFarm") }}
+                        </div>
+                        <div
+                            v-else
+                            class="action-btn action-btn-enter"
+                            @click="handleEnterFarm(item)"
+                        >
+                            {{ t("garden.enterFarm") }}
+                        </div>
+                    </div>
+
+                    <div class="phenology-box">
+                        {{ t("garden.currentPhenology") }}{{ item.phenologyText || t("garden.phenologyPlaceholder") }}
+                    </div>
+
+                    <div class="task-bar">
+                        <div class="task-bar__left">
+                            <img class="task-icon" src="@/assets/img/home/task.png" alt="" />
+                            <span class="task-name van-ellipsis">{{ item.taskName || t("garden.taskNamePlaceholder") }}</span>
+                            <span class="task-status">{{ item.taskStatus || t("garden.pendingExecute") }}</span>
+                        </div>
+                        <div class="task-bar__forward" @click.stop="handleForward(item)">
+                            <Icon size="14" color="#2199F8" name="share" />
+                            <span>{{ t("garden.forward") }}</span>
+                        </div>
+                    </div>
+                </div>
+
+                <div v-if="!filteredFarmList.length" class="empty-tip">
+                    {{ t("garden.noFarm") }}
+                </div>
+            </div>
+        </div>
+
+        <div class="invite-bar">
+            <div class="invite-btn" @click="handleInviteFarmer">
+                <img class="invite-btn-icon" src="@/assets/img/home/user-icon.png" alt="">
+                <span>{{ t("garden.inviteFarmer") }}</span>
+            </div>
+            <div class="invite-btn" @click="handleInviteService">
+                <img class="invite-btn-icon" src="@/assets/img/home/user-icon.png" alt="">
+                <span>{{ t("garden.inviteService") }}</span>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { computed, nextTick, onActivated, onMounted, ref, watch } from "vue";
+import { useRouter } from "vue-router";
+import { useStore } from "vuex";
+import { ElMessage } from "element-plus";
+import { Plus, Search, Share, User, UserFilled } from "@element-plus/icons-vue";
+import { useI18n } from "@/i18n";
+import customHeader from "@/components/customHeader.vue";
+import IndexMap from "@/views/old_mini/work_execute/index.js";
+import { Icon } from 'vant';
+
+const { t } = useI18n();
+const router = useRouter();
+const store = useStore();
+
+const mapContainer = ref(null);
+const indexMap = new IndexMap();
+
+const farmList = ref([]);
+const searchFarm = ref("");
+const regionVal = ref("");
+const typeVal = ref("");
+
+const regionOptions = computed(() => {
+    const set = new Set();
+    farmList.value.forEach((farm) => {
+        const region = farm.city || farm.farm_city || "";
+        if (region) set.add(region);
+    });
+    return Array.from(set).map((value) => ({ label: value, value }));
+});
+
+const typeOptions = computed(() => {
+    const set = new Set();
+    farmList.value.forEach((farm) => {
+        if (farm.categoryLabel) set.add(farm.categoryLabel);
+    });
+    return Array.from(set).map((value) => ({ label: value, value }));
+});
+
+const filteredFarmList = computed(() => {
+    const keyword = searchFarm.value.trim().toLowerCase();
+    return farmList.value.filter((farm) => {
+        if (keyword && !(farm.name || "").toLowerCase().includes(keyword)) {
+            return false;
+        }
+        if (regionVal.value) {
+            const region = farm.city || farm.farm_city || "";
+            if (region !== regionVal.value) return false;
+        }
+        if (typeVal.value && farm.categoryLabel !== typeVal.value) {
+            return false;
+        }
+        return true;
+    });
+});
+
+function resolveFarmPointWkt(farm) {
+    if (farm.geom_wkt && /^POINT\s*\(/i.test(String(farm.geom_wkt).trim())) {
+        return farm.geom_wkt;
+    }
+    if (farm.farm_location && /^POINT\s*\(/i.test(String(farm.farm_location).trim())) {
+        return farm.farm_location;
+    }
+    return farm.geom_wkt || "";
+}
+
+function resolveCategoryLabel(farm) {
+    return (
+        farm.category_name ||
+        farm.farm_category_name ||
+        farm.crop_name ||
+        farm.species_name ||
+        farm.farm_category ||
+        ""
+    );
+}
+
+function normalizeFarmList(data) {
+    const selectedId = localStorage.getItem("selectedFarmId");
+    return (data || []).map((farm) => {
+        const id = farm.farm_id;
+        return {
+            ...farm,
+            id,
+            name: farm.farm_name,
+            wkt: resolveFarmPointWkt(farm),
+            farmName: farm.farm_name,
+            rawAddress: farm.farm_address || farm.city || "",
+            categoryLabel: resolveCategoryLabel(farm),
+            phenologyText: farm.current_phenology || farm.phenology_name || farm.phenology || "",
+            taskName: farm.pending_work_name || farm.farm_work_name || farm.work_name || "",
+            taskStatus: farm.work_status_name || farm.work_status || "",
+            isCurrent: selectedId != null && Number(id) === Number(selectedId),
+        };
+    });
+}
+
+function saveSelectedFarm(farm) {
+    localStorage.setItem("selectedFarmId", farm.id);
+    localStorage.setItem("selectedFarmName", farm.name || "");
+    localStorage.setItem("selectedFarmPoint", farm.wkt || "");
+    localStorage.setItem("selectedFarmData", JSON.stringify(farm));
+}
+
+function markCurrentFarm(selectedId) {
+    farmList.value = farmList.value.map((farm) => ({
+        ...farm,
+        isCurrent: Number(farm.id) === Number(selectedId),
+    }));
+}
+
+function getDefaultMapPoint() {
+    const withPoint = farmList.value.find((farm) => farm.wkt);
+    return withPoint?.wkt || store.state.home.miniUserLocationPoint || "POINT(113.38 23.18)";
+}
+
+function refreshMapMarkers() {
+    if (!indexMap.kmap) return;
+    const points = filteredFarmList.value.filter((farm) => farm.wkt);
+    indexMap.initData(points, "name", "wkt");
+}
+
+function ensureMap() {
+    if (!mapContainer.value) return;
+    if (!indexMap.kmap) {
+        indexMap.initMap(getDefaultMapPoint(), mapContainer.value, true);
+    }
+    refreshMapMarkers();
+    nextTick(() => {
+        indexMap.kmap?.map?.updateSize?.();
+    });
+}
+
+function getFarmList() {
+    VE_API.farm.getFarmList()
+        .then(({ data }) => {
+            farmList.value = normalizeFarmList(data);
+            nextTick(() => {
+                ensureMap();
+            });
+        })
+        .catch(() => {
+            farmList.value = [];
+        });
+}
+
+function handleEnterFarm(item) {
+    markCurrentFarm(item.id);
+    saveSelectedFarm(item);
+    ElMessage.success(t("garden.enterFarmSuccess"));
+    router.back();
+}
+
+function handleAddFarm() {
+    router.push("/create_farm?from=all_garden&isReload=true");
+}
+
+function handleForward() {
+    ElMessage.info(t("garden.forwardTip"));
+}
+
+function handleInviteFarmer() {
+    ElMessage.info(t("garden.inviteTip"));
+}
+
+function handleInviteService() {
+    ElMessage.info(t("garden.inviteTip"));
+}
+
+watch(filteredFarmList, () => {
+    if (indexMap.kmap) {
+        refreshMapMarkers();
+    }
+});
+
+onMounted(() => {
+    getFarmList();
+    nextTick(() => {
+        ensureMap();
+    });
+});
+
+onActivated(() => {
+    getFarmList();
+    nextTick(() => {
+        if (!indexMap.kmap) {
+            ensureMap();
+            return;
+        }
+        if (mapContainer.value && indexMap.kmap.map) {
+            const checkAndUpdateSize = () => {
+                const container = mapContainer.value;
+                if (!container) return;
+                const rect = container.getBoundingClientRect();
+                if (rect.width > 0 && rect.height > 0) {
+                    indexMap.kmap.map.updateSize();
+                    refreshMapMarkers();
+                } else {
+                    setTimeout(checkAndUpdateSize, 100);
+                }
+            };
+            setTimeout(checkAndUpdateSize, 200);
+        }
+    });
+});
+</script>
+
+<style lang="scss" scoped>
+.all-garden-page {
+    display: flex;
+    flex-direction: column;
+    height: 100vh;
+    background: #f5f5f5;
+    box-sizing: border-box;
+}
+
+.page-body {
+    flex: 1;
+    overflow: auto;
+    padding: 10px 12px 108px;
+    box-sizing: border-box;
+}
+
+.map-wrap {
+    position: relative;
+    border-radius: 8px;
+    overflow: hidden;
+    background: #e8e8e8;
+
+    .map-container {
+        width: 100%;
+        height: 162px;
+        clip-path: inset(0 round 8px);
+    }
+
+    .add-farm-btn {
+        position: absolute;
+        right: 10px;
+        bottom: 10px;
+        z-index: 2;
+        display: inline-flex;
+        align-items: center;
+        gap: 2px;
+        height: 28px;
+        padding: 0 10px;
+        border-radius: 14px;
+        background: #fff;
+        color: #2199f8;
+        font-size: 12px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+    }
+}
+
+.farm-filter {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 8px;
+    margin-top: 10px;
+
+    .filter-select-wrap {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+    }
+
+    .filter-search {
+        // flex: 1;
+        min-width: 0;
+    }
+
+    .filter-select {
+        width: 92px;
+        flex-shrink: 0;
+    }
+
+    :deep(.el-input__wrapper),
+    :deep(.el-select__wrapper) {
+        min-height: 28px;
+        line-height: 28px;
+        height: 28px;
+        padding: 0 8px;
+        box-sizing: border-box;
+        border-radius: 4px;
+        box-shadow: 0 0 0 1px rgba(180, 180, 180, 0.4) inset;
+    }
+}
+
+.farm-list {
+    margin-top: 10px;
+}
+
+.farm-card {
+    margin-bottom: 10px;
+    padding: 10px 12px;
+    border-radius: 8px;
+    background: #fff;
+    box-sizing: border-box;
+
+    &.is-current {
+        border: 1px solid #2199f8;
+
+        .farm-name {
+            color: #2199f8;
+        }
+    }
+}
+
+.farm-card__header {
+    display: flex;
+    align-items: flex-start;
+    justify-content: space-between;
+    gap: 8px;
+}
+
+.farm-card__main {
+    flex: 1;
+    min-width: 0;
+}
+
+.farm-card__title-row {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    min-width: 0;
+}
+
+.farm-name {
+    max-width: 180px;
+    font-size: 14px;
+    color: #1D2129;
+    font-weight: 500;
+}
+
+.category-tag {
+    flex-shrink: 0;
+    height: 20px;
+    padding: 0 8px;
+    border-radius: 2px;
+    background: rgba(33, 153, 248, 0.1);
+    color: #2199f8;
+    font-size: 12px;
+    line-height: 20px;
+}
+
+.farm-address {
+    margin-top: 2px;
+    font-size: 12px;
+    color: rgba(32, 32, 32, 0.4);
+}
+
+.action-btn {
+    flex-shrink: 0;
+    height: 28px;
+    padding: 0 10px;
+    border-radius: 4px;
+    font-size: 12px;
+    line-height: 28px;
+}
+
+.action-btn-current {
+    color: #fff;
+    background: #2199f8;
+}
+
+.action-btn-enter {
+    color: #fff;
+    background: #ff953d;
+}
+
+.phenology-box {
+    margin-top: 8px;
+    padding: 6px 8px;
+    border-radius: 4px;
+    background: rgba(160, 160, 160, 0.1);
+    font-size: 12px;
+    color: #767676;
+    line-height: 18px;
+}
+
+.task-bar {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    gap: 8px;
+    margin-top: 10px;
+    padding: 8px;
+    border-radius: 6px;
+    background: rgba(33, 153, 248, 0.1);
+}
+
+.task-bar__left {
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    min-width: 0;
+    flex: 1;
+}
+
+.task-icon {
+    width: 16px;
+    height: 16px;
+    flex-shrink: 0;
+}
+
+.task-name {
+    max-width: 120px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #000;
+}
+
+.task-status {
+    flex-shrink: 0;
+    height: 20px;
+    padding: 0 6px;
+    border-radius: 2px;
+    background: #2199f8;
+    color: #fff;
+    font-size: 12px;
+    line-height: 20px;
+}
+
+.task-bar__forward {
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    flex-shrink: 0;
+    color: #2199f8;
+    font-size: 14px;
+}
+
+.empty-tip {
+    padding: 40px 0;
+    text-align: center;
+    color: #86909c;
+    font-size: 13px;
+}
+
+.invite-bar {
+    position: fixed;
+    left: 0;
+    right: 0;
+    bottom: 60px;
+    z-index: 20;
+    display: flex;
+    justify-content: center;
+    gap: 16px;
+    pointer-events: none;
+}
+
+.invite-btn {
+    pointer-events: auto;
+    display: inline-flex;
+    align-items: center;
+    justify-content: center;
+    box-sizing: border-box;
+    gap: 6px;
+    min-width: 110px;
+    height: 40px;
+    padding: 0 16px;
+    border-radius: 20px;
+    background: linear-gradient(180deg, #72C1FF 0%, #2199F8 100%);
+    color: #fff;
+    font-size: 14px;
+    // box-shadow: 0 4px 12px rgba(33, 153, 248, 0.35);
+    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.1);
+    .invite-btn-icon {
+        width: 16px;
+        height: 16px;
+        filter: brightness(0) invert(1);
+    }
+}
+</style>

+ 98 - 0
src/components/farmHeader.vue

@@ -0,0 +1,98 @@
+<template>
+    <div class="farm-header">
+        <div class="farm-header__info">
+            <el-avatar class="farm-header__avatar" :size="46" :src="circleUrl" />
+            <div class="farm-header__text">
+                <div class="farm-header__name van-ellipsis">{{ farmName }}</div>
+                <div class="farm-header__location van-ellipsis">{{ farmLocation }}</div>
+            </div>
+        </div>
+        <div class="farm-header__more" @click="goAllGarden">
+            <el-icon class="farm-header__more-icon">
+                <Switch />
+            </el-icon>
+            <span>{{ t("agriFile.moreFarms") }}</span>
+        </div>
+    </div>
+</template>
+
+<script setup>
+import { onActivated, onMounted, ref } from "vue";
+import { useRouter } from "vue-router";
+import { Switch } from "@element-plus/icons-vue";
+import { useI18n } from "@/i18n";
+
+const { t } = useI18n();
+const router = useRouter();
+
+const defaultAvatar = require("@/assets/img/home/banner.png");
+const farmName = ref("");
+const farmLocation = ref("");
+const circleUrl = ref(defaultAvatar);
+
+const loadFarmInfo = () => {
+    try {
+        const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
+        farmName.value = farm.farm_name || farm.name || t("agriFile.farmName");
+        farmLocation.value = farm.farm_address || farm.rawAddress || farm.city || t("agriFile.farmLocation");
+        circleUrl.value = farm.cover || farm.image || farm.avatar || defaultAvatar;
+    } catch {
+        farmName.value = t("agriFile.farmName");
+        farmLocation.value = t("agriFile.farmLocation");
+        circleUrl.value = defaultAvatar;
+    }
+};
+
+const goAllGarden = () => {
+    router.push("/all_garden");
+};
+
+onMounted(loadFarmInfo);
+onActivated(loadFarmInfo);
+
+defineExpose({ loadFarmInfo });
+</script>
+
+<style lang="scss" scoped>
+.farm-header {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 10px;
+
+    .farm-header__info {
+        display: flex;
+        align-items: center;
+        gap: 6px;
+        min-width: 0;
+        flex: 1;
+    }
+
+    .farm-header__text {
+        min-width: 0;
+    }
+
+    .farm-header__name {
+        font-size: 16px;
+        font-weight: 500;
+    }
+
+    .farm-header__location {
+        margin-top: 2px;
+        color: rgba(0, 0, 0, 0.5);
+        font-size: 12px;
+    }
+
+    .farm-header__more {
+        display: flex;
+        align-items: center;
+        gap: 4px;
+        flex-shrink: 0;
+        padding: 6px 10px;
+        border-radius: 16px;
+        background: #fff;
+        color: #2199f8;
+        cursor: pointer;
+    }
+}
+</style>

+ 4 - 4
src/components/popup/ImagePreviewPopup.vue

@@ -40,8 +40,8 @@
                     class="image-preview-popup__arrow image-preview-popup__arrow--left"
                     @click.stop="handlePrev"
                 >
-                    <el-icon size="20">
-                        <ArrowLeftBold color="#F0D09C" />
+                    <el-icon size="16">
+                        <ArrowLeftBold color="#fff" />
                     </el-icon>
                 </div>
                 <!-- 右箭头 -->
@@ -50,8 +50,8 @@
                     class="image-preview-popup__arrow image-preview-popup__arrow--right"
                     @click.stop="handleNext"
                 >
-                    <el-icon size="20">
-                        <ArrowRightBold color="#F0D09C" />
+                    <el-icon size="16">
+                        <ArrowRightBold color="#fff" />
                     </el-icon>
                 </div>
             </div>

+ 38 - 4
src/i18n/messages.js

@@ -3,11 +3,11 @@ import { recordDetailsZh, recordDetailsEn } from "./recordDetails-messages";
 export default {
     zh: {
         tabbar: {
-            growthReport: "长势报告",
+            situationAssessment: "农情研判",
             agriFile: "农事档案",
             agriRecord: "农事规划",
             trace: "有味溯源",
-            execute: "农事执行",
+            schedule: "农事列表",
         },
         common: {
             current: "当前",
@@ -51,7 +51,19 @@ export default {
         garden: {
             currentFarm: "当前农场",
             setDefaultFarm: "设为默认农场",
+            enterFarm: "进入农场",
+            enterFarmSuccess: "已切换农场",
             addFarm: "新增农场",
+            currentPhenology: "当前物候:",
+            phenologyPlaceholder: "暂无物候信息",
+            pendingExecute: "待执行",
+            taskNamePlaceholder: "农事名称",
+            forward: "转发",
+            forwardTip: "转发功能即将开放",
+            inviteFarmer: "邀请农户",
+            inviteService: "邀请农服",
+            inviteTip: "邀请功能即将开放",
+            noFarm: "暂无农场",
             statFarm11: "农事11",
             statFarm22: "农事22",
             statSomeRisk: "某某风险",
@@ -264,6 +276,11 @@ export default {
             fromFarm: "来自某某农场农场",
             noData: "暂无数据",
             all: "全部",
+            highRisk: "高风险",
+            executeAreaLabel: "执行区域:",
+            forward: "转发",
+            viewDetail: "查看详情",
+            forwardTip: "转发功能即将开放",
             redispatch: "重新派发",
             remindAccept: "提醒接受",
             modifyInfo: "修改信息",
@@ -340,11 +357,11 @@ export default {
     },
     en: {
         tabbar: {
-            growthReport: "Growth Report",
+            situationAssessment: "Situation Assessment",
             agriFile: "Crop Archives",
             agriRecord: "Farm Planning",
             trace: "Traceability",
-            execute: "Execution",
+            schedule: "Schedule",
         },
         common: {
             current: "Current",
@@ -388,7 +405,19 @@ export default {
         garden: {
             currentFarm: "Current farm",
             setDefaultFarm: "Set as default",
+            enterFarm: "Enter farm",
+            enterFarmSuccess: "Farm switched",
             addFarm: "Add farm",
+            currentPhenology: "Current phenology: ",
+            phenologyPlaceholder: "No phenology info",
+            pendingExecute: "Pending",
+            taskNamePlaceholder: "Task name",
+            forward: "Forward",
+            forwardTip: "Forward feature coming soon",
+            inviteFarmer: "Invite farmer",
+            inviteService: "Invite agri service",
+            inviteTip: "Invite feature coming soon",
+            noFarm: "No farms",
             statFarm11: "Farm Work 11",
             statFarm22: "Farm Work 22",
             statSomeRisk: "Risk Alert",
@@ -601,6 +630,11 @@ export default {
             fromFarm: "From demo farm",
             noData: "No data",
             all: "All",
+            highRisk: "High risk",
+            executeAreaLabel: "Execution area: ",
+            forward: "Forward",
+            viewDetail: "View details",
+            forwardTip: "Forward feature coming soon",
             redispatch: "Redispatch",
             remindAccept: "Remind to accept",
             modifyInfo: "Edit info",

+ 7 - 0
src/router/globalRoutes.js

@@ -191,4 +191,11 @@ export default [
         meta: { keepAlive: true },
         component: () => import("@/views/old_mini/agri_file/pages/albumMap.vue"),
     },
+    // 更多农场
+    {
+        path: "/all_garden",
+        name: "AllGarden",
+        meta: { keepAlive: true },
+        component: () => import("@/components/allGarden.vue"),
+    },
 ];

+ 3 - 69
src/views/old_mini/agri_file/index.vue

@@ -1,20 +1,6 @@
 <template>
     <div class="agri-file-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
-        <div class="farm-header">
-            <div class="farm-header__info">
-                <el-avatar class="farm-header__avatar" :size="46" :src="circleUrl" />
-                <div class="farm-header__text">
-                    <div class="farm-header__name van-ellipsis">{{ farmName }}</div>
-                    <div class="farm-header__location van-ellipsis">{{ farmLocation }}</div>
-                </div>
-            </div>
-            <div class="farm-header__more">
-                <el-icon class="farm-header__more-icon">
-                    <Switch />
-                </el-icon>
-                <span>{{ t("agriFile.moreFarms") }}</span>
-            </div>
-        </div>
+        <farm-header />
         <div class="nav-cards">
             <div v-for="item in navCards" :key="item.key" class="nav-card"
                 :class="{ 'is-active': activeNav === item.key }" @click="activeNav = item.key">
@@ -69,11 +55,12 @@
 import { computed, nextTick, onActivated, onBeforeUnmount, onMounted, ref, watch } from "vue";
 import { useStore } from "vuex";
 import { useRouter } from "vue-router";
-import { Switch, Plus } from "@element-plus/icons-vue";
+import { Plus } from "@element-plus/icons-vue";
 import Overlay from "ol/Overlay";
 import { useI18n } from "@/i18n";
 import FileMap from "./fileMap";
 import * as util from "@/common/ol_common.js";
+import farmHeader from "@/components/farmHeader.vue";
 
 const { t } = useI18n();
 const store = useStore();
@@ -83,10 +70,6 @@ const tabBarHeight = computed(() => store.state.home.tabBarHeight);
 const defaultCover = require("@/assets/img/home/banner.png");
 const DEFAULT_CENTER = [113.6142086995688, 23.585836479509055];
 
-const defaultAvatar = require("@/assets/img/home/banner.png");
-const farmName = ref("");
-const farmLocation = ref("");
-const circleUrl = ref(defaultAvatar);
 const activeNav = ref("album");
 const mapContainer = ref(null);
 const fileMap = new FileMap();
@@ -185,19 +168,6 @@ const fillMockLabels = () => {
     albumList.value = albumList.value.map((item) => ({ ...item, name: albumName }));
 };
 
-const loadFarmInfo = () => {
-    try {
-        const farm = JSON.parse(localStorage.getItem("selectedFarmData") || "{}");
-        farmName.value = farm.farm_name || farm.name || t("agriFile.farmName");
-        farmLocation.value = farm.farm_address || farm.rawAddress || farm.city || t("agriFile.farmLocation");
-        circleUrl.value = farm.cover || farm.image || farm.avatar || defaultAvatar;
-    } catch {
-        farmName.value = t("agriFile.farmName");
-        farmLocation.value = t("agriFile.farmLocation");
-        circleUrl.value = defaultAvatar;
-    }
-};
-
 const goAddZone = () => {
     router.push("/add_zone");
 };
@@ -208,12 +178,10 @@ const goAlbumMap = () => {
 
 onMounted(() => {
     fillMockLabels();
-    loadFarmInfo();
     initAlbumMap();
 });
 onActivated(() => {
     fillMockLabels();
-    loadFarmInfo();
     initAlbumMap();
 });
 onBeforeUnmount(clearMapOverlays);
@@ -235,40 +203,6 @@ watch(activeNav, (key) => {
     overflow: hidden;
     background: linear-gradient(180deg, #D1EBFF 0%, #FFFFFF 100%);
 
-    .farm-header {
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        padding: 10px;
-
-        .farm-header__info {
-            display: flex;
-            align-items: center;
-            gap: 6px;
-
-            .farm-header__name {
-                font-size: 16px;
-                font-weight: 500;
-            }
-
-            .farm-header__location {
-                margin-top: 2px;
-                color: rgba(0, 0, 0, 0.5);
-                font-size: 12px;
-            }
-        }
-
-        .farm-header__more {
-            display: flex;
-            align-items: center;
-            gap: 4px;
-            padding: 6px 10px;
-            border-radius: 16px;
-            background: #fff;
-            color: #2199F8;
-        }
-    }
-
     .nav-cards {
         position: relative;
         z-index: 2;

+ 7 - 5
src/views/old_mini/work_execute/components/calendar.vue

@@ -29,7 +29,7 @@
             >
                 <div class="day-box">
                     <span class="days-week">{{ day.isToday ? "今天" : `周${day.dayOfWeek}` }}</span>
-                    <span class="days-one">{{ day.day }}</span>
+                    <span class="days-one">{{ day.dayLabel }}</span>
                 </div>
                 <div v-if="day.solarTerm" class="solar-term">{{ day.solarTerm }}</div>
                 <div v-if="day.typeName" class="type-num">{{ day.typeName.lengthVal }}</div>
@@ -39,12 +39,12 @@
                 </div> -->
             </div>
 
-            <div class="days-fixed">
+            <!-- <div class="days-fixed">
                 <div>
                     <el-icon><Calendar /></el-icon>
                 </div>
                 <div>日历</div>
-            </div>
+            </div> -->
         </div>
     </div>
 </template>
@@ -89,8 +89,11 @@ const calendarDays = computed(() => {
         // 获取该日期的农事数据
         const typeName = getTaskByDate(formatDate(date));
 
+        const month = String(date.getMonth() + 1).padStart(2, "0");
+        const dayNum = String(date.getDate()).padStart(2, "0");
         daysList.push({
             day: date.getDate(),
+            dayLabel: `${month}.${dayNum}`,
             date: formatDate(date),
             isToday: formatDate(date) === formatDate(today),
             dayOfWeek: displayDayOfWeek,
@@ -359,7 +362,7 @@ const selectedDate = ref(null);
             }
         }
         .day-box {
-            background: #F7F7F7;
+            background: #fff;
             color: #000;
             border-radius: 8px;
             padding: 7px 0;
@@ -391,7 +394,6 @@ const selectedDate = ref(null);
             margin: 0 auto;
             font-size: 14px;
             line-height: 16px;
-            font-weight: bold;
             padding-top: 2px;
             // width: 32px;
             // height: 32px;

+ 176 - 880
src/views/old_mini/work_execute/index.vue

@@ -1,5 +1,6 @@
 <template>
     <div class="task-page" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
+        <farm-header class="task-page__header" />
         <div class="task-top">
             <div class="map-container" ref="mapContainer"></div>
         </div>
@@ -7,413 +8,137 @@
             <customCalendar ref="calendarRef" @dateSelect="handleDateSelect"></customCalendar>
         </div>
         <div class="task-list">
-            <div class="list-filter">
+            <div class="work-task-list">
                 <div
-                    v-for="item in filterTabs"
-                    :key="item.value"
-                    class="filter-item"
-                    :class="{ active: activeIndex === item.value }"
-                    @click="handleActiveFilter(item.value)"
+                    v-for="item in taskList"
+                    :key="item.id"
+                    class="task-card"
+                    :class="{ 'is-risk': item.isHighRisk }"
                 >
-                    {{ t(item.labelKey) }}({{ taskCounts[item.value] || 0 }})
-                </div>
-            </div>
-            <div class="list-box">
-                <div class="list-header">
-                    <div class="select-group">
-                        <el-select class="select-item" v-model="selectParma.farmWorkTypeId" :placeholder="t('workExecute.farmWorkType')"
-                            @change="resetAndLoad">
-                            <el-option v-for="item in farmWorkTypeList" :key="item.id"
-                                :label="item.id === 0 ? t('workExecute.all') : item.name"
-                                :value="item.id" />
-                        </el-select>
-                        <el-select class="select-item" v-model="selectParma.districtCode" :placeholder="t('workExecute.farmFilter')"
-                            @change="resetAndLoad">
-                            <el-option v-for="item in districtList" :key="item.code" :label="item.name"
-                                :value="item.code" />
-                        </el-select>
+                    <div v-if="item.cornerTip" class="task-card__corner">{{ item.cornerTip }}</div>
+
+                    <div class="task-card__header">
+                        <img class="task-card__icon" src="@/assets/img/home/task.png" alt="" />
+                        <span class="task-card__title van-ellipsis">{{ item.title }}</span>
+                        <span v-if="item.isHighRisk" class="risk-badge">{{ t("workExecute.highRisk") }}</span>
                     </div>
-                    <!-- <div class="action-btn">{{ t("workExecute.batchAccept") }}</div> -->
-                </div>
-                <!-- 任务列表 -->
-                <div class="work-task-list" v-loading="loading" element-loading-background="rgba(0, 0, 0, 0.3)">
-                    <div class="task-item" v-for="(item, index) in taskList" :key="index"
-                        :class="[
-                            isAbnormalItem(item) ? 'timeout-item' : '',
-                            locale === 'en' ? 'task-item--en' : '',
-                        ]" @click="handleItem(item, index)">
-                        <div class="item-title" :class="{ 'item-title--en': locale === 'en' }">
-                            <img class="task-icon" src="@/assets/img/home/task.png" alt="">
-                            <div class="item-title-main">
-                                <span class="title-text">{{ item?.operation?.name }}</span>
-                                <span class="task-status" :class="`task-status--${getWorkType(item.operation?.type)}`">{{
-                                    item.operation?.type }}</span>
-                            </div>
-                        </div>
-                        <span class="task-tag timeout" v-if="isAbnormalItem(item)">
-                            <el-icon>
-                                <WarningFilled />
-                            </el-icon>
-                            {{ getAbnormalLabel() }}
-                        </span>
-                        <span class="task-tag" v-else :class="`task-tag--${getStatusTagClass()}`">
-                            <el-icon v-if="activeStatus === '已完成'" class="task-tag__icon">
-                                <CircleCheck />
-                            </el-icon>
-                            {{ getStatusLabel(activeStatus) }}
-                        </span>
-                        <div class="item-content">
-                            <div class="item-info">
-                                <div class="info-item">
-                                    <div class="info-name">{{ t("workExecute.responsible") }}</div><span class="val-text">
-                                        {{ item.operation?.executor }}</span>
-                                </div>
-                                <div class="info-item" v-if="mode !== 'review'">
-                                    <div class="info-name">{{ t("workExecute.farmWorkDetail") }}</div><span class="val-text">
-                                        {{ item.operation?.work_detail }}</span>
-                                </div>
-                                <div class="info-item">
-                                    <div class="info-name">{{ t("workExecute.farmSituation") }}</div><span class="val-text">
-                                        {{ item.operation?.work_reason }}</span>
-                                </div>
-                            </div>
-                            <div class="excutor-info">
-                                <div class="executor-stats">
-                                    <div class="stat-cell">
-                                        <div class="stat-value">
-                                            {{ item.time ? formatGMTToYMD(item.time) : "--" }}
-                                        </div>
-                                        <div class="stat-label">{{ t("workExecute.executeTime") }}</div>
-                                    </div>
-                                    <div class="cell-line"></div>
-                                    <div class="stat-cell">
-                                        <div class="stat-value">{{ item.geohash_sample || "--" }}</div>
-                                        <div class="stat-label">{{ t("workExecute.executeArea") }}</div>
-                                    </div>
-                                    <div class="cell-line"></div>
-                                    <div class="stat-cell">
-                                        <div class="stat-value">{{ item.operation?.machine_code || "--" }}</div>
-                                        <div class="stat-label">{{ t("workExecute.executeMachine") }}</div>
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
 
-                        <div
-                            v-if="activeStatus === '已完成'"
-                            class="execute-photos"
-                            @click.stop
-                        >
-                            <div class="execute-photos__wrap">
-                                <span class="execute-photos__label">{{ t('workExecute.executePhotos') }}</span>
-                                <div class="execute-photos__list">
-                                    <div
-                                        v-for="(photo, photoIndex) in getExecutePhotoList(item)"
-                                        :key="photoIndex"
-                                        class="execute-photos__item"
-                                    >
-                                        <img :src="photo" alt="" />
-                                    </div>
-                                </div>
-                            </div>
+                    <div class="task-card__body">
+                        <div class="info-line">
+                            {{ t("workExecute.farmSituation") }}{{ item.analysisSummary }}
                         </div>
-
-                        <div class="unqualified-reason" v-if="isAbnormalItem(item) && activeStatus === '已完成'">
-                            {{ t("workExecute.unqualifiedReason") }}{{ item.operation?.reason || t("workExecute.defaultUnqualifiedReason") }}
+                        <div class="info-line">
+                            {{ t("workExecute.executeAreaLabel") }}{{ item.areaCode }}
                         </div>
+                    </div>
 
-                        <div class="compare-imgs" v-if="isAbnormalItem(item) && activeStatus === '已完成' && (item.beforeImage || item.afterImage)"
-                            @click.stop>
-                            <div class="img-tag">{{ t("workExecute.before") }}</div>
-                            <div class="img-tag right-tag">{{ t("workExecute.after") }}</div>
-                            <div class="img-item" v-if="item.beforeImage">
-                                <img :src="getWorkImageUrl(item.beforeImage)" alt="" />
-                            </div>
-                            <div class="img-item" v-if="item.afterImage">
-                                <img :src="getWorkImageUrl(item.afterImage)" alt="" />
-                            </div>
+                    <div class="task-card__footer">
+                        <div class="card-btn card-btn--ghost" @click.stop="handleForward(item)">
+                            {{ t("workExecute.forward") }}
                         </div>
-
-                        <div class="task-footer">
-                            <div class="farm-info">{{ t("workExecute.fromFarm") }}</div>
-                            <div class="btn-group" v-if="getItemStatusButtons(item).length">
-                                <div v-for="btn in getItemStatusButtons(item)" :key="btn.label" class="edit-btn"
-                                    :class="btn.type" @click.stop="handleStatusBtn(btn, item, index)">
-                                    {{ btn.label }}
-                                </div>
-                            </div>
+                        <div class="card-btn card-btn--primary" @click.stop="handleViewDetail(item)">
+                            {{ t("workExecute.viewDetail") }}
                         </div>
                     </div>
-                    <div class="empty-tip" v-if="!loading && taskList.length === 0">{{ t("workExecute.noData") }}</div>
                 </div>
+
+                <div v-if="!taskList.length" class="empty-tip">{{ t("workExecute.noData") }}</div>
             </div>
         </div>
     </div>
 </template>
 
 <script setup>
-import { computed, nextTick, onActivated, onMounted, ref, watch } from "vue";
+import { computed, nextTick, onActivated, onMounted, ref } from "vue";
 import { useStore } from "vuex";
-import IndexMap from "./index";
 import { useRouter } from "vue-router";
-import { WarningFilled, CircleCheck } from "@element-plus/icons-vue";
 import { ElMessage } from "element-plus";
-import config from "@/api/config.js";
+import IndexMap from "./index";
 import customCalendar from "./components/calendar.vue";
+import farmHeader from "@/components/farmHeader.vue";
 import { useI18n } from "@/i18n";
-import executePhotoPlaceholder1 from "@/assets/img/common/sd-1.jpg";
-import executePhotoPlaceholder2 from "@/assets/img/common/sd-2.jpg";
-import executePhotoPlaceholder3 from "@/assets/img/common/sd-3.jpg";
-import executePhotoPlaceholder4 from "@/assets/img/common/sd-4.jpg";
-
-const EXECUTE_PHOTO_PLACEHOLDERS = [
-    executePhotoPlaceholder1,
-    executePhotoPlaceholder2,
-    executePhotoPlaceholder3,
-    executePhotoPlaceholder4,
-];
-
-const { t, locale } = useI18n();
+
+const { t } = useI18n();
 const store = useStore();
 const router = useRouter();
 const indexMap = new IndexMap();
 const mapContainer = ref(null);
+const calendarRef = ref(null);
 const tabBarHeight = computed(() => store.state.home.tabBarHeight);
-const selectParma = ref({
-    farmWorkTypeId: null,
-    districtCode: null,
-});
-
-const taskList = ref([])
-
-const mode = "execute";
-const activeStatus = ref("待接受");
-
-const STATUS_BTN_MAP = {
-    待接受: [
-        { labelKey: "workExecute.modifyInfo", type: "normal", action: "redispatch" },
-        { labelKey: "workExecute.acceptTask", type: "primary", action: "remindAccept" },
-    ],
-    已接受: [
-        { labelKey: "workExecute.modifyInfo", type: "normal", action: "redispatch" },
-        { labelKey: "workExecute.startExecute", type: "primary-blue", action: "startExecute" },
-    ],
-    执行中: [{ labelKey: "workExecute.markComplete", type: "primary-green", action: "markComplete" }],
-    已超时: [
-        { labelKey: "workExecute.redispatch", type: "normal", action: "redispatch" },
-        { labelKey: "workExecute.remindAccept", type: "primary", action: "remindAccept" },
-    ],
-    未达标: [{ labelKey: "workExecute.redispatchAgain", type: "primary", action: "redispatch" }],
-};
-
-const STATUS_LABEL_MAP = {
-    待接受: "workExecute.pending",
-    已接受: "workExecute.accepted",
-    执行中: "workExecute.executing",
-    已完成: "workExecute.completed",
-    已超时: "workExecute.timeout",
-    未达标: "workExecute.unqualified",
-};
-
-const getStatusLabel = (status) => t(STATUS_LABEL_MAP[status] || status);
-
-const STATUS_TAG_CLASS_MAP = {
-    待接受: "pending",
-    已接受: "accepted",
-    执行中: "executing",
-    已完成: "completed",
-};
-
-const getStatusTagClass = () => STATUS_TAG_CLASS_MAP[activeStatus.value] || "pending";
-
-const STATUS_BY_TAB_VALUE = { 0: "待接受", 1: "执行中", 2: "已接受", 3: "已完成" };
-
-const isTruthyFlag = (value) => Number(value) === 1 || value === true;
-
-const ABNORMAL_FLAG_MAP = {
-    待接受: ["is_aovertime"],
-    已接受: ["is_eovertime", "is_aovertime"],
-    执行中: ["is_covertime", "is_aovertime"],
-    已完成: ["is_anormal", "is_unqualified", "is_aovertime"],
-};
-
-const isAbnormalItem = (item, status = activeStatus.value) => {
-    const operation = item?.operation || {};
-    const flags = ABNORMAL_FLAG_MAP[status] || ["is_aovertime"];
-    return flags.some((field) => isTruthyFlag(operation[field]));
-};
-
-const ABNORMAL_LABEL_KEY_MAP = {
-    待接受: "workExecute.acceptTimeout",
-    已接受: "workExecute.executeAbnormal",
-    执行中: "workExecute.completeAbnormal",
-    已完成: "workExecute.unqualified",
-};
 
-const ABNORMAL_STATUS_STR_MAP = {
-    待接受: "接受异常",
-    已接受: "执行异常",
-    执行中: "完成异常",
-    已完成: "未达标",
-};
-
-const getAbnormalLabel = () => t(ABNORMAL_LABEL_KEY_MAP[activeStatus.value] || "workExecute.acceptTimeout");
-
-const getAbnormalStatusStr = () => ABNORMAL_STATUS_STR_MAP[activeStatus.value] || "接受异常";
-
-const mapStatusButtons = (buttons) =>
-    buttons.map((btn) => ({
-        ...btn,
-        label: t(btn.labelKey),
-    }));
-
-const getItemStatusButtons = (item) => {
-    if (isAbnormalItem(item)) {
-        if (activeStatus.value === "已完成") {
-            return mapStatusButtons(STATUS_BTN_MAP["未达标"] || []);
-        }
-        return [];
-    }
-    return mapStatusButtons(STATUS_BTN_MAP[activeStatus.value] || []);
-};
+const formatDateYMD = (date = new Date()) =>
+    `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
 
-const formatGMTToYMD = (gmtString) => {
-    const date = new Date(gmtString);
-    return date.toISOString().split("T")[0];
-};
+const filterDate = ref(formatDateYMD());
 
-const getWorkImageUrl = (image) => {
-    if (!image?.cloud_filename) return "";
-    return config.base_img_url3 + image.cloud_filename;
-};
+/** 假数据:后续可替换为接口 */
+const taskList = ref([
+    {
+        id: 1,
+        title: "喷施杀菌剂",
+        isHighRisk: true,
+        cornerTip: "发现异常后机动执行",
+        analysisSummary: "近期高温高湿,蒂蛀虫风险上升",
+        areaCode: "A-01 / 分区一",
+        wkt: ""
+    },
+    {
+        id: 2,
+        title: "叶面追肥",
+        isHighRisk: false,
+        cornerTip: "",
+        analysisSummary: "转色期养分需求增加,需补充钾肥",
+        areaCode: "B-03 / 分区二",
+    },
+    {
+        id: 3,
+        title: "灌溉补水",
+        isHighRisk: true,
+        cornerTip: "发现异常后机动执行",
+        analysisSummary: "土壤墒情偏低,局部出现轻度干旱胁迫",
+        areaCode: "C-02 / 分区三",
+    },
+    {
+        id: 4,
+        title: "病虫害巡查",
+        isHighRisk: false,
+        cornerTip: "",
+        analysisSummary: "重点关注新梢与果实表面异常斑点",
+        areaCode: "D-05 / 全园",
+    },
+]);
 
-const resolveExecutePhotoUrl = (src) => {
-    if (!src) return "";
-    if (typeof src === "object" && src.cloud_filename) {
-        return config.base_img_url3 + src.cloud_filename;
-    }
-    if (typeof src === "string") {
-        if (src.startsWith("http")) return src;
-        return config.base_img_url2 + src;
-    }
-    return "";
+const handleDateSelect = (date) => {
+    filterDate.value = date ?? formatDateYMD();
 };
 
-const getExecutePhotoList = (item) => {
-    const operation = item?.operation || {};
-    const raw =
-        operation.execute_evidence ??
-        operation.executeEvidence ??
-        item.execute_evidence ??
-        item.executeEvidence;
-
-    let photos = [];
-
-    if (raw) {
-        if (Array.isArray(raw)) {
-            photos = raw.map(resolveExecutePhotoUrl).filter(Boolean);
-        } else if (typeof raw === "string") {
-            try {
-                const parsed = JSON.parse(raw);
-                if (Array.isArray(parsed)) {
-                    photos = parsed.map(resolveExecutePhotoUrl).filter(Boolean);
-                }
-            } catch {
-                const url = resolveExecutePhotoUrl(raw);
-                if (url) photos = [url];
-            }
-        }
-    }
-
-    return (photos.length ? photos : EXECUTE_PHOTO_PLACEHOLDERS).slice(0, 4);
+const handleForward = () => {
+    ElMessage.info(t("workExecute.forwardTip"));
 };
 
-const handleItem = (item, index) => {
-    const statusStr = isAbnormalItem(item) ? getAbnormalStatusStr() : activeStatus.value;
-    const status = activeIndex.value;
+const handleViewDetail = (item) => {
     router.push({
         path: "/agri_record_detail",
         query: {
-            id: item.operation?.work_id,
-            status,
-            statusStr,
+            id: item.id,
+            statusStr: item.isHighRisk ? "执行异常" : "待接受",
         },
     });
 };
 
-const handleStatusBtn = (btn, item, index) => {
-    if (btn.action === "redispatch") {
-        ElMessage.info(t("workExecute.redispatchMsg", { name: item.operation?.name }));
-    } else {
-        ElMessage.success(t("workExecute.remindSuccess"));
-    }
-};
-
-const formatDateYMD = (date = new Date()) =>
-    `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
-
-const calendarRef = ref(null);
-const filterDate = ref(formatDateYMD());
-const fullTaskList = ref([]);
-
-const getCalendarTaskList = (list) => {
-    return list.map((item) => ({
-        expectedExecuteDate: item.time,
-        executeDeadlineDate: item.time,
-    }));
-};
-
-const syncCalendarData = () => {
-    nextTick(() => {
-        calendarRef.value?.setSolarTerm(getCalendarTaskList(fullTaskList.value));
-    });
-};
-
-const handleDateSelect = (date) => {
-    filterDate.value = date ?? formatDateYMD();
-    resetAndLoad();
-    syncCalendarData();
-};
-// 各状态任务数量,key 为 work_operation_type:0待接受 1执行中 2已接受 3已完成
-const taskCounts = ref({ 0: 0, 1: 0, 2: 0, 3: 0 });
-const STATUS_TAB_VALUES = [0, 2, 1, 3];
-const filterTabs = [
-    { value: 0, labelKey: "workExecute.pending" },
-    { value: 2, labelKey: "workExecute.accepted" },
-    { value: 1, labelKey: "workExecute.executing" },
-    { value: 3, labelKey: "workExecute.completed" },
-];
-// 当前选中的筛选索引
-const activeIndex = ref(0);
-const loading = ref(false);
-
-const cityCode = ref("");
-//根据城市的坐标返回区县列表
-const districtList = ref([]);
-
-//农事类型列表
-const farmWorkTypeList = ref([]);
-function getFarmWorkTypeList() {
-    VE_API.z_farm_work_record.getFarmWorkTypeList().then(({ data }) => {
-        farmWorkTypeList.value = data;
-        farmWorkTypeList.value.unshift({ id: 0, name: "全部" });
-    });
-}
-
 const mapPoint = ref(null);
 
 onMounted(() => {
     mapPoint.value = store.state.home.miniUserLocationPoint;
-    getFarmWorkTypeList();
-    syncCalendarData();
     nextTick(() => {
-        indexMap.initMap(mapPoint.value, mapContainer.value, true);
-        indexMap.initData(taskList.value, '', 'farmPoint');
+        if (mapContainer.value) {
+            indexMap.initMap(mapPoint.value, mapContainer.value, true);
+            indexMap.initData([], "", "farmPoint");
+        }
     });
 });
 
 onActivated(() => {
-    resetAndLoad();
     nextTick(() => {
         if (!indexMap.kmap) {
             if (mapContainer.value) {
@@ -422,7 +147,6 @@ onActivated(() => {
             }
             return;
         }
-
         if (mapContainer.value && indexMap.kmap.map) {
             const checkAndUpdateSize = () => {
                 const container = mapContainer.value;
@@ -438,81 +162,6 @@ onActivated(() => {
         }
     });
 });
-
-const getRegionRecordList = (data) => {
-    if (!Array.isArray(data)) return [];
-    return data.filter((item) => item.operation !== null);
-};
-
-const getRegionRecordParams = (workOperationType) => ({
-    // farm_id: '320',
-    // time: '2026-06-30',
-    farm_id: localStorage.getItem('selectedFarmId'),
-    time: filterDate.value,
-    work_operation_type: workOperationType,
-});
-
-const taskListsByType = ref({ 0: [], 1: [], 2: [], 3: [] });
-
-const sortTaskListByAbnormal = (list, tabValue) => {
-    const status = STATUS_BY_TAB_VALUE[tabValue] || "待接受";
-    return [...list].sort((a, b) => {
-        const aAbnormal = isAbnormalItem(a, status) ? 1 : 0;
-        const bAbnormal = isAbnormalItem(b, status) ? 1 : 0;
-        if (bAbnormal !== aAbnormal) return bAbnormal - aAbnormal;
-        return Number(a.AO ?? 0) - Number(b.AO ?? 0);
-    });
-};
-
-const loadPageData = async () => {
-    loading.value = true;
-    try {
-        const results = await Promise.all(
-            STATUS_TAB_VALUES.map((type) => VE_API.farm.getRegionRecord(getRegionRecordParams(type)))
-        );
-        const counts = { 0: 0, 1: 0, 2: 0, 3: 0 };
-        const listsByType = { 0: [], 1: [], 2: [], 3: [] };
-        STATUS_TAB_VALUES.forEach((type, index) => {
-            const { code, data } = results[index] || {};
-            const list = code === 200 ? getRegionRecordList(data) : [];
-            listsByType[type] = sortTaskListByAbnormal(list, type);
-            counts[type] = list.length;
-        });
-        taskListsByType.value = listsByType;
-        taskCounts.value = counts;
-        taskList.value = listsByType[activeIndex.value] || [];
-    } finally {
-        loading.value = false;
-    }
-};
-
-// 监听 activeIndex 变化,切换 Tab 时复用已请求的数据
-watch(activeIndex, () => {
-    taskList.value = taskListsByType.value[activeIndex.value] || [];
-});
-
-// 重置并重新加载
-const resetAndLoad = () => {
-    loadPageData();
-};
-
-function handleActiveFilter(i) {
-    activeIndex.value = i;
-    selectParma.value.districtCode = cityCode.value;
-    selectParma.value.farmWorkTypeId = null;
-    const statusMap = { 0: "待接受", 1: "执行中", 2: "已接受", 3: "已完成" };
-    activeStatus.value = STATUS_BY_TAB_VALUE[i] || statusMap[i] || "待接受";
-    syncCalendarData();
-}
-
-const getWorkType = (type) => {
-    const map = {
-        '标准防治类': 1,
-        '标准施肥类': 2,
-        '标准调节类': 3,
-    }
-    return map[type] || type;
-}
 </script>
 
 <style lang="scss" scoped>
@@ -521,491 +170,138 @@ const getWorkType = (type) => {
     height: calc(100vh - 50px);
     overflow: auto;
     box-sizing: border-box;
-    background: #FFFFFF;
+    background: linear-gradient(180deg, #d1ebff 0%, #ffffff 100%);
 
     .map-container {
         width: 100%;
         height: 162px;
-        clip-path: inset(0px round 8px);
-    }
-
-    .list-box {
-        background: #F1F3F4;
-        padding: 12px 12px 8px 12px;
-
-        .list-header {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-
-            .action-btn {
-                box-sizing: border-box;
-                height: 32px;
-                line-height: 30px;
-                color: #2199F8;
-                font-size: 14px;
-                border-radius: 5px;
-                padding: 0 10px;
-                background: #FFFFFF;
-                border: 1px solid #2199F8;
-                color: #2199F8;
-            }
-        }
-    }
-
-    .select-group {
-        display: flex;
-        padding: 0px 12px 0 12px;
-
-        .select-item {
-            width: 100%;
-
-            ::v-deep {
-                .el-select__wrapper {
-                    text-align: center;
-                    gap: 2px;
-                    box-shadow: none;
-                    justify-content: center;
-                    background: none;
-                }
-
-                .el-select__selection {
-                    flex: none;
-                    width: fit-content;
-                }
-
-                .el-select__placeholder {
-                    position: static;
-                    transform: none;
-                    width: fit-content;
-                    color: rgba(0, 0, 0, 0.2);
-                }
-
-                .el-select__caret {
-                    color: rgba(0, 0, 0, 0.2);
-                }
-            }
-        }
+        clip-path: inset(0 round 8px);
     }
 
     .calendar-container {
-        padding: 4px 12px 8px 12px;
+        padding: 4px 12px 8px;
     }
 
     .task-top {
-        padding: 10px 12px 0 12px;
+        padding: 10px 12px 0;
     }
 
-    .task-content-loading {
-        height: 80px;
-        border-radius: 8px;
-        position: absolute;
-        top: 60px;
-        left: 0;
-        width: 100%;
-    }
-
-    .task-content {
-        min-height: 80px;
+    .task-list {
+        padding: 0 12px 20px;
     }
 
-    .empty-data {
-        text-align: center;
-        font-size: 14px;
-        color: #6f7274;
-        padding: 20px 0;
+    .work-task-list {
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
     }
 
-    .task-list {
+    .task-card {
         position: relative;
+        padding: 14px 12px 12px;
+        border-radius: 8px;
         background: #fff;
-    }
-
-    .list-filter {
-        display: flex;
-        align-items: center;
-        justify-content: space-around;
-        margin-top: 10px;
+        box-sizing: border-box;
+        overflow: hidden;
+        box-shadow: 0px 2px 2px 0px rgba(0, 0, 0, 0.04);
+
+        &__corner {
+            position: absolute;
+            top: 0;
+            right: 0;
+            max-width: 58%;
+            padding: 4px 10px;
+            border-radius: 0 8px 0 8px;
+            background: rgba(255, 106, 106, 0.1);
+            color: #FF6A6A;
+            font-size: 12px;
+            line-height: 16px;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+        }
 
-        .filter-item {
-            padding: 0 6px;
-            height: 32px;
-            color: #1D2129;
-            font-size: 14px;
-            border-radius: 20px;
-            position: relative;
+        &__header {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+            padding-right: 8px;
+            min-width: 0;
+        }
 
-            &.active {
-                color: #2199f8;
+        &__icon {
+            width: 16px;
+            height: 16px;
+            flex-shrink: 0;
+        }
 
-                &::after {
-                    content: "";
-                    position: absolute;
-                    bottom: 0;
-                    left: 0;
-                    width: 100%;
-                    height: 2px;
-                    background: #2199f8;
-                }
-            }
+        &__title {
+            max-width: 160px;
+            font-size: 16px;
+            font-weight: 500;
+            color: #000;
         }
-    }
 
-    .work-task-list {
-        margin-top: 10px;
+        .risk-badge {
+            flex-shrink: 0;
+            height: 18px;
+            padding: 0 6px;
+            border-radius: 2px;
+            background: rgba(255, 106, 106, 0.12);
+            color: #ff6a6a;
+            font-size: 12px;
+        }
 
-        .task-item {
+        &__body {
             margin-top: 10px;
-            background: #fff;
-            border-radius: 8px;
-            padding: 12px 16px;
-            border: 1px solid rgba(0, 0, 0, 0.08);
-            position: relative;
-
-            &.timeout-item {
-                border: 1px solid #FF6A6A;
-                box-shadow: 0px 0px 4px 0px #FF6A6A66;
-            }
-
-            &--en {
-                padding-top: 22px;
-            }
-
-            .item-title {
-                display: flex;
-                align-items: center;
-                gap: 8px;
-                padding-bottom: 10px;
-                border-bottom: 1px solid rgba(0, 0, 0, 0.06);
-                color: #1d2129;
-                font-size: 16px;
-
-                .task-icon {
-                    width: 16px;
-                    flex-shrink: 0;
-                }
-
-                .item-title-main {
-                    flex: 1;
-                    min-width: 0;
-                    display: flex;
-                    align-items: center;
-                    gap: 8px;
-                }
-
-                &--en {
-                    align-items: flex-start;
-
-                    .task-icon {
-                        margin-top: 2px;
-                    }
-
-                    .item-title-main {
-                        flex-direction: column;
-                        align-items: flex-start;
-                        gap: 4px;
-                    }
-
-                    .task-status {
-                        margin-left: 0;
-                    }
-                }
-
-                .title-text {
-                    min-width: 0;
-                }
-
-                .task-status {
-                    margin-left: 0;
-                    border-radius: 2px;
-                    height: 20px;
-                    line-height: 20px;
-                    padding: 1px 6px;
-                    box-sizing: border-box;
-                    font-size: 12px;
-                    color: #ffb32f;
-                    background: rgba(255, 179, 47, 0.1);
-
-                    &.task-status--1 {
-                        background: rgba(33, 153, 248, 0.1);
-                        color: #2199f8;
-                    }
-
-                    &.task-status--2 {
-                        background: rgba(255, 179, 47, 0.1);
-                        color: #FFB32F;
-                    }
-
-                    &.task-status--3 {
-                        background: rgba(58, 173, 148, 0.1);
-                        color: #3AAD94;
-                    }
-                }
-            }
-
-            .task-tag {
-                position: absolute;
-                right: 0;
-                top: 0;
-                background: rgba(255, 149, 61, 0.1);
-                color: #F46E00;
-                font-size: 12px;
-                padding: 0 10px;
-                height: 25px;
-                line-height: 25px;
-                border-radius: 0 7px 0 8px;
-
-                &.timeout {
-                    background: #FF6A6A;
-                    color: #fff;
-                    display: flex;
-                    align-items: center;
-                    gap: 2px;
-                }
-
-                &--pending {
-                    background: rgba(255, 149, 61, 0.1);
-                    color: #F46E00;
-                }
-
-                &--accepted {
-                    background: rgba(33, 153, 248, 0.1);
-                    color: #2199F8;
-                }
-
-                &--executing {
-                    background: rgba(33, 153, 248, 0.1);
-                    color: #2199F8;
-                }
-
-                &--completed {
-                    background: rgba(74, 191, 50, 0.1);
-                    color: #4ABF32;
-                    display: flex;
-                    align-items: center;
-                    gap: 2px;
-
-                    .task-tag__icon {
-                        font-size: 12px;
-                    }
-                }
-            }
-
-            .execute-photos {
-                margin: 8px 0 4px;
-
-                &__wrap {
-                    background: #ebf5ff;
-                    border: 1px solid rgba(33, 153, 248, 0.15);
-                    border-radius: 12px;
-                    padding: 12px;
-                }
-
-                &__label {
-                    display: inline-block;
-                    padding: 4px 10px;
-                    background: #2199f8;
-                    color: #fff;
-                    font-size: 12px;
-                    line-height: 18px;
-                    border-radius: 4px;
-                    margin-bottom: 8px;
-                }
-
-                &__list {
-                    display: flex;
-                    gap: 8px;
-                    background: #fff;
-                    border-radius: 10px;
-                    padding: 10px;
-                }
-
-                &__item {
-                    flex: 1;
-                    aspect-ratio: 1;
-                    border-radius: 8px;
-                    overflow: hidden;
-
-                    img {
-                        display: block;
-                        width: 100%;
-                        height: 100%;
-                        object-fit: cover;
-                    }
-                }
-            }
-
-            .item-content {
-                padding: 12px 0;
-
-                .excutor-info {
-                    margin-top: 12px;
-                    background: rgba(189, 189, 189, 0.1);
-                    border-radius: 6px;
-                    padding-bottom: 12px;
-
-                    .excutor-info-top {
-                        padding: 12px 12px 0 12px;
-                    }
-                }
-
-                .executor-stats {
-                    display: flex;
-                    align-items: center;
-                    padding-top: 10px;
-                    justify-content: space-around;
-                }
-
-                .cell-line {
-                    height: 20px;
-                    width: 1px;
-                    background: #e5e6eb;
-                }
+            padding-top: 10px;
+            border-top: 0.5px solid rgba(0, 0, 0, 0.05);
+        }
 
-                .stat-value {
-                    font-size: 14px;
-                    font-weight: 500;
-                    color: #0B0B0B;
-                    margin-bottom: 4px;
-                    word-break: break-all;
-                }
+        .info-line {
+            font-size: 14px;
+            line-height: 20px;
+            color: rgba(111, 114, 116, 0.6);
 
-                .stat-label {
-                    font-size: 12px;
-                    line-height: 20px;
-                    color: rgba(107, 107, 107, 0.5);
-                    text-align: center;
-                }
+            & + .info-line {
+                margin-top: 6px;
             }
+        }
 
-            .item-info {
-                color: rgba(111, 114, 116, 0.6);
-                font-size: 14px;
-                line-height: 21px;
-
-                .info-item {
-                    display: flex;
-
-                    .info-name {
-                        flex: none;
-                    }
-                }
-
-                .val-text {
-                    // color: #1d2129;
-                }
-
-                .info-item+.info-item {
-                    padding-top: 6px;
-                }
-            }
+        &__footer {
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            gap: 12px;
+            margin-top: 14px;
+        }
 
-            .task-footer {
-                padding-top: 4px;
-                display: flex;
-                align-items: center;
-                justify-content: space-between;
-                .farm-info {
-                    font-size: 14px;
-                    color: rgba(32, 32, 32, 0.4);
-                }
-            }
+        .card-btn {
+            height: 32px;
+            border-radius: 20px;
+            text-align: center;
+            line-height: 32px;
+            font-size: 12px;
 
-            .btn-group {
-                display: flex;
-                align-items: center;
-                justify-content: end;
-                gap: 10px;
+            &--ghost {
+                background: #F6F6F6;
+                color: #000000;
+                padding: 0 20px;
             }
 
-            .edit-btn {
-                height: 30px;
+            &--primary {
+                background: rgba(33, 153, 248, 0.1);
+                color: #2199f8;
                 padding: 0 16px;
-                border-radius: 24px;
-                text-align: center;
-                line-height: 30px;
-                font-size: 14px;
-
-                &.normal {
-                    background: #F6F6F6;
-                    color: #000000;
-                }
-
-                &.primary {
-                    background: #FF953D;
-                    color: #fff;
-                }
-
-                &.primary-blue {
-                    background: #2199F8;
-                    color: #fff;
-                }
-
-                &.primary-green {
-                    background: #37C11B;
-                    color: #fff;
-                }
-            }
-
-            .compare-imgs {
-                display: flex;
-                align-items: center;
-                gap: 10px;
-                position: relative;
-                margin: 10px 0;
-
-                .img-tag {
-                    position: absolute;
-                    z-index: 10;
-                    top: 0;
-                    left: 0;
-                    font-size: 12px;
-                    color: #fff;
-                    background: rgba(0, 0, 0, 0.7);
-                    border-radius: 5px 0 5px 0;
-                    padding: 0 8px;
-                    height: 15px;
-                    line-height: 15px;
-
-                    &.right-tag {
-                        right: 0;
-                        left: auto;
-                        border-radius: 0 5px 0 5px;
-                    }
-                }
-
-                .img-item {
-                    flex: 1;
-                    height: 120px;
-                    overflow: hidden;
-                    border: 0.5px solid rgba(0, 0, 0, 0.1);
-                    border-radius: 5px;
-
-                    img {
-                        width: 100%;
-                        height: 100%;
-                        border-radius: 5px;
-                        object-fit: cover;
-                    }
-                }
             }
         }
+    }
 
-        .unqualified-reason {
-            color: #f74e4e;
-            font-size: 12px;
-            padding: 4px 10px;
-            background: rgba(247, 78, 78, 0.08);
-            border-radius: 4px;
-            line-height: 18px;
-        }
-
-        .empty-tip {
-            text-align: center;
-            color: #86909c;
-            font-size: 14px;
-            padding: 20px 0;
-        }
+    .empty-tip {
+        padding: 40px 0;
+        text-align: center;
+        color: #86909c;
+        font-size: 14px;
     }
 }
 </style>