Explorar o código

feat:修改农事执行页面逻辑和样式

wangsisi hai 4 días
pai
achega
f260764f86

+ 10 - 4
src/api/modules/farm.js

@@ -13,10 +13,6 @@ module.exports = {
         url: config.base_dev_url + "v2/farm/userFarmSelectOption",
         type: "get",
     },
-    listByUserId: {
-        url: config.base_dev_url + "farm_subject/listByUserId",
-        type: "get",
-    },
     fruitsTypeItemList: {
         url: config.base_dev_url + "mini_fruits_type_item/list/{parentId}",
         type: "get",
@@ -59,4 +55,14 @@ module.exports = {
         url: config.base_new_url + "find_farm_information",
         type: "get",
     },
+    //分区状态显示
+    getRegionRecord: {
+        url: config.base_new_url + "region_record",
+        type: "get",
+    },
+    //农事详情查询
+    getRegionWorkDetail: {
+        url: config.base_new_url + "find_region_work",
+        type: "get",
+    },
 }

+ 0 - 55
src/api/modules/monitor.js

@@ -20,61 +20,11 @@ module.exports = {
         url: config.base_dev_url + "container_farm_work_arrange/save",
         type: "post",
     },
-    //批量保存多个物候期的农场自定义时间
-    batchSaveFarmPhenologyTime: {
-        url: config.base_dev_url + "container_space_time/batchSaveFarmPhenologyTime",
-        type: "post",
-    },
-    //农资用户复制自己的方案
-    copyScheme: {
-        url: config.base_dev_url + "container_farm_work_scheme/copyMyScheme",
-        type: "get",
-    },
-    //删除方案(逻辑删除)
-    deleteScheme: {
-        url: config.base_dev_url + "container_farm_work_scheme/deleteScheme",
-        type: "get",
-    },
-    //修改方案名称
-    renameScheme: {
-        url: config.base_dev_url + "container_farm_work_scheme/renameScheme?schemeId={schemeId}&name={name}",
-        type: "post",
-    },
     // 批量验证多个农事药肥报价信息是否完整
     batchValidatePesticideFertilizerQuotes: {
         url: config.base_dev_url + "z_farm_work_lib/batchValidatePesticideFertilizerQuotes",
         type: "post",
     },
-    //将方案设置为可用
-    enableScheme: {
-        url: config.base_dev_url + "container_farm_work_scheme/enableScheme",
-        type: "get",
-    },
-    // 获取作物档案列表
-    getArchivesList: {
-        url: config.base_dev_url + "container_crop_archive/cropArchiveV2",
-        type: "get",
-    },
-    // 获取农事记录
-    getFarmWorkPlan: {
-        url: config.base_dev_url + "container_farm_work_group/farmWorkPlan",
-        type: "get",
-    },
-    //根据农场主体ID查询其下所有农场的分区列表
-    listRegionsBySubjectId: {
-        url: config.base_dev_url + "farm_subject/listRegionsBySubjectId",
-        type: "get",
-    },
-    //农情照片分页查询
-    getFarmImagePage: {
-        url: config.base_dev_url + "container_crop_archive/farmImagePage",
-        type: "get",
-    },
-    //根据农场主体ID查询其下所有农场的列表
-    listFarmsBySubjectId: {
-        url: config.base_dev_url + "farm_subject/listFarmsBySubjectId",
-        type: "get",
-    },
     //获取物候列表
     getPhenologyList: {
         url: config.base_new_url + "farm_works",
@@ -85,11 +35,6 @@ module.exports = {
         url: config.base_new_url + "farm_risk_and_tracking",
         type: "get",
     },
-    //农场农情档案查询
-    getFarmRecord: {
-        url: config.base_new_url + "find_farm_record",
-        type: "get",
-    },
     //获取农事详情
     getWorkDetail: {
         url: config.base_new_url + "find_farm_work",

+ 46 - 4
src/components/popup/tipPopup.vue

@@ -25,6 +25,14 @@
                 <div class="text2">{{ text2 }}</div>
             </div>
         </template>
+        <template v-else-if="type === 'executeSuccess'">
+            <img class="tip-icon success-icon" src="@/assets/img/home/right.png" alt="" />
+            <div class="tip-text execute-success-text" :class="{ 'no-bottom': hideBtn && !noticeText }">
+                <div class="execute-success-title">{{ text }}</div>
+                <div class="execute-success-subtitle">{{ text2 }}</div>
+            </div>
+            <div v-if="noticeText" class="tip-notice">{{ noticeText }}</div>
+        </template>
         <template v-else>
             <img class="tip-icon success-icon" src="@/assets/img/home/create-farm-icon.png" alt="" />
             <div class="tip-text success-text">{{ text }} <span class="highlight-text">{{ highlightText }}</span> {{ text2 }}</div>
@@ -80,6 +88,11 @@ const props = defineProps({
         type: String,
         default: "",
     },
+    // 底部提示说明(executeSuccess 类型)
+    noticeText: {
+        type: String,
+        default: "",
+    },
     // 是否可以关闭
     hasClose: {
         type: Boolean,
@@ -141,8 +154,8 @@ const handleClosed = () => {
 <style scoped lang="scss">
 .tip-popup {
     z-index: 10000 !important;
-    width: 80%;
-    padding: 28px 28px 20px;
+    width: 100%;
+    padding: 28px 20px 20px;
     display: flex;
     flex-direction: column;
     align-items: center;
@@ -167,7 +180,7 @@ const handleClosed = () => {
             margin-bottom: 6px;
         }
         &.success-text {
-            font-size: 20px;
+            font-size: 24px;
             font-weight: 400;
         }
         .highlight-text {
@@ -177,9 +190,38 @@ const handleClosed = () => {
             font-family: 'PangMenZhengDao';
         }
         .text2 {
-            padding-top: 4px;
+            padding-top: 10px;
+            font-size: 20px;
+            color: rgba(0, 0, 0, 0.5);
         }
+
+        &.execute-success-text {
+            width: 100%;
+            margin-bottom: 16px;
+
+            .execute-success-title,
+            .execute-success-subtitle {
+                font-size: 24px;
+                font-weight: 400;
+            }
+
+            .execute-success-subtitle {
+                padding-top: 4px;
+            }
+        }
+    }
+
+    .tip-notice {
+        width: 100%;
+        box-sizing: border-box;
+        padding: 2px 10px;
+        border-radius: 5px;
+        background: rgba(255, 106, 106, 0.1);
+        color: #FF4A4A;
+        font-size: 15px;
+        text-align: left;
     }
+
     .tip-btn {
         width: 100%;
         box-sizing: border-box;

+ 122 - 0
src/i18n/messages.js

@@ -224,6 +224,7 @@ export default {
             executeTime: "执行时间",
             executeArea: "执行区域",
             executeMachine: "执行农机",
+            executePhotos: "执行照片",
             unqualifiedReason: "未达标原因:",
             defaultUnqualifiedReason: "未在合适时间执行,药液未充分喷撒吸收",
             before: "前",
@@ -233,12 +234,72 @@ export default {
             all: "全部",
             redispatch: "重新派发",
             remindAccept: "提醒接受",
+            modifyInfo: "修改信息",
+            acceptTask: "我要接受",
+            startExecute: "开始执行",
+            markComplete: "我已完成",
             remindExecute: "提醒执行",
             remindComplete: "提醒完成",
             redispatchAgain: "重新下发",
             remindSuccess: "提醒成功!",
             redispatchMsg: "重新派发:{name}",
         },
+        agriRecordDetail: {
+            title: "农事详情",
+            deadlinePrefix: "截止到",
+            executionArea: "执行区域",
+            navigateHere: "导航到这里",
+            workPurpose: "农事目的",
+            drugPrescription: "药物处方",
+            executionParams: "执行参数",
+            precautions: "注意事项",
+            cancelEdit: "取消修改",
+            modifyInfo: "修改信息",
+            saveInfo: "保存信息",
+            acceptTask: "我要接受",
+            saveSuccess: "保存成功",
+            acceptSuccessTitle: "您已接受成功",
+            acceptSuccessDesc: "请在设置的执行时间内执行农事",
+            executeSuccessTitle: "农事任务已生效",
+            executeSuccessDesc: "请立即按要求实地执行",
+            executeSuccessNotice: "注:执行中需拍摄照片,任务完成后需提交照片审核!",
+            completePopup: {
+                selectExecuteTime: "请选择实际执行时间",
+                evidenceTitle: "农事凭证",
+                evidenceHint: "请上传执行照片",
+                confirm: "确认信息",
+                selectTimeRequired: "请选择实际执行时间",
+                uploadRequired: "请上传执行照片",
+                uploadFailed: "图片上传失败,请稍后再试!",
+            },
+            selectDate: "请选择日期",
+            pleaseInput: "请输入",
+            fields: {
+                executor: "负责人",
+                workReasonDetail: "农事详情",
+                cropConditionAnalysis: "农情研判",
+                workTime: "执行时间",
+                executionMethod: "执行参数",
+            },
+            farmWorkType: {
+                1: "标准农事",
+                2: "机动农事",
+                3: "气象预警农事",
+                4: "气象恢复农事",
+                5: "异常农事",
+                6: "标准施肥类",
+                7: "标准防治类",
+                8: "标准调节类",
+                91: "标准感知",
+                92: "营养评估",
+                93: "物候分区",
+                94: "产量估计",
+                95: "复核感知",
+                96: "气象胁迫",
+                97: "异常感知",
+                98: "虫情感知",
+            },
+        },
     },
     en: {
         tabbar: {
@@ -463,6 +524,7 @@ export default {
             executeTime: "Execution time",
             executeArea: "Execution area",
             executeMachine: "Machinery",
+            executePhotos: "Execution photos",
             unqualifiedReason: "Reason: ",
             defaultUnqualifiedReason: "Not executed at the right time; spray was not fully absorbed",
             before: "Before",
@@ -472,11 +534,71 @@ export default {
             all: "All",
             redispatch: "Redispatch",
             remindAccept: "Remind to accept",
+            modifyInfo: "Edit info",
+            acceptTask: "Accept",
+            startExecute: "Start execution",
+            markComplete: "I have completed",
             remindExecute: "Remind to execute",
             remindComplete: "Remind to complete",
             redispatchAgain: "Redispatch",
             remindSuccess: "Reminder sent!",
             redispatchMsg: "Redispatched: {name}",
         },
+        agriRecordDetail: {
+            title: "Work Details",
+            deadlinePrefix: "Due by",
+            executionArea: "Execution area",
+            navigateHere: "Navigate here",
+            workPurpose: "Purpose",
+            drugPrescription: "Prescription",
+            executionParams: "Execution parameters",
+            precautions: "Precautions",
+            cancelEdit: "Cancel",
+            modifyInfo: "Edit info",
+            saveInfo: "Save",
+            acceptTask: "Accept",
+            saveSuccess: "Saved successfully",
+            acceptSuccessTitle: "Accepted successfully",
+            acceptSuccessDesc: "Please complete the farm work within the scheduled time",
+            executeSuccessTitle: "Farm task is now active",
+            executeSuccessDesc: "Please execute on site as required",
+            executeSuccessNotice: "Note: Take photos during execution and submit them for review after completion.",
+            completePopup: {
+                selectExecuteTime: "Select actual execution time",
+                evidenceTitle: "Farm work evidence",
+                evidenceHint: "Please upload execution photos",
+                confirm: "Confirm",
+                selectTimeRequired: "Please select the actual execution time",
+                uploadRequired: "Please upload execution photos",
+                uploadFailed: "Image upload failed. Please try again later.",
+            },
+            selectDate: "Select date",
+            pleaseInput: "Please enter",
+            fields: {
+                executor: "Responsible",
+                workReasonDetail: "Work details",
+                cropConditionAnalysis: "Crop assessment",
+                workTime: "Execution time",
+                executionMethod: "Execution parameters",
+            },
+            farmWorkType: {
+                1: "Standard farm work",
+                2: "Flexible farm work",
+                3: "Weather alert work",
+                4: "Weather recovery work",
+                5: "Abnormal farm work",
+                6: "Standard fertilization",
+                7: "Standard prevention",
+                8: "Standard regulation",
+                91: "Standard sensing",
+                92: "Nutrition assessment",
+                93: "Phenology zoning",
+                94: "Yield estimation",
+                95: "Review sensing",
+                96: "Weather stress",
+                97: "Abnormal sensing",
+                98: "Pest sensing",
+            },
+        },
     },
 };

+ 226 - 0
src/views/old_mini/agri_record_detail/components/completePopup.vue

@@ -0,0 +1,226 @@
+<template>
+    <popup v-model:show="showValue" class="complete-popup" closeable @closed="handleClosed">
+        <div class="popup-content">
+            <div class="time-wrap">
+                <div class="name pb-10">{{ t('agriRecordDetail.completePopup.selectExecuteTime') }}</div>
+                <div class="time-input">
+                    <el-date-picker
+                        v-model="executeTime"
+                        format="YYYY.MM.DD"
+                        value-format="YYYY-MM-DD"
+                        :disabled-date="disabledDate"
+                        size="large"
+                        style="width: 100%"
+                        type="date"
+                        :placeholder="t('agriRecordDetail.selectDate')"
+                        :editable="false"
+                    />
+                </div>
+            </div>
+            <div class="upload-wrap" :class="{ 'upload-cont': fileList.length }">
+                <div class="name">{{ t('agriRecordDetail.completePopup.evidenceTitle') }}</div>
+                <div class="sub-name">{{ t('agriRecordDetail.completePopup.evidenceHint') }}</div>
+                <uploader
+                    class="uploader"
+                    v-model="fileList"
+                    multiple
+                    :max-count="5"
+                    :after-read="afterRead"
+                >
+                    <img class="plus" src="@/assets/img/home/plus.png" alt="" />
+                </uploader>
+            </div>
+            <div class="button-wrap">
+                <div class="button primary" @click="handleConfirm">{{ t('agriRecordDetail.completePopup.confirm') }}</div>
+            </div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import { Popup, Uploader } from 'vant';
+import { computed, ref, watch } from 'vue';
+import { ElMessage } from 'element-plus';
+import { useStore } from 'vuex';
+import { useI18n } from '@/i18n';
+import { getFileExt } from '@/utils/util';
+import UploadFile from '@/utils/upliadFile';
+
+const { t } = useI18n();
+const store = useStore();
+const miniUserId = store.state.home.miniUserId;
+
+const props = defineProps({
+    show: {
+        type: Boolean,
+        default: false,
+    },
+});
+
+const emit = defineEmits(['update:show', 'confirm']);
+
+const showValue = computed({
+    get: () => props.show,
+    set: (value) => emit('update:show', value),
+});
+
+const fileList = ref([]);
+const fileArr = ref([]);
+const executeTime = ref('');
+const uploadFileObj = new UploadFile();
+
+const resetForm = () => {
+    fileList.value = [];
+    fileArr.value = [];
+    executeTime.value = '';
+};
+
+watch(
+    () => props.show,
+    (visible) => {
+        if (visible) {
+            resetForm();
+        }
+    }
+);
+
+const afterRead = (file) => {
+    const files = Array.isArray(file) ? file : [file];
+
+    files.forEach((item) => {
+        const fileVal = item.file;
+        if (!fileVal) return;
+
+        item.status = 'uploading';
+        item.message = '上传中...';
+        const ext = getFileExt(fileVal.name);
+        const key = `birdseye-look-mini/${miniUserId}/${Date.now()}.${ext}`;
+        uploadFileObj
+            .put(key, fileVal)
+            .then((resFilename) => {
+                item.status = 'done';
+                item.message = '';
+                fileArr.value.push(resFilename);
+            })
+            .catch(() => {
+                item.status = 'failed';
+                item.message = '上传失败';
+                ElMessage.error(t('agriRecordDetail.completePopup.uploadFailed'));
+            });
+    });
+};
+
+const disabledDate = (time) => time.getTime() > Date.now();
+
+const handleConfirm = () => {
+    if (!executeTime.value) {
+        ElMessage.warning(t('agriRecordDetail.completePopup.selectTimeRequired'));
+        return;
+    }
+    if (!fileArr.value.length) {
+        ElMessage.warning(t('agriRecordDetail.completePopup.uploadRequired'));
+        return;
+    }
+    emit('confirm', {
+        executeDate: executeTime.value,
+        executeEvidence: [...fileArr.value],
+    });
+    showValue.value = false;
+};
+
+const handleClosed = () => {
+    resetForm();
+};
+</script>
+
+<style lang="scss" scoped>
+.complete-popup {
+    width: 90%;
+    border-radius: 8px;
+
+    ::v-deep {
+        .van-popup__close-icon--top-right {
+            top: 8px;
+            right: 10px;
+            color: #333333;
+        }
+    }
+
+    .popup-content {
+        padding: 24px 16px 20px;
+        background: linear-gradient(360deg, #ffffff 74.2%, #d1ebff 100%);
+        border-radius: 8px;
+    }
+}
+
+.pb-10 {
+    padding-bottom: 12px;
+}
+
+.name {
+    color: #000000;
+    font-size: 16px;
+    font-weight: 500;
+}
+
+.sub-name {
+    font-size: 12px;
+    color: rgba(0, 0, 0, 0.4);
+    padding-bottom: 12px;
+}
+
+.upload-wrap {
+    &.upload-cont {
+        ::v-deep {
+            .van-uploader__wrapper {
+                flex-wrap: nowrap;
+            }
+        }
+    }
+
+    .plus {
+        margin-right: 12px;
+        width: 80px;
+        height: 80px;
+    }
+
+    ::v-deep {
+        .van-image__img {
+            border-radius: 8px;
+        }
+
+        .van-uploader,
+        .van-uploader__wrapper,
+        .van-uploader__input-wrapper {
+            width: 100%;
+        }
+    }
+}
+
+.time-wrap {
+    margin-bottom: 12px;
+
+    .time-input {
+        width: 100%;
+    }
+}
+
+.button-wrap {
+    display: flex;
+    justify-content: center;
+    margin: 24px 4px 0;
+
+    .button {
+        border-radius: 20px;
+        padding: 7px 0;
+        text-align: center;
+        font-size: 16px;
+
+        &.primary {
+            flex: 1;
+            background: #2199f8;
+            color: #fff;
+        }
+    }
+}
+</style>

+ 310 - 114
src/views/old_mini/agri_record_detail/index.vue

@@ -1,50 +1,49 @@
 <template>
     <div class="agri-record-detail">
-        <custom-header name="农事详情"></custom-header>
+        <custom-header :name="t('agriRecordDetail.title')"></custom-header>
         <div class="content">
-            <div class="status-banner" :class="`status-banner--${statusInfo.theme}`">
-                <div class="status-banner__title">{{ statusInfo.title }}</div>
-                <div class="status-banner__deadline">截止到 {{ statusInfo.deadline }}</div>
+            <div class="status-banner" :class="`status-banner--${statusBannerTheme}`">
+                <div class="status-banner__title">{{ statusBannerTitle }}</div>
+                <div class="status-banner__deadline">{{ t('agriRecordDetail.deadlinePrefix') }} {{ workDetail.work_time }}</div>
             </div>
             <div class="box-wrap execution-area-card">
                 <div class="execution-area-card__header">
-                    <span class="title">执行区域</span>
-                    <span class="code">{{ executionArea.zoneCode }}</span>
+                    <span class="title">{{ t('agriRecordDetail.executionArea') }}</span>
+                    <span class="code">ws0gefwg9tdn</span>
                 </div>
                 <div class="map-container" ref="mapContainer"></div>
                 <div class="execution-area-card__footer">
                     <div class="address">
-                        <div class="address-title">{{ executionArea.addressTitle }}</div>
-                        <div class="address-detail">{{ executionArea.addressDetail }}</div>
+                        <div class="address-title">{{ farmInfo.farm_name }}</div>
+                        <div class="address-detail">{{ farmInfo.rawAddress }}</div>
                     </div>
-                    <div class="nav-btn" @click="handleNavigate">导航到这里</div>
+                    <div class="nav-btn" @click="handleNavigate">{{ t('agriRecordDetail.navigateHere') }}</div>
                 </div>
             </div>
             <div class="box-wrap work-summary-card">
-                <div class="header">
-                    <div class="icon">
-                        <el-icon color="#fff" :size="14">
-                            <School />
-                        </el-icon>
-                    </div>
-                    <span class="title">{{ workSummary.workName }}</span>
-                    <div class="tags">
-                        <span
-                            v-for="(tag, index) in workSummary.tags"
-                            :key="index"
-                            class="work-tag"
-                            :class="`work-tag--${tag.theme}`"
-                        >{{ tag.label }}</span>
+                <div class="header" :class="{ 'header--en': locale === 'en' }">
+                    <img class="icon" src="@/assets/img/home/task.png" alt="">
+                    <div class="header-main">
+                        <span class="title">{{ workDetail.work_name }}</span>
+                        <div class="tags" v-if="workTagLabel">
+                            <span
+                                class="work-tag"
+                                :class="`work-tag--${getWorkTagTheme(workDetail.farm_work_type)}`"
+                            >{{ workTagLabel }}</span>
+                        </div>
                     </div>
                 </div>
                 <div class="body">
                     <div
-                        v-for="(row, index) in workSummary.detailRows"
+                        v-for="(row, index) in detailRows"
                         :key="index"
                         class="detail-row"
-                        :class="{ 'detail-row--field': isFieldRow(row) }"
+                        :class="{
+                            'detail-row--field': isFieldRow(row),
+                            'detail-row--textarea': getEffectiveRowType(row) === 'textarea',
+                        }"
                     >
-                        <span class="detail-row__label">{{ row.label }}</span>
+                        <span class="detail-row__label">{{ t(row.labelKey) }}</span>
                         <span v-if="isTextRow(row)" class="detail-row__value">{{ formatRowValue(row) }}</span>
                         <el-date-picker
                             v-else-if="getEffectiveRowType(row) === 'date'"
@@ -53,7 +52,7 @@
                             type="date"
                             format="YYYY.MM.DD"
                             value-format="YYYY-MM-DD"
-                            placeholder="请选择日期"
+                            :placeholder="t('agriRecordDetail.selectDate')"
                             :editable="false"
                             style="width: 100%"
                         />
@@ -61,145 +60,271 @@
                             v-else-if="getEffectiveRowType(row) === 'input'"
                             v-model="row.value"
                             class="detail-row__control"
-                            placeholder="请输入"
+                            :placeholder="t('agriRecordDetail.pleaseInput')"
+                        />
+                        <el-input
+                            v-else-if="getEffectiveRowType(row) === 'textarea'"
+                            v-model="row.value"
+                            class="detail-row__control"
+                            type="textarea"
+                            :rows="3"
+                            :placeholder="t('agriRecordDetail.pleaseInput')"
                         />
                     </div>
                 </div>
             </div>
             <div class="box-wrap">
                 <div class="info-item">
-                    <div class="info-title"><span class="title-block"></span>农事目的</div>
-                    <div class="info-value">农事目的农事目的农事目的农事目的农事目的农事目</div>
+                    <div class="info-title"><span class="title-block"></span>{{ t('agriRecordDetail.workPurpose') }}</div>
+                    <div class="info-value">{{ workDetail?.work_purpose }}</div>
                 </div>
                 <div class="info-item">
-                    <div class="info-title"><span class="title-block"></span>药物处方</div>
-                    <div class="info-value">农事目的农事目的农事目的农事目的农事目的农事目</div>
+                    <div class="info-title"><span class="title-block"></span>{{ t('agriRecordDetail.drugPrescription') }}</div>
+                    <div class="info-value">{{ workDetail?.drug_prescription }}</div>
                 </div>
                 <div class="info-item">
-                    <div class="info-title"><span class="title-block"></span>执行参数</div>
-                    <div class="info-value">农事目的农事目的农事目的农事目的农事目的农事目</div>
+                    <div class="info-title"><span class="title-block"></span>{{ t('agriRecordDetail.executionParams') }}</div>
+                    <div class="info-value">{{ workDetail?.execution_method || '--' }}</div>
                 </div>
                 <div class="info-item">
-                    <div class="info-title"><span class="title-block"></span>注意事项</div>
-                    <div class="info-value">农事目的农事目的农事目的农事目的农事目的农事目</div>
+                    <div class="info-title"><span class="title-block"></span>{{ t('agriRecordDetail.precautions') }}</div>
+                    <div class="info-value">{{ workDetail?.precautions }}</div>
                 </div>
             </div>
         </div>
-        <div class="custom-bottom-fixed-btns">
-            <div class="bottom-btn secondary-btn" @click="handleToggleEdit">
-                {{ isEditing ? '取消修改' : '修改信息' }}
-            </div>
-            <div class="bottom-btn primary-btn" :class="{ 'orange-btn': !isEditing }" @click="handleAccept">{{ isEditing ? '保存信息' : '我要接受' }}</div>
+        <div
+            class="custom-bottom-fixed-btns"
+            :class="{ 'custom-bottom-fixed-btns--single': isExecutingStatus && !isEditing }"
+            v-if="!isAcceptOvertime && !isCompletedStatus"
+        >
+            <template v-if="isExecutingStatus && !isEditing">
+                <div class="bottom-btn primary-btn green-btn" @click="handleAccept">{{ t('workExecute.markComplete') }}</div>
+            </template>
+            <template v-else>
+                <div class="bottom-btn secondary-btn" @click="handleToggleEdit">
+                    {{ isEditing ? t('agriRecordDetail.cancelEdit') : t('agriRecordDetail.modifyInfo') }}
+                </div>
+                <div class="bottom-btn primary-btn" :class="{ 'orange-btn': !isEditing && !isAcceptedStatus }" @click="handleAccept">{{ primaryActionText }}</div>
+            </template>
         </div>
+        <tip-popup
+            v-model:show="showAcceptSuccessPopup"
+            type="success"
+            :text="t('agriRecordDetail.acceptSuccessTitle')"
+            :text2="t('agriRecordDetail.acceptSuccessDesc')"
+            hideBtn
+        />
+        <tip-popup
+            v-model:show="showExecuteSuccessPopup"
+            type="executeSuccess"
+            :text="t('agriRecordDetail.executeSuccessTitle')"
+            :text2="t('agriRecordDetail.executeSuccessDesc')"
+            :notice-text="t('agriRecordDetail.executeSuccessNotice')"
+            hideBtn
+        />
+        <complete-popup v-model:show="showCompletePopup" @confirm="handleCompleteConfirm" />
     </div>
 </template>
 
 <script setup>
-import { ref, onActivated, nextTick } from 'vue';
+import { ref, computed, onActivated, onDeactivated, onBeforeUnmount, nextTick } from 'vue';
 import { School } from '@element-plus/icons-vue';
 import * as util from '@/common/ol_common.js';
 import customHeader from '@/components/customHeader.vue';
 import { useI18n } from '@/i18n';
-import IndexMap from './map/index.js';
+import AreaMap from '../work_detail/components/areaMap.js';
+import tipPopup from '@/components/popup/tipPopup.vue';
+import completePopup from './components/completePopup.vue';
+import { ElMessage } from 'element-plus';
+import { useRoute } from 'vue-router';
 
-const { t } = useI18n();
+const { t, locale } = useI18n();
 
+const route = useRoute();
 const DEFAULT_FARM_POINT = 'POINT(113.6142086995688 23.585836479509055)';
 const mapContainer = ref(null);
-const recordDetailMap = new IndexMap();
-
-const syncExecutionAreaMap = async () => {
-    const location = DEFAULT_FARM_POINT;
-    await nextTick();
-    if (!mapContainer.value) return;
-    if (recordDetailMap.kmap) {
-        const coordinate = util.wktCastGeom(location).getFirstCoordinate();
-        recordDetailMap.setMapPosition(coordinate);
-        recordDetailMap.kmap?.map?.updateSize?.();
-        return;
+const recordDetailMap = new AreaMap();
+
+const workDetail = ref({});
+
+const farmZones = computed(() => {
+    const zones = workDetail.value?.zones;
+    if (!Array.isArray(zones)) return [];
+    return zones.filter(
+        (z) => z && typeof z.zone_geometry === 'string' && z.zone_geometry.trim()
+    );
+});
+
+function wktCenterPointFromZones(zones) {
+    const list = Array.isArray(zones) ? zones : [];
+    for (const zone of list) {
+        const wkt = zone?.zone_geometry;
+        if (!wkt || typeof wkt !== 'string') continue;
+        try {
+            const c = util.wktCastGeom(wkt.trim()).getFirstCoordinate();
+            return `POINT(${c[0]} ${c[1]})`;
+        } catch {
+            /* try next zone */
+        }
     }
-    recordDetailMap.initMap(location, mapContainer.value);
-    recordDetailMap.kmap?.map?.updateSize?.();
-};
+    return DEFAULT_FARM_POINT;
+}
+
+function destroyExecutionAreaMap() {
+    recordDetailMap.destroyMap();
+}
 
+function initExecutionAreaMap() {
+    nextTick(() => {
+        if (!mapContainer.value) return;
+        const zones = farmZones.value;
+        recordDetailMap.destroyMap();
+        recordDetailMap.initMap(wktCenterPointFromZones(zones), mapContainer.value);
+        recordDetailMap.initZones(zones);
+    });
+}
+
+const farmInfo = ref({});
 onActivated(async () => {
+    farmInfo.value = JSON.parse(localStorage.getItem('selectedFarmData') || '{}');
     isEditing.value = false;
     detailRowsSnapshot = null;
-    await syncExecutionAreaMap();
+    await getRegionWorkDetail();
 });
 
+onDeactivated(() => {
+    destroyExecutionAreaMap();
+});
+
+onBeforeUnmount(() => {
+    destroyExecutionAreaMap();
+});
+
+const getRegionWorkDetail = async () => {
+    const params = {
+        id: route.query.id,
+        work_operations_type: route.query.status
+    };
+    const { code, data } = await VE_API.farm.getRegionWorkDetail(params);
+    if (code === 200 && data && data.length > 0) {
+        workDetail.value = data[0];
+        detailRows.value = buildDetailRows(workDetail.value);
+        initExecutionAreaMap();
+    }
+};
+
 const EDITABLE_FIELD_MAP = {
-    执行时间: 'date',
-    执行农机: 'input',
+    work_time: 'date',
+    execution_method: 'textarea',
 };
 
 const isEditing = ref(false);
+const showAcceptSuccessPopup = ref(false);
+const showExecuteSuccessPopup = ref(false);
+const showCompletePopup = ref(false);
 let detailRowsSnapshot = null;
 
 const getEffectiveRowType = (row) => {
     if (!isEditing.value) return undefined;
-    return EDITABLE_FIELD_MAP[row.label] || undefined;
+    return EDITABLE_FIELD_MAP[row.field] || undefined;
 };
 
 const isTextRow = (row) => !getEffectiveRowType(row);
 const isFieldRow = (row) => !!getEffectiveRowType(row);
 
-const formatRowValue = (row) => {
-    if (!row?.value) return row?.value ?? '';
-    if (row.type === 'date' || row.label === '执行时间') {
-        return String(row.value).replace(/-/g, '.');
-    }
-    return row.value;
-};
+const formatRowValue = (row) => row?.value ?? '';
 
 const normalizeDateValue = (value) => {
     if (!value) return value;
     return String(value).replace(/\./g, '-');
 };
 
-const normalizeDetailRows = (rows = []) =>
-    rows.map((row) => {
-        if (row.type === 'date' && row.value) {
-            return { ...row, value: normalizeDateValue(row.value) };
-        }
-        return row;
-    });
+const DETAIL_ROW_SCHEMA = [
+    { labelKey: 'agriRecordDetail.fields.executor', field: 'executor' },
+    { labelKey: 'agriRecordDetail.fields.workReasonDetail', field: 'work_reason_detail' },
+    { labelKey: 'agriRecordDetail.fields.cropConditionAnalysis', field: 'crop_condition_analysis' },
+    { labelKey: 'agriRecordDetail.fields.workTime', field: 'work_time', type: 'date' },
+    { labelKey: 'agriRecordDetail.fields.executionMethod', field: 'execution_method', type: 'textarea' },
+];
+
+const buildDetailRows = (data = {}) =>
+    DETAIL_ROW_SCHEMA.map(({ labelKey, field, type }) => ({
+        labelKey,
+        field,
+        type,
+        value: data[field] ?? '',
+    }));
+
+const detailRows = ref([]);
+
+const STATUS_BANNER_MAP = {
+    待接受: 'workExecute.pending',
+    已接受: 'workExecute.accepted',
+    执行中: 'workExecute.executing',
+    已完成: 'workExecute.completed',
+    接受超时: 'workExecute.acceptTimeout',
+};
+
+const statusBannerTitle = computed(() => {
+    const statusStr = route.query.statusStr;
+    const key = STATUS_BANNER_MAP[statusStr];
+    return key ? t(key) : statusStr || '';
+});
 
-const workSummary = ref({
-    workName: '农事名称',
-    tags: [
-        { label: '感知类', theme: 'orange' },
-        { label: '标准类', theme: 'blue' },
-        { label: '复核类', theme: 'green' },
-    ],
-    detailRows: normalizeDetailRows([
-        { label: '负责人', value: '张扬洋' },
-        { label: '农事详情', value: '解释解释解释解释' },
-        { label: '农情研判', value: '原因原因原因原因原因原因原因原因' },
-        { label: '执行时间', value: '2025.05.06' },
-        { label: '执行农机', value: 'D-260602-1455-01-01' },
-    ]),
+const statusBannerTheme = computed(() => {
+    const statusStr = route.query.statusStr;
+    if (statusStr === '接受超时') return 'overtime';
+    if (statusStr === '执行中') return 'executing';
+    if (statusStr === '已完成') return 'completed';
+    if (statusStr === '已接受') return 'accepted';
+    const status = Number(route.query.status);
+    if (status === 0) return 'pending';
+    if (status === 2) return 'accepted';
+    if (status === 3) return 'completed';
+    if (status === 3) return 'completed';
+    return 'pending';
 });
 
-const statusInfo = ref({
-    title: '待接收',
-    deadline: '2025.05.06',
-    theme: 'orange',
+const workTagLabel = computed(() => {
+    const type = workDetail.value?.farm_work_type;
+    if (type == null || type === '') return '';
+    return t(`agriRecordDetail.farmWorkType.${type}`);
 });
 
-const executionArea = ref({
-    zoneCode: 'ws0gefwg9tdn',
-    addressTitle: '具体地址具体地址具体地',
-    addressDetail: '广东省广州市从化区富江道',
+const isAcceptOvertime = computed(() =>
+    Number(workDetail.value?.is_aovertime) === 1 || route.query.statusStr === '接受超时'
+);
+
+const isAcceptedStatus = computed(() =>
+    route.query.statusStr === '已接受' || Number(route.query.status) === 2
+);
+
+const isExecutingStatus = computed(() => route.query.statusStr === '执行中');
+
+const isCompletedStatus = computed(() =>
+    route.query.statusStr === '已完成' || Number(route.query.status) === 3
+);
+
+const primaryActionText = computed(() => {
+    if (isEditing.value) return t('agriRecordDetail.saveInfo');
+    if (isAcceptedStatus.value) return t('workExecute.startExecute');
+    return t('agriRecordDetail.acceptTask');
 });
 
+const getWorkTagTheme = (type) => {
+    const n = Number(type);
+    if (n === 95) return 'green';
+    if (n >= 91 && n <= 98) return 'orange';
+    return 'blue';
+};
+
 const handleNavigate = () => {};
 
 const handleToggleEdit = () => {
     if (!isEditing.value) {
-        detailRowsSnapshot = JSON.parse(JSON.stringify(workSummary.value.detailRows));
-        workSummary.value.detailRows = workSummary.value.detailRows.map((row) => {
-            if (row.label === '执行时间' && row.value) {
+        detailRowsSnapshot = JSON.parse(JSON.stringify(detailRows.value));
+        detailRows.value = detailRows.value.map((row) => {
+            if (row.field === 'work_time' && row.value) {
                 return { ...row, value: normalizeDateValue(row.value) };
             }
             return row;
@@ -208,16 +333,36 @@ const handleToggleEdit = () => {
         return;
     }
     if (detailRowsSnapshot) {
-        workSummary.value.detailRows = detailRowsSnapshot;
+        detailRows.value = detailRowsSnapshot;
         detailRowsSnapshot = null;
     }
     isEditing.value = false;
 };
 
 const handleAccept = () => {
+    if (isEditing.value) {
+        // TODO: 提交保存农事信息接口
+        isEditing.value = false;
+        detailRowsSnapshot = null;
+        ElMessage.success(t('agriRecordDetail.saveSuccess'));
+        return;
+    }
+    if (isAcceptedStatus.value) {
+        // TODO: 开始执行接口
+        showExecuteSuccessPopup.value = true;
+        return;
+    }
+    if (isExecutingStatus.value) {
+        showCompletePopup.value = true;
+        return;
+    }
     // TODO: 提交接受农事接口
-    isEditing.value = false;
-    detailRowsSnapshot = null;
+    showAcceptSuccessPopup.value = true;
+};
+
+const handleCompleteConfirm = () => {
+    // TODO: 提交完成农事接口,payload: { executeDate, executeEvidence }
+    ElMessage.success(t('agriRecordDetail.saveSuccess'));
 };
 </script>
 
@@ -243,15 +388,24 @@ const handleAccept = () => {
             padding: 20px 12px 72px;
             color: #fff;
 
-            &--orange {
+            &--0,
+            &--pending {
                 background: #FF9838;
             }
 
-            &--blue {
+            &--2,
+            &--accepted,
+            &--executing {
                 background: #2199F8;
             }
 
-            &--red {
+            &--3,
+            &--completed {
+                background: #4ABF32;
+            }
+
+            &--1,
+            &--overtime {
                 background: #FF6A6A;
             }
 
@@ -334,14 +488,31 @@ const handleAccept = () => {
                     padding-bottom: 8px;
                     margin-bottom: 8px;
                     .icon {
-                        width: 22px;
-                        height: 22px;
-                        border-radius: 4px;
-                        background: #2199F8;
+                        width: 16px;
+                        height: 16px;
+                        flex-shrink: 0;
+                    }
+
+                    .header-main {
+                        flex: 1;
+                        min-width: 0;
                         display: flex;
                         align-items: center;
-                        justify-content: center;
-                        flex-shrink: 0;
+                        gap: 8px;
+                    }
+
+                    &--en {
+                        align-items: flex-start;
+
+                        .icon {
+                            margin-top: 2px;
+                        }
+
+                        .header-main {
+                            flex-direction: column;
+                            align-items: flex-start;
+                            gap: 4px;
+                        }
                     }
     
                     .title {
@@ -386,7 +557,8 @@ const handleAccept = () => {
                         gap: 8px;
     
                         &__label {
-                            width: 56px;
+                            width: 72px;
+                            min-width: 72px;
                             flex-shrink: 0;
                             color: rgba(111, 114, 116, 0.6);
                         }
@@ -418,6 +590,21 @@ const handleAccept = () => {
                                 color: #2199F8;
                             }
                         }
+
+                        &--textarea {
+                            align-items: flex-start;
+
+                            .detail-row__label {
+                                padding-top: 6px;
+                            }
+
+                            :deep(.el-textarea__inner) {
+                                border: 1px solid rgba(33, 153, 248, 0.5);
+                                border-radius: 4px;
+                                box-shadow: none;
+                                color: #2199F8;
+                            }
+                        }
                     }
                 }
 
@@ -460,6 +647,11 @@ const handleAccept = () => {
 
     .custom-bottom-fixed-btns {
         justify-content: space-between;
+
+        &--single {
+            justify-content: center;
+        }
+
         .secondary-btn{
             background: #F6F6F6;
             border: none;
@@ -472,6 +664,10 @@ const handleAccept = () => {
             background: #FF953D;
             color: #fff;
         }
+        .green-btn{
+            background: #4ABF32;
+            color: #fff;
+        }
     }
 }
 </style>

+ 279 - 274
src/views/old_mini/work_execute/index.vue

@@ -8,30 +8,27 @@
         </div>
         <div class="task-list">
             <div class="list-filter">
-                <div class="filter-item" :class="{ active: activeIndex === 0 }" @click="handleActiveFilter(0)">
-                    {{ t("workExecute.pending") }}({{ taskCounts[0] || 0 }})
-                </div>
-                <div class="filter-item" :class="{ active: activeIndex === 2 }" @click="handleActiveFilter(2)">
-                    {{ t("workExecute.accepted") }}({{ taskCounts[2] || 0 }})
-                </div>
-                <div class="filter-item" :class="{ active: activeIndex === 3 }" @click="handleActiveFilter(3)">
-                    {{ t("workExecute.executing") }}({{ taskCounts[3] || 0 }})
-                </div>
-                <div class="filter-item" :class="{ active: activeIndex === 4 }" @click="handleActiveFilter(3)">
-                    {{ t("workExecute.completed") }}({{ taskCounts[4] || 0 }})
+                <div
+                    v-for="item in filterTabs"
+                    :key="item.value"
+                    class="filter-item"
+                    :class="{ active: activeIndex === item.value }"
+                    @click="handleActiveFilter(item.value)"
+                >
+                    {{ 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="getSimpleList">
+                            @change="getRegionRecordData">
                             <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="getSimpleList">
+                            @change="getRegionRecordData">
                             <el-option v-for="item in districtList" :key="item.code" :label="item.name"
                                 :value="item.code" />
                         </el-select>
@@ -41,31 +38,39 @@
                 <!-- 任务列表 -->
                 <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="isTimeoutItem(index) ? 'timeout-item' : ''" @click="handleItem(item, index)">
-                        <div class="item-title">
+                        :class="[
+                            isAcceptOvertime(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="">
-                            <span class="title-text">{{ item.operation?.name }}</span>
-                            <span class="task-status" :class="`task-status--${item.operation?.type}`">{{
-                                item.operation?.typeName }}</span>
+                            <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="isTimeoutItem(index)">
+                        <span class="task-tag timeout" v-if="isAcceptOvertime(item)">
                             <el-icon>
                                 <WarningFilled />
                             </el-icon>
-                            {{ getTimeoutText() }}
+                            {{ t("workExecute.acceptTimeout") }}
                         </span>
-                        <span class="task-tag" v-else>
+                        <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">
-                                        某某某、某某某</span>
+                                        {{ 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?.drug }}</span>
+                                        {{ item.operation?.work_detail }}</span>
                                 </div>
                                 <div class="info-item">
                                     <div class="info-name">{{ t("workExecute.farmSituation") }}</div><span class="val-text">
@@ -94,6 +99,25 @@
                             </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>
+
                         <div class="unqualified-reason" v-if="activeStatus === '未达标'">
                             {{ t("workExecute.unqualifiedReason") }}{{ item.operation?.reason || t("workExecute.defaultUnqualifiedReason") }}
                         </div>
@@ -112,44 +136,46 @@
 
                         <div class="task-footer">
                             <div class="farm-info">{{ t("workExecute.fromFarm") }}</div>
-                            <div class="btn-group" v-if="getItemStatusButtons(index).length">
-                                <div v-for="btn in getItemStatusButtons(index)" :key="btn.label" class="edit-btn"
+                            <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>
-
                     </div>
-
                     <div class="empty-tip" v-if="!loading && taskList.length === 0">{{ t("workExecute.noData") }}</div>
                 </div>
             </div>
         </div>
     </div>
-    <!-- <upload-execute ref="uploadExecuteRef" :onlyShare="onlyShare" @uploadSuccess="handleUploadSuccess" /> -->
-    <!-- 服务报价单 -->
-    <!-- <price-sheet-popup :key="activeIndex" ref="priceSheetPopupRef"></price-sheet-popup> -->
-    <!-- 新增:激活上传弹窗 -->
-    <!-- <active-upload-popup ref="activeUploadPopupRef" @handleUploadSuccess="handleUploadSuccess"></active-upload-popup> -->
 </template>
 
 <script setup>
 import { computed, nextTick, onActivated, onMounted, ref, watch } from "vue";
-import { useRoute } from "vue-router";
 import { useStore } from "vuex";
 import IndexMap from "./index";
 import { useRouter } from "vue-router";
-import { WarningFilled } from "@element-plus/icons-vue";
+import { WarningFilled, CircleCheck } from "@element-plus/icons-vue";
 import { ElMessage } from "element-plus";
 import config from "@/api/config.js";
 import customCalendar from "./components/calendar.vue";
 import { useI18n } from "@/i18n";
-
-const { t } = useI18n();
+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 store = useStore();
 const router = useRouter();
-const route = useRoute();
 const indexMap = new IndexMap();
 const mapContainer = ref(null);
 const tabBarHeight = computed(() => store.state.home.tabBarHeight);
@@ -158,89 +184,21 @@ const selectParma = ref({
     districtCode: null,
 });
 
-// 任务列表数据(用于显示,可能被筛选)
-const taskList = ref([
-    {
-        time: "Fri, 22 May 2026 00:00:00 GMT",
-        geohash_sample: "ws0gs49ns213",
-        farmName: "冬季清园",
-        typeName: "荔枝",
-        userType: 1,
-        address: "广东省广州市从化区太平镇凤凰路88号",
-        farmPoint: "POINT(113.61652616170711 23.58399613872042)",
-        sourceData: {
-            currentPhenologyName: "花芽分化",
-            currentPhenologyStartDate: "2026-01-15",
-            daysUntilNext: 3,
-        },
-        operation: {
-            name: "冬季清园",
-            type: "1",
-            typeName: "标准类",
-            work_reason: "近期气温回升,需及时清理园内枯枝落叶,减少病虫越冬基数",
-            drug: "石硫合剂 500倍液",
-            machine_code: "DJI-T40-001",
-            work_id: 5001,
-        },
-    },
-    {
-        time: "Mon, 26 May 2026 00:00:00 GMT",
-        geohash_sample: "ws0gefxm68u5",
-        farmName: "施肥促花",
-        typeName: "柑橘",
-        userType: 2,
-        address: "广东省肇庆市德庆县官圩镇果园示范区",
-        farmPoint: "POINT(113.62757101477101 23.590796948574365)",
-        sourceData: {
-            currentPhenologyName: "开花期",
-            currentPhenologyStartDate: "2026-02-20",
-            daysUntilNext: 12,
-        },
-        operation: {
-            name: "施肥促花",
-            type: "2",
-            typeName: "感知类",
-            work_reason: "花芽分化期养分需求增加,需补充磷钾肥",
-            drug: "磷酸二氢钾 0.3%",
-            machine_code: "DJI-T50-003",
-            work_id: 5002,
-        },
-    },
-    {
-        time: "Wed, 28 May 2026 00:00:00 GMT",
-        geohash_sample: "ws0gefzjqqqw",
-        farmId: 1003,
-        farmName: "病虫害防治",
-        typeName: "水稻",
-        userType: 1,
-        address: "广东省清远市清城区龙塘镇农业科技园",
-        farmPoint: "POINT(113.62240816252164 23.59499176519138)",
-        sourceData: {
-            currentPhenologyName: "分蘖期",
-            currentPhenologyStartDate: "2026-03-01",
-            daysUntilNext: 8,
-        },
-        operation: {
-            name: "病虫害防治",
-            type: "1",
-            typeName: "复核类",
-            work_reason: "监测到蚜虫活动增多,建议及时喷药防治",
-            drug: "吡虫啉 1500倍液",
-            machine_code: "DJI-T40-002",
-            work_id: 5003,
-        },
-    },
-]);
+const taskList = ref([])
+
 const mode = "execute";
 const activeStatus = ref("待接受");
 
 const STATUS_BTN_MAP = {
     待接受: [
-        { labelKey: "workExecute.redispatch", type: "normal", action: "redispatch" },
-        { labelKey: "workExecute.remindAccept", type: "primary", action: "remindAccept" },
+        { 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.remindExecute", type: "primary", action: "remindExecute" }],
-    执行中: [{ labelKey: "workExecute.remindComplete", type: "primary", action: "remindComplete" }],
+    执行中: [{ labelKey: "workExecute.markComplete", type: "primary-green", action: "markComplete" }],
     已超时: [
         { labelKey: "workExecute.redispatch", type: "normal", action: "redispatch" },
         { labelKey: "workExecute.remindAccept", type: "primary", action: "remindAccept" },
@@ -259,52 +217,28 @@ const STATUS_LABEL_MAP = {
 
 const getStatusLabel = (status) => t(STATUS_LABEL_MAP[status] || status);
 
-const isTimeoutItem = (index) => index === 0;
-
-const getTimeoutRemindBtn = () => {
-    const map = {
-        待接受: { labelKey: "workExecute.remindAccept", type: "primary", action: "remindAccept" },
-        已接受: { labelKey: "workExecute.remindExecute", type: "primary", action: "remindExecute" },
-        执行中: { labelKey: "workExecute.remindComplete", type: "primary", action: "remindComplete" },
-    };
-    const btn = map[activeStatus.value];
-    return btn ? { ...btn, label: t(btn.labelKey) } : null;
+const STATUS_TAG_CLASS_MAP = {
+    待接受: "pending",
+    已接受: "accepted",
+    执行中: "executing",
+    已完成: "completed",
 };
 
+const getStatusTagClass = () => STATUS_TAG_CLASS_MAP[activeStatus.value] || "pending";
+
+const isAcceptOvertime = (item) => Number(item?.operation?.is_aovertime) === 1;
+
 const mapStatusButtons = (buttons) =>
     buttons.map((btn) => ({
         ...btn,
         label: t(btn.labelKey),
     }));
 
-const getItemStatusButtons = (index) => {
-    const baseButtons = mapStatusButtons(STATUS_BTN_MAP[activeStatus.value] || []);
-    if (!isTimeoutItem(index)) {
-        return baseButtons;
-    }
-    if (activeStatus.value === "待接受") {
-        return baseButtons;
-    }
-    const remindBtn = getTimeoutRemindBtn();
-    if (remindBtn) {
-        return [
-            { label: t("workExecute.redispatch"), type: "normal", action: "redispatch" },
-            remindBtn,
-        ];
+const getItemStatusButtons = (item) => {
+    if (isAcceptOvertime(item)) {
+        return [];
     }
-    return [
-        { label: t("workExecute.redispatch"), type: "normal", action: "redispatch" },
-        ...baseButtons,
-    ];
-};
-
-const getTimeoutText = () => {
-    const map = {
-        待接受: "workExecute.acceptTimeout",
-        已接受: "workExecute.executeTimeout",
-        执行中: "workExecute.completeTimeout",
-    };
-    return t(map[activeStatus.value] || "workExecute.timeout");
+    return mapStatusButtons(STATUS_BTN_MAP[activeStatus.value] || []);
 };
 
 const formatGMTToYMD = (gmtString) => {
@@ -317,14 +251,56 @@ const getWorkImageUrl = (image) => {
     return config.base_img_url3 + image.cloud_filename;
 };
 
+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 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 handleItem = (item, index) => {
+    const statusStr = item.operation?.is_aovertime ? '接受超时' : activeStatus.value;
+    const status = item.operation?.is_aovertime ? 1 : activeIndex.value;
     router.push({
         path: "/agri_record_detail",
         query: {
-            miniJson: JSON.stringify({
-                id: item.operation?.work_id,
-                status: activeStatus.value,
-            }),
+            id: item.operation?.work_id,
+            status,
+            statusStr,
         },
     });
 };
@@ -368,21 +344,15 @@ const handleDateSelect = (date) => {
 };
 // 各状态任务数量
 const taskCounts = ref([0, 0, 0]);
+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(2);
-const noData = ref(false);
+const activeIndex = ref(0);
 const loading = ref(false);
-// 分页相关
-const page = ref(0);
-const limit = ref(10);
-const loadingMore = ref(false);
-const finished = ref(false);
-
-// 查询未来农事预警
-const getFutureFarmWorkWarning = async (item) => {
-    const res = await VE_API.home.listFutureFarmWorkWarning({ farmId: item.farmId });
-    item.timelineList = res.data || [];
-};
 
 const cityCode = ref("");
 //根据城市的坐标返回区县列表
@@ -401,8 +371,6 @@ const mapPoint = ref(null);
 
 onMounted(() => {
     mapPoint.value = store.state.home.miniUserLocationPoint;
-    fullTaskList.value = [...taskList.value];
-    // getDistrictListByCity();
     resetAndLoad();
     getFarmWorkTypeList();
     syncCalendarData();
@@ -415,9 +383,7 @@ onMounted(() => {
 });
 
 onActivated(() => {
-    if (route.query.noReload) {
-        return;
-    }
+    getRegionRecordData();
     // 确保地图已初始化,使用 nextTick 等待 DOM 更新
     nextTick(() => {
         // 检查地图实例是否已初始化
@@ -458,124 +424,50 @@ onActivated(() => {
     });
 });
 
-// 监听 activeIndex 变化,重新加载数据
-watch(activeIndex, () => {
-    resetAndLoad();
-});
-
-// 加载列表数据(支持分页)
-async function getSimpleList(isLoadMore = false) {
-    if (!isLoadMore) {
-        // 重置分页
-        page.value = 0;
-        finished.value = false;
-        // taskList.value = [];
-        loading.value = true;
-    } else {
-        loadingMore.value = true;
-    }
-
+const getRegionRecordData = async () => {
     const params = {
-        ...selectParma.value,
-        page: page.value,
-        limit: limit.value,
-        flowStatus: 5,
+        // farm_id: store.state.home.miniUserFarmId,
+        farm_id: '320',
+        time: '2026-06-23',
+        work_operation_type: activeIndex.value,
     };
-
-    try {
-        const { data } = await VE_API.home.listUnansweredFarms(params);
-
-        if (data && data.length > 0) {
-            // 为每个item初始化timelineList
-            const newItems = data.map((item) => {
-                let sourceData = item?.latestPhenologyProgressBroadcast?.sourceData;
-                if (sourceData) {
-                    try {
-                        sourceData = JSON.parse(sourceData);
-                    } catch (e) {
-                        console.error("解析sourceData失败:", e);
-                        sourceData = null;
-                    }
-                }
-                return {
-                    ...item,
-                    sourceData,
-                    timelineList: [],
-                };
-            });
-
-            // 串行请求,为每个农场获取时间轴数据
-            for (let i = 0; i < newItems.length; i++) {
-                await getFutureFarmWorkWarning(newItems[i]);
-            }
-
-            // 追加数据
-            // const newTaskList = [...taskList.value, ...newItems];
-            // taskList.value = newTaskList.filter(item => item.timelineList.length > 0);
-
-            // 更新分页
-            page.value += 1;
-
-            // 判断是否还有更多数据
-            if (data.length < limit.value) {
-                finished.value = true;
-            }
-
-            // 更新地图数据
-            indexMap.initData(taskList.value, '', 'farmPoint');
-        } else {
-            finished.value = true;
-            if (taskList.value.length === 0) {
-                noData.value = true;
-            }
-        }
-
-        // 数据处理完成后再设置loading为false
-        if (!isLoadMore) {
-            loading.value = false;
-        } else {
-            loadingMore.value = false;
-        }
-    } catch (error) {
-        console.error("获取任务列表失败:", error);
-        if (!isLoadMore) {
-            loading.value = false;
-        } else {
-            loadingMore.value = false;
-        }
-        finished.value = true;
-        if (taskList.value.length === 0) {
-            noData.value = true;
-        }
-    }
-}
-
-// 滚动加载更多
-const onLoad = () => {
-    if (!finished.value && !loadingMore.value) {
-        getSimpleList(true);
+    const { code, data } = await VE_API.farm.getRegionRecord(params);
+    if (code === 200 && data && data.length > 0) {
+        taskList.value = data.filter(item => item.operation !== null);
+    } else {
+        taskList.value = [];
     }
 };
 
+// 监听 activeIndex 变化,重新加载数据
+watch(activeIndex, () => {
+    resetAndLoad();
+});
+
 // 重置并重新加载
 const resetAndLoad = () => {
-    getSimpleList(false);
+    getRegionRecordData();
 };
 
 function handleActiveFilter(i) {
     activeIndex.value = i;
     selectParma.value.districtCode = cityCode.value;
     selectParma.value.farmWorkTypeId = null;
-    const statusMap = { 0: "待接受", 2: "已接受", 3: "执行中" };
+    const statusMap = { 0: "待接受", 1: "执行中", 2: "已接受", 3: "已完成" };
     activeStatus.value = statusMap[i] || "待接受";
     filterDate.value = null;
-    taskList.value = [...fullTaskList.value];
+    // taskList.value = [...fullTaskList.value];
     calendarRef.value?.clearSelection();
     syncCalendarData();
 }
 
-function handleRemindCustomer(item) {
-    // 接受
+const getWorkType = (type) => {
+    const map = {
+        '标准防治类': 1,
+        '标准施肥类': 2,
+        '标准调节类': 3,
+    }
+    return map[type] || type;
 }
 </script>
 
@@ -730,9 +622,14 @@ function handleRemindCustomer(item) {
                 border: 1px solid #f74e4e;
             }
 
+            &--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;
@@ -740,14 +637,41 @@ function handleRemindCustomer(item) {
 
                 .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 {
-                    padding-left: 8px;
+                    min-width: 0;
                 }
 
                 .task-status {
-                    margin-left: 8px;
+                    margin-left: 0;
                     border-radius: 2px;
                     height: 20px;
                     line-height: 20px;
@@ -793,6 +717,77 @@ function handleRemindCustomer(item) {
                     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 {
@@ -895,6 +890,16 @@ function handleRemindCustomer(item) {
                     background: #FF953D;
                     color: #fff;
                 }
+
+                &.primary-blue {
+                    background: #2199F8;
+                    color: #fff;
+                }
+
+                &.primary-green {
+                    background: #37C11B;
+                    color: #fff;
+                }
             }
 
             .compare-imgs {