ソースを参照

feat:对接农事列表接口和详情页样式

wangsisi 21 時間 前
親
コミット
f8e6371d87

+ 4 - 3
src/api/modules/questionnaire.js

@@ -31,9 +31,10 @@ module.exports = {
         url: config.base_new_url + "pheno/inner_interact_tasks",
         type: "get",
     },
-    // 获取农事任务(zone_id + date,返回当日扁平 fw_tasks)
-    getFwTasks: {
-        url: config.base_new_url + "pheno/fw_tasks",
+    // 分区农事任务列表:GET /api/questionnaire/zone_fw_task_list
+    // 参数 zone_id + farm_id + trigger_date;返回 fw_tasks(标准化)/ weather_work(气象)
+    getZoneFwTaskList: {
+        url: config.base_new_url + "questionnaire/zone_fw_task_list",
         type: "get",
     },
     // 获取农事详细内容(人工 / 机械方案)

+ 10 - 2
src/i18n/messages.js

@@ -320,9 +320,13 @@ export default {
             reason: "农事原因",
             purpose: "农事目的",
             notes: "注意事项",
-            prescription: "药物处方",
+            prescription: "药肥处方",
+            plantingPlan: "种植方案",
+            triggerCondition: "触发条件",
             executionMethod: "执行方式",
             executeParameters: "执行参数",
+            agriMaterial: "农资信息",
+            usageMethod: "使用方法",
         },
         workExecute: {
             pending: "待接受",
@@ -765,9 +769,13 @@ export default {
             reason: "Reason",
             purpose: "Purpose",
             notes: "Notes",
-            prescription: "Rx",
+            prescription: "Prescription",
+            plantingPlan: "Planting plan",
+            triggerCondition: "Trigger",
             executionMethod: "Method",
             executeParameters: "Parameters",
+            agriMaterial: "Supplies",
+            usageMethod: "Usage",
         },
         workExecute: {
             pending: "Pending",

+ 109 - 0
src/views/old_mini/work_detail/components/agriMaterialTable.vue

@@ -0,0 +1,109 @@
+<template>
+    <div class="agri-material-table">
+        <div class="agri-material-table__header">
+            <div
+                v-for="col in columns"
+                :key="col.key"
+                class="agri-material-table__col"
+                :class="`col-${col.key}`"
+            >
+                {{ col.label }}
+            </div>
+        </div>
+        <div
+            v-for="(row, index) in rows"
+            :key="row.id || index"
+            class="agri-material-table__row"
+        >
+            <div
+                v-for="col in columns"
+                :key="col.key"
+                class="agri-material-table__col"
+                :class="`col-${col.key}`"
+            >
+                {{ row[col.key] || "-" }}
+            </div>
+        </div>
+        <div v-if="!rows.length" class="agri-material-table__empty">暂无数据</div>
+    </div>
+</template>
+
+<script setup>
+const columns = [
+    { key: "effect", label: "功效" },
+    { key: "name", label: "名称" },
+    { key: "brand", label: "品牌" },
+    { key: "dosage", label: "亩用量" },
+];
+
+defineProps({
+    rows: {
+        type: Array,
+        default: () => [],
+    },
+});
+</script>
+
+<style lang="scss" scoped>
+.agri-material-table {
+    border: 1px solid rgba(33, 153, 248, 0.35);
+    border-radius: 6px;
+    overflow: hidden;
+    background: #fff;
+}
+
+.agri-material-table__header,
+.agri-material-table__row {
+    display: flex;
+    align-items: stretch;
+}
+
+.agri-material-table__header {
+    background: rgba(33, 153, 248, 0.1);
+    color: #2199F8;
+    font-size: 13px;
+    font-weight: 500;
+}
+
+.agri-material-table__row {
+    color: #4F4F4F;
+    font-size: 13px;
+    border-top: 1px solid rgba(0, 0, 0, 0.06);
+}
+
+.agri-material-table__col {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    text-align: center;
+    padding: 10px 4px;
+    box-sizing: border-box;
+    word-break: break-word;
+    line-height: 18px;
+
+    &.col-effect {
+        width: 56px;
+        flex: none;
+    }
+    &.col-name {
+        flex: 1.1;
+        min-width: 0;
+    }
+    &.col-brand {
+        flex: 1;
+        min-width: 0;
+    }
+    &.col-dosage {
+        width: 72px;
+        flex: none;
+    }
+}
+
+.agri-material-table__empty {
+    padding: 16px 0;
+    text-align: center;
+    color: #9C9C9C;
+    font-size: 13px;
+    border-top: 1px solid rgba(0, 0, 0, 0.06);
+}
+</style>

ファイルの差分が大きいため隠しています
+ 285 - 696
src/views/old_mini/work_detail/index.vue


+ 77 - 52
src/views/old_mini/work_execute/index.vue

@@ -41,6 +41,9 @@
 
                     <div class="task-card__body">
                         <div class="info-line">
+                            触发条件:{{ item.fw_trigger || '--' }}
+                        </div>
+                        <div class="info-line">
                             {{ t("workExecute.farmSituation") }}{{ item.analysisSummary }}
                         </div>
                         <div class="info-line">
@@ -94,11 +97,17 @@ import wx from "weixin-js-sdk";
 
 
 // ---------- 常量 ----------
-/** zoneId / date 临时固定,后续改为当前分区 / 当天 */
+/** zoneId / farmId / date 临时固定,后续改为当前分区 / 当天 */
 const QUERY = {
     zoneId: 41,
+    farmId: 321,
     date: "2025-09-11",
 };
+/** 任务类型:fw_tasks 标准化农事 / weather_work 气象农事 */
+const TASK_KIND = {
+    NORMAL: "normal",
+    WEATHER: "weather",
+};
 const COMPLETED_STATUS_RE = /已完成|已执行|已认证|本人完成/;
 
 const { t } = useI18n();
@@ -131,6 +140,13 @@ function getMiniUserId() {
     return store.state.home.miniUserId || localStorage.getItem("MINI_USER_ID");
 }
 
+/** 与图片一致:zone_id / farm_id / trigger_date(临时写死,后续改当前分区) */
+const resolveQueryParams = () => ({
+    zone_id: QUERY.zoneId,
+    farm_id: QUERY.farmId,
+    trigger_date: filterDate.value || QUERY.date,
+});
+
 const isCompletedStatus = (status) => COMPLETED_STATUS_RE.test(String(status || ""));
 
 const formatExecuteDate = (dateStr) => {
@@ -144,36 +160,51 @@ const buildPointWkt = (lng, lat, index = 0) => {
     return `POINT(${Number(lng) + offset} ${Number(lat) + offset * 0.6})`;
 };
 
-/** 将扁平 fw_tasks 映射为卡片;日期优先用任务自身 date,否则用接口 data.date */
-const mapFwTasks = (fwTasks, dateStr, lat, lng, zoneId) => {
-    const list = Array.isArray(fwTasks) ? fwTasks : [];
-    return list
-        .filter((task) => task)
-        .map((task, index) => {
-            const executeDateFull = task.date || dateStr;
-            const completed = isCompletedStatus(task.fw_status);
-            return {
-                id: `${task.manual_id || task.mechanical_id || "fw"}_${index}_${executeDateFull}`,
-                status: completed ? "completed" : "pending",
-                title: task.fw_name || "",
-                isHighRisk: false,
-                isNormal: true,
-                isAbnormal: false,
-                isWeather: false,
-                cornerTip: task.fw_status || (completed ? "已完成" : "待执行"),
-                analysisSummary: task.fw_simple_explain || "",
-                areaCode: zoneId ? `分区 ${zoneId}` : "",
-                farmName: task.fw_name || "",
-                executeDate: formatExecuteDate(executeDateFull),
-                executeDateFull,
-                zoneId,
-                manualId: task.manual_id,
-                mechanicalId: task.mechanical_id,
-                latitude: lat,
-                longitude: lng,
-                wkt: buildPointWkt(lng, lat, index),
-            };
-        });
+/** 单条任务映射为卡片;kind 区分标准化 / 气象 */
+const mapTaskItem = (task, index, { dateStr, lat, lng, zoneId, kind }) => {
+    const executeDateFull = task.date || dateStr;
+    const completed = isCompletedStatus(task.fw_status);
+    const isWeather = kind === TASK_KIND.WEATHER;
+    const prefix = isWeather ? "weather" : "fw";
+    return {
+        id: `${prefix}_${task.manual_id || task.mechanical_id || index}_${executeDateFull}`,
+        status: completed ? "completed" : "pending",
+        title: task.fw_name || "",
+        isHighRisk: false,
+        isNormal: !isWeather,
+        isAbnormal: false,
+        isWeather,
+        cornerTip: task.fw_status || (completed ? "已完成" : "待执行"),
+        analysisSummary: task.fw_simple_explain || task.fw_explain || "",
+        areaCode: zoneId ? `分区 ${zoneId}` : "",
+        farmName: task.fw_name || "",
+        executeDate: formatExecuteDate(executeDateFull),
+        executeDateFull,
+        zoneId,
+        manualId: task.manual_id,
+        mechanicalId: task.mechanical_id,
+        latitude: lat,
+        longitude: lng,
+        wkt: buildPointWkt(lng, lat, index),
+    };
+};
+
+/** 合并 fw_tasks(标准化)+ weather_work(气象) */
+const mapZoneTaskList = (data) => {
+    const dateStr = resolveRangeStart(data);
+    const lat = data.latitude ?? 22.970229;
+    const lng = data.longitude ?? 113.746646;
+    const zoneId = data.zone_id ?? QUERY.zoneId;
+    const ctx = { dateStr, lat, lng, zoneId };
+    const normalList = (Array.isArray(data.fw_tasks) ? data.fw_tasks : [])
+        .filter(Boolean)
+        .map((task, index) => mapTaskItem(task, index, { ...ctx, kind: TASK_KIND.NORMAL }));
+    const weatherList = (Array.isArray(data.weather_work) ? data.weather_work : [])
+        .filter(Boolean)
+        .map((task, index) =>
+            mapTaskItem(task, normalList.length + index, { ...ctx, kind: TASK_KIND.WEATHER })
+        );
+    return { list: [...normalList, ...weatherList], lat, lng, dateStr, zoneId };
 };
 
 const resolveRangeStart = (data) => {
@@ -221,35 +252,23 @@ const syncCalendar = (data) => {
     });
 };
 
-const fetchFwTasks = async () => {
+const fetchZoneFwTaskList = async () => {
     try {
-        const res = await VE_API.questionnaire.getFwTasks({
-            zone_id: QUERY.zoneId,
-            date: QUERY.date,
-        });
+        const params = resolveQueryParams();
+        const res = await VE_API.questionnaire.getZoneFwTaskList(params);
         const data = res?.data;
         if (!data) {
             taskList.value = [];
             mapCenterPoint.value = null;
-            calendarRef.value?.setStartDate?.(QUERY.date);
-            calendarRef.value?.selectDateByValue?.(QUERY.date);
+            calendarRef.value?.setStartDate?.(params.trigger_date);
+            calendarRef.value?.selectDateByValue?.(params.trigger_date);
             calendarRef.value?.setSolarTerm?.([]);
             refreshMapMarkers();
             return;
         }
-
-        const lat = data.latitude ?? 22.970229;
-        const lng = data.longitude ?? 113.746646;
-        const dateStr = resolveRangeStart(data);
+        const { list, lat, lng, dateStr } = mapZoneTaskList(data);
         mapCenterPoint.value = `POINT(${lng} ${lat})`;
-
-        taskList.value = mapFwTasks(
-            data.fw_tasks,
-            dateStr,
-            lat,
-            lng,
-            data.zone_id ?? QUERY.zoneId
-        );
+        taskList.value = list;
         filterDate.value = dateStr;
         syncCalendar(data);
         nextTick(() => refreshMapMarkers());
@@ -266,7 +285,10 @@ const handleTabChange = (key) => {
 };
 
 const handleDateSelect = (date) => {
-    filterDate.value = date ?? QUERY.date;
+    const next = date ?? QUERY.date;
+    if (filterDate.value === next) return;
+    filterDate.value = next;
+    fetchZoneFwTaskList();
 };
 
 const handleForward = (item) => {
@@ -295,7 +317,10 @@ const buildFwTaskQuery = (item) => ({
     lat: item?.latitude,
     lng: item?.longitude,
     fw_name: item?.title || item?.farmName || "",
+    isNormal: item?.isNormal ? 1 : 0,
     isAbnormal: item?.isAbnormal ? 1 : 0,
+    isWeather: item?.isWeather ? 1 : 0,
+    isHighRisk: item?.isHighRisk ? 1 : 0,
 });
 
 const handleViewDetail = (item) => {
@@ -327,7 +352,7 @@ const initMap = () => {
 onMounted(() => {
     checkFarmCalendarPopup();
     nextTick(async () => {
-        await fetchFwTasks();
+        await fetchZoneFwTaskList();
         initMap();
     });
 });

この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません