Jelajahi Sumber

fix: 对接报告

lxf 2 minggu lalu
induk
melakukan
52e9b854d1

+ 5 - 0
src/api/modules/questionnaire.js

@@ -11,6 +11,11 @@ module.exports = {
         url: config.base_new_url + "pheno/initial_interact_options",
         type: "get",
     },
+    // 保存感知结果接口
+    saveInteractResult: {
+        url: config.base_new_url + "pheno/interact_record",
+        type: "post",
+    },
     // 初始化报告
     generateReport: {
         url: config.base_new_url + "report/generate",

+ 82 - 24
src/components/popup/restorePlantingPopup.vue

@@ -90,7 +90,11 @@
             </div>
 
             <div class="restore-planting-popup__actions">
-                <div class="restore-planting-popup__btn restore-planting-popup__btn--primary" @click="handleConfirm">
+                <div
+                    class="restore-planting-popup__btn restore-planting-popup__btn--primary"
+                    :class="{ disabled: submitting }"
+                    @click="handleConfirm"
+                >
                     {{ questionType === "time" ? t("agriFile.confirmNow") : t("agriFile.submitInfo") }}
                 </div>
             </div>
@@ -135,6 +139,7 @@ const visible = ref(false);
 const questionType = ref("time"); // time | phenology
 const selectedDate = ref("");
 const phenologyAnswer = ref("");
+const submitting = ref(false);
 
 const hasDateInteract = ref(false);
 const hasOptionsInteract = ref(false);
@@ -198,15 +203,17 @@ const extractHighlightWord = (text) => {
 };
 
 /** Message 需高于弹窗 z-index: 20000 */
-const showWarning = (message) => {
+const showMessage = (message, type = "warning") => {
     ElMessage({
         message,
-        type: "warning",
+        type,
         customClass: "restore-planting-message",
         zIndex: 40000,
     });
 };
 
+const showWarning = (message) => showMessage(message, "warning");
+
 const resetForm = () => {
     questionType.value = hasDateInteract.value ? "time" : "phenology";
     selectedDate.value = "";
@@ -276,8 +283,8 @@ const checkShouldShow = async () => {
     try {
         const res = await VE_API.questionnaire.getInitialInteraction({
             zone_id: props.zoneId,
-            // date: formatToday(),
-            date: '2025-07-10',
+            date: formatToday(),
+            // date: '2025-07-10',
         });
         if (res?.code === 200 && res.data) {
             applyQuestionData(res.data);
@@ -314,33 +321,82 @@ const handlePhenologySelect = (value) => {
     selectedPeriodCode.value = current?.period_code || "";
 };
 
-/** 组装提交载荷(提交接口待对接) */
-const buildSubmitPayload = () => ({
-    zone_id: props.zoneId,
-    date: toApiDate(selectedDate.value) || formatToday(),
-    period_code: selectedPeriodCode.value,
-    interact_code: dateInteractData.value?.interact_code || "",
-});
+/** 组装保存感知结果载荷 */
+/**
+ * 组装保存感知结果载荷
+ * @param {'time'|'phenology'} type 问题一不含 period_code;问题二不含 interact_code
+ */
+const buildSubmitPayload = (type = "time") => {
+    const payload = {
+        zone_id: Number(props.zoneId) || props.zoneId,
+        date: toApiDate(selectedDate.value) || formatToday(),
+    };
+    if (type === "time") {
+        payload.interact_code = dateInteractData.value?.interact_code || "";
+    } else {
+        payload.period_code = selectedPeriodCode.value || "";
+    }
+    return payload;
+};
+
+/** 从 axios 错误中取出可读信息 */
+const getRequestErrorMessage = (err) => {
+    const data = err?.response?.data;
+    if (typeof data === "string" && data) return data;
+    if (data?.msg) return data.msg;
+    if (data?.message) return data.message;
+    if (err?.message) return err.message;
+    return "提交失败,请稍后再试";
+};
+
+/**
+ * 调用保存感知结果接口
+ * @param {'time'|'phenology'} type
+ * @param {{ closeOnSuccess?: boolean }} options closeOnSuccess=false 时成功后不关弹窗(用于继续答问题二)
+ */
+const submitInteractResult = async (type = "time", { closeOnSuccess = true } = {}) => {
+    if (submitting.value) return false;
+    submitting.value = true;
+    try {
+        const res = await VE_API.questionnaire.saveInteractResult(buildSubmitPayload(type));
+        if (res?.code === 200) {
+            if (closeOnSuccess) {
+                showMessage(res.msg || "提交成功", "success");
+                visible.value = false;
+                resetForm();
+            }
+            return true;
+        }
+        showMessage(res?.msg || "提交失败,请稍后再试", "error");
+        return false;
+    } catch (err) {
+        showMessage(getRequestErrorMessage(err), "error");
+        return false;
+    } finally {
+        submitting.value = false;
+    }
+};
 
 const handleConfirm = async () => {
+    if (submitting.value) return;
+
+    // 问题一:立即确认 → 提交(不含 period_code)
     if (questionType.value === "time") {
         if (!selectedDate.value) {
             showWarning(t("agriFile.selectTime"));
             return;
         }
-        // 还有物候题则进入问题二,否则直接结束
-        if (hasOptionsInteract.value) {
+        const ok = await submitInteractResult("time", {
+            // 还有问题二则提交成功后继续展示,不关弹窗
+            closeOnSuccess: !hasOptionsInteract.value,
+        });
+        if (ok && hasOptionsInteract.value) {
             questionType.value = "phenology";
-            return;
         }
-        // TODO: 对接提交接口
-        // await VE_API.xxx(buildSubmitPayload());
-        visible.value = false;
-        resetForm();
         return;
     }
 
-    // 问题二:需已选择「已到达」并写入 period_code
+    // 问题二:需已选择「已到达」并写入 period_code;提交不含 interact_code
     if (phenologyAnswer.value !== "reached" || !selectedPeriodCode.value) {
         const current = interactMap.value[currentInteractKey.value];
         if (phenologyAnswer.value === "reached" && current?.period_code) {
@@ -351,10 +407,7 @@ const handleConfirm = async () => {
         }
     }
 
-    // TODO: 对接提交接口(提交前用 buildSubmitPayload,内含 period_code)
-    // await VE_API.xxx(buildSubmitPayload());
-    visible.value = false;
-    resetForm();
+    await submitInteractResult("phenology");
 };
 
 defineExpose({
@@ -549,6 +602,11 @@ defineExpose({
             // flex: 1;
             background: linear-gradient(180deg, #76c3ff 0%, #2199f8 100%);
             color: #fff;
+
+            &.disabled {
+                opacity: 0.6;
+                pointer-events: none;
+            }
         }
     }
 }

+ 1 - 1
src/views/old_mini/agri_file/index.vue

@@ -247,7 +247,7 @@ const reportList = ref([
 
 const showUploadPopup = ref(false);
 const showCompleteFarmPopup = ref(false);
-const showDiagnosisReportPopup = ref(false);
+const showDiagnosisReportPopup = ref(true);
 // 后续由接口控制是否展示代管授权弹窗
 const showProxyAuthPopup = ref(false);
 const proxyServiceName = ref("农服名称");

+ 300 - 172
src/views/old_mini/agri_file/pages/diagnosisReport.vue

@@ -2,77 +2,71 @@
     <div class="diagnosis-report-page">
         <custom-header :name="t('agriFile.initialReport')" :isGoBack="true" @goback="handleBack" />
 
-        <div class="diagnosis-report-body">
+        <div v-if="loading" class="diagnosis-report-status">加载中...</div>
+        <div v-else-if="!reportData" class="diagnosis-report-status">暂无报告数据</div>
+
+        <div v-else class="diagnosis-report-body">
             <div class="report-hero">
                 <div class="report-hero__title">{{ reportData.title }}</div>
                 <div class="report-hero__desc">{{ reportData.intro }}</div>
             </div>
 
-            <!-- 基本情况与种植价值 -->
-            <div class="report-section">
-                <div class="report-section__title">{{ reportData.basic.title }}</div>
-                <div v-for="card in reportData.basic.cards" :key="card.title" class="info-card">
-                    <div class="info-card__title">{{ card.title }}</div>
-                    <div v-for="(para, idx) in card.paragraphs" :key="idx" class="info-card__text">
-                        {{ para }}
+            <div
+                v-for="(section, sIdx) in reportData.sections"
+                :key="sIdx"
+                class="report-section"
+            >
+                <div class="report-section__title">{{ section.displayTitle || section.title }}</div>
+
+                <!-- 基本情况与种植价值:灰色卡片(无 ### 分组) -->
+                <template v-if="section.type === 'basic'">
+                    <div v-for="card in section.cards" :key="card.title" class="info-card">
+                        <div class="info-card__title">{{ card.title }}</div>
+                        <div v-for="(para, idx) in card.paragraphs" :key="idx" class="info-card__text">
+                            {{ para }}
+                        </div>
                     </div>
-                </div>
-            </div>
+                </template>
 
-            <!-- 决定产量的气象风险 -->
-            <div class="report-section">
-                <div class="report-section__title">{{ reportData.weatherRisk.title }}</div>
-                <div class="info-card__text mb-10">{{ reportData.weatherRisk.intro }}</div>
-                <div class="section-divider">
-                    <span>{{ reportData.weatherRisk.groupTitle }}</span>
-                </div>
-                <div class="info-card fertilizer-card">
-                    <div v-for="item in reportData.weatherRisk.items" :key="item.tag" class="tag-block">
-                        <span class="tag-block__tag">{{ item.tag }}</span>
-                        <div class="tag-block__text">{{ item.content }}</div>
-                    </div>
-                    <div class="suggest-card">
-                        <div class="suggest-card__title">{{ reportData.weatherRisk.suggest.title }}</div>
-                        <div v-for="row in reportData.weatherRisk.suggest.rows" :key="row.label"
-                            class="suggest-card__row">
-                            <span class="suggest-card__label">{{ row.label }}</span>
-                            <span>{{ row.content }}</span>
+                <!-- 气象风险 / 肥料 / 病虫害:分组 + 飞鸟建议 -->
+                <template v-else>
+                    <div v-if="section.intro" class="info-card__text mb-10">{{ section.intro }}</div>
+
+                    <div v-for="(group, gIdx) in section.groups" :key="gIdx" class="risk-group">
+                        <div class="section-divider">
+                            <span>{{ group.title }}</span>
+                        </div>
+                        <div class="info-card fertilizer-card">
+                            <div v-for="item in group.items" :key="item.tag" class="tag-block">
+                                <span class="tag-block__tag">{{ item.tag }}</span>
+                                <div class="tag-block__text">{{ item.content }}</div>
+                            </div>
                         </div>
                     </div>
-                </div>
-            </div>
 
-            <!-- 肥料供给效率分析 -->
-            <div class="report-section">
-                <div class="report-section__title">{{ reportData.fertilizer.title }}</div>
-                <div class="info-card fertilizer-card">
-                    <div class="info-card__text">{{ reportData.fertilizer.intro }}</div>
-                    <div class="section-divider">
-                        <span>{{ reportData.fertilizer.groupTitle }}</span>
-                    </div>
-                    <div v-for="item in reportData.fertilizer.items" :key="item.tag" class="tag-block">
-                        <span class="tag-block__tag">{{ item.tag }}</span>
-                        <div class="tag-block__text">{{ item.content }}</div>
-                    </div>
-                    <div class="suggest-card">
-                        <div class="suggest-card__title">{{ reportData.fertilizer.suggest.title }}</div>
-                        <div v-for="row in reportData.fertilizer.suggest.rows" :key="row.label"
-                            class="suggest-card__row">
+                    <div v-if="section.suggest" class="suggest-card">
+                        <div class="suggest-card__title">{{ section.suggest.title }}</div>
+                        <div
+                            v-for="row in section.suggest.rows"
+                            :key="row.label"
+                            class="suggest-card__row"
+                        >
                             <span class="suggest-card__label">{{ row.label }}</span>
                             <span>{{ row.content }}</span>
                         </div>
                     </div>
-                </div>
+                </template>
             </div>
         </div>
 
-        <div class="share-btn" @click="handleShare">{{ t("agriFile.forwardReport") }}</div>
+        <!-- <div v-if="reportData" class="share-btn" @click="handleShare">{{ t("agriFile.forwardReport") }}</div> -->
     </div>
 </template>
 
 <script setup>
-import { ref } from "vue";
+import { onMounted, ref } from "vue";
 import { useRouter, useRoute } from "vue-router";
+import { ElMessage } from "element-plus";
 import customHeader from "@/components/customHeader.vue";
 import { useI18n } from "@/i18n";
 import wx from "weixin-js-sdk";
@@ -81,109 +75,226 @@ const { t } = useI18n();
 const router = useRouter();
 const route = useRoute();
 
-/** 假数据:后续可替换为接口 */
-const reportData = ref({
-    title: "《广州市从化区妃子笑荔枝初始种植诊断报告》",
-    intro: "针对您的果园坐标、妃子笑荔枝和种植类型,飞鸟结合从化区长期种植条件,为您生成专属初始种植诊断报告",
-    basic: {
-        title: "基本情况与种植价值",
-        cards: [
-            {
-                title: "种植基础",
-                paragraphs: [
-                    "从化地处北回归线附近低纬度地带,属南亚热带季风气候,年均气温约20℃上下,雨量充足、光照充沛,森林覆盖率高的低山丘陵为荔枝提供了较理想的生长环境。",
-                ],
-            },
-            {
-                title: "品种价值",
-                paragraphs: [
-                    "妃子笑为当地率先登场的早熟品种,早结、丰产稳产性好,果果大核小、肉厚清甜,商品性较好,且抗逆性和成花着果能力在同产区中表现突出。",
-                    "产期价值:本地妃子笑多在5月下旬至6月中旬成熟采收,比当地桂味、糯米糍、槐枝等中晚熟品种提早约一个月上市,能在荔枝上市初期先供应市场、填补尝鲜空档。",
-                ],
-            },
-            {
-                title: "商品定位",
-                paragraphs: [
-                    "依托从化自然环境与当季鲜采优势,妃子笑宜以「本地早熟、果大肉厚清甜、新摘即卖」为卖点,面向本地及周边短途鲜食市场,强调产地直供与口感新鲜度。",
-                ],
+const loading = ref(false);
+const reportData = ref(null);
+
+// ---------------------------------------------------------------------------
+// Markdown 解析:将接口 report 文本转成页面结构
+// ---------------------------------------------------------------------------
+
+/** 去掉 markdown 标题前缀,保留书名号 */
+const cleanTitle = (text = "") =>
+    String(text)
+        .replace(/^#+\s*/, "")
+        .trim();
+
+/** 去掉「一、」「二、」等章节序号,用于展示 */
+const stripChapterNo = (title = "") =>
+    String(title)
+        .replace(/^[一二三四五六七八九十百]+[、..]\s*/, "")
+        .trim();
+
+/** 是否为「基本情况与种植价值」章节(结构与其它章节不同:仅有一级列表卡片) */
+const isBasicSectionTitle = (title = "") => /基本情况|种植价值/.test(title);
+
+/** 解析 `- 标签:内容` / `- 标签:内容` */
+const parseLabeledItem = (line) => {
+    const text = String(line).replace(/^[-*•]\s+/, "").trim();
+    const match = text.match(/^([^::]+)[::]\s*(.+)$/);
+    if (!match) return null;
+    return {
+        label: match[1].trim(),
+        content: match[2].trim(),
+    };
+};
+
+/** 判断是否为「飞鸟建议」小节 */
+const isSuggestHeading = (title) => /飞鸟建议/.test(title || "");
+
+/**
+ * 将 markdown 报告解析为 { title, intro, sections }
+ * - basic:基本情况与种植价值(- 标签:内容 → 灰色卡片)
+ * - risk:含 ### 分组 + 可选飞鸟建议
+ */
+const parseReportMarkdown = (markdown) => {
+    const lines = String(markdown || "")
+        .replace(/\r\n/g, "\n")
+        .split("\n")
+        .map((line) => line.trimEnd());
+
+    let title = "";
+    const introLines = [];
+    const sections = [];
+    let currentSection = null;
+    let currentGroup = null;
+    let inIntro = true;
+
+    const createSection = (rawTitle) => {
+        const fullTitle = cleanTitle(rawTitle);
+        const basic = isBasicSectionTitle(fullTitle);
+        return {
+            title: fullTitle,
+            displayTitle: stripChapterNo(fullTitle),
+            type: basic ? "basic" : "risk",
+            intro: "",
+            cards: [],
+            groups: [],
+            suggest: null,
+            _introLines: [],
+        };
+    };
+
+    const flushGroup = () => {
+        if (!currentSection || !currentGroup) return;
+        // 基本情况章节不应出现 ###,若误入则并入卡片
+        if (currentSection.type === "basic") {
+            currentGroup.items.forEach((item) => {
+                currentSection.cards.push({
+                    title: item.tag,
+                    paragraphs: [item.content],
+                });
+            });
+            currentGroup = null;
+            return;
+        }
+        if (isSuggestHeading(currentGroup.title)) {
+            currentSection.suggest = {
+                title: currentGroup.title,
+                rows: currentGroup.items.map((item) => ({
+                    label: `${item.tag}:`,
+                    content: item.content,
+                })),
+            };
+        } else {
+            currentSection.groups.push({
+                title: currentGroup.title,
+                items: currentGroup.items,
+            });
+        }
+        currentGroup = null;
+    };
+
+    for (const raw of lines) {
+        const line = raw.trim();
+        if (!line) continue;
+
+        if (/^#\s+/.test(line) && !/^##/.test(line)) {
+            title = cleanTitle(line);
+            continue;
+        }
+
+        if (/^##\s+/.test(line)) {
+            flushGroup();
+            inIntro = false;
+            currentSection = createSection(line);
+            sections.push(currentSection);
+            continue;
+        }
+
+        if (/^###\s+/.test(line)) {
+            flushGroup();
+            if (!currentSection) {
+                currentSection = createSection("");
+                sections.push(currentSection);
+            }
+            // 基本情况章节忽略 ###,当作普通内容跳过标题行
+            if (currentSection.type === "basic") {
+                currentGroup = null;
+                continue;
+            }
+            currentGroup = {
+                title: cleanTitle(line).replace(/^\d+\.\s*/, ""),
+                items: [],
+            };
+            continue;
+        }
+
+        if (/^[-*•]\s+/.test(line)) {
+            const item = parseLabeledItem(line);
+            if (!item) continue;
+            if (!currentSection) {
+                currentSection = createSection("");
+                sections.push(currentSection);
+            }
+
+            // 基本情况:一律进灰色卡片
+            if (currentSection.type === "basic") {
+                const exist = currentSection.cards.find((c) => c.title === item.label);
+                if (exist) {
+                    exist.paragraphs.push(item.content);
+                } else {
+                    currentSection.cards.push({
+                        title: item.label,
+                        paragraphs: [item.content],
+                    });
+                }
+                continue;
             }
-        ],
-    },
-    weatherRisk: {
-        title: "决定产量的气象风险",
-        intro: "飞鸟系统基于当地多年气象与物候窗口,梳理对产量形成影响最大的关键风险,帮助提前布局应对。",
-        groupTitle: "开花坐果期",
-        items: [
-            {
-                tag: "风险判断",
-                content:
-                    "开花坐果期阴雨涝渍年发生概率55.0%,历史强度等级4级,是制约坐果稳定的重要风险,需重点对待。",
-            },
-            {
-                tag: "主要影响",
-                content:
-                    "本物候期尚无花、果器官,研判重点放在叶和梢。高温、强光叠加缺水后,嫩叶失水速度超过根系补水速度,首先表现萎蔫与灼伤。",
-            },
-            {
-                tag: "重点准备",
-                content:
-                    "围绕排水防涝、保温防寒和抗旱灌水提前备好物资与预案,把灾害应对前移到敏感窗口来临之前。",
-            },
-        ],
-        suggest: {
-            title: "飞鸟建议",
-            rows: [
-                {
-                    label: "优先能力:",
-                    content:
-                        "重点提升防灾保果与应急稳产的成套管理能力,尤其是排水防涝、保温防寒和抗旱灌水的机动响应。",
-                },
-                {
-                    label: "管理方向:",
-                    content:
-                        "围绕开花坐果和果实膨大两大敏感窗口,把灾害应对前移到物候来临之前,守住产量形成的稳定期。",
-                },
-            ],
-        },
-    },
-    fertilizer: {
-        title: "肥料供给效率分析",
-        intro: "飞鸟系统结合历史土壤、地形水文和遥感长势数据,进一步分析发现,当地肥料利用效率的主要限制在于酸性黏重土壤的通气缓冲和湿热旱交替下的根系活力波动。",
-        groupTitle: "土壤长期供肥",
-        items: [
-            {
-                tag: "主要问题",
-                content:
-                    "当地为赤红壤黏壤土,pH约5.4偏酸,有机质水平中等,养分缓冲与保蓄能力总体偏弱。",
-            },
-            {
-                tag: "形成原因",
-                content:
-                    "暴雨涝渍年发生概率65.0%,长期强降雨易促使可移动养分淋失,进一步削弱酸性土壤的养分缓冲能力。",
-            },
-            {
-                tag: "生产影响",
-                content:
-                    "养分流失加快、供应节奏不稳,可能影响花芽分化、开花坐果和幼果稳定所需的营养基础。",
-            },
-        ],
-        suggest: {
-            title: "飞鸟建议",
-            rows: [
-                {
-                    label: "优先能力:",
-                    content:
-                        "重点提升排水防涝、土壤改良与分期稳肥的成套管理能力,尤其是酸性黏重土壤的通气缓冲与根系养护。",
-                },
-                {
-                    label: "管理方向:",
-                    content:
-                        "围绕开花坐果和果实膨大两大敏感窗口,把灾害应对前移到物候来临之前,守住产量形成的稳定期。",
-                },
-            ],
-        },
-    },
-});
+
+            if (currentGroup) {
+                currentGroup.items.push({
+                    tag: item.label,
+                    content: item.content,
+                });
+            } else {
+                // risk 章节若列表在 ### 之前,先挂到临时卡片,一般不会出现
+                currentSection.cards.push({
+                    title: item.label,
+                    paragraphs: [item.content],
+                });
+            }
+            continue;
+        }
+
+        if (inIntro && !currentSection) {
+            introLines.push(line);
+        } else if (currentSection && !currentGroup) {
+            currentSection._introLines.push(line);
+        }
+    }
+
+    flushGroup();
+
+    sections.forEach((section) => {
+        section.intro = (section._introLines || []).join("");
+        delete section._introLines;
+        // 再次按标题校正类型,避免误判
+        if (isBasicSectionTitle(section.title)) {
+            section.type = "basic";
+        } else if (section.groups.length || section.suggest) {
+            section.type = "risk";
+        }
+    });
+
+    return {
+        title: title || "初始种植诊断报告",
+        intro: introLines.join(""),
+        sections,
+    };
+};
+
+// ---------------------------------------------------------------------------
+// 接口
+// ---------------------------------------------------------------------------
+const fetchReport = async () => {
+    loading.value = true;
+    reportData.value = null;
+    try {
+        const res = await VE_API.questionnaire.generateReport({
+            region: "宁晋县",
+            crop: "冬小麦"
+        });
+        if (res?.code === 200 && res.data?.report) {
+            reportData.value = parseReportMarkdown(res.data.report);
+        } else {
+            ElMessage.error(res?.msg || "获取诊断报告失败");
+        }
+    } catch (e) {
+        ElMessage.error(e?.response?.data?.msg || e?.message || "获取诊断报告失败");
+    } finally {
+        loading.value = false;
+    }
+};
 
 const handleBack = () => {
     router.back();
@@ -192,10 +303,11 @@ const handleBack = () => {
 const handleShare = () => {
     const query = {
         askInfo: { title: "转发报告", content: "是否分享给好友" },
-        shareText: reportData.value.title,
+        shareText: reportData.value?.title || "",
         targetUrl: "diagnosis_report",
         paramsPage: JSON.stringify({
             id: route.query.id,
+            zone_id: route.query.zone_id || route.query.zoneId || 42,
             fromShare: 1,
         }),
         imageUrl: "https://birdseye-img.sysuimars.com/temp/field.png",
@@ -204,6 +316,10 @@ const handleShare = () => {
         url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
     });
 };
+
+onMounted(() => {
+    fetchReport();
+});
 </script>
 
 <style lang="scss" scoped>
@@ -214,6 +330,13 @@ const handleShare = () => {
     padding-bottom: 90px;
 }
 
+.diagnosis-report-status {
+    padding: 48px 16px;
+    text-align: center;
+    font-size: 14px;
+    color: #86909c;
+}
+
 .diagnosis-report-body {
     padding: 10px 10px 60px;
     max-height: calc(100vh - 40px);
@@ -222,14 +345,14 @@ const handleShare = () => {
 }
 
 .report-hero {
-    padding: 0px 0px 10px;
+    padding: 0 0 10px;
     text-align: center;
 
     &__title {
         font-size: 16px;
         font-weight: bold;
         line-height: 24px;
-        color: #000000;
+        color: #000;
     }
 
     &__desc {
@@ -247,7 +370,7 @@ const handleShare = () => {
     padding: 10px;
     border-radius: 10px;
 
-    &+& {
+    & + & {
         margin-top: 18px;
     }
 
@@ -256,18 +379,24 @@ const handleShare = () => {
         font-size: 16px;
         font-weight: 600;
         line-height: 24px;
-        color: #000000;
+        color: #000;
     }
 }
 
-.mb-10 {
-        margin-bottom: 10px;
+.risk-group {
+    & + & {
+        margin-top: 14px;
     }
+}
+
+.mb-10 {
+    margin-bottom: 10px;
+}
 
 .info-card {
     padding: 10px;
     border-radius: 4px;
-    background: #F7F8FA;
+    background: #f7f8fa;
     box-sizing: border-box;
 
     &.fertilizer-card {
@@ -279,7 +408,7 @@ const handleShare = () => {
         }
     }
 
-    &+& {
+    & + & {
         margin-top: 10px;
     }
 
@@ -288,16 +417,15 @@ const handleShare = () => {
         font-size: 14px;
         font-weight: 500;
         line-height: 22px;
-        color: #1D2129;
+        color: #1d2129;
     }
 
     &__text {
-        // margin-bottom: 10px;
         font-size: 12px;
         line-height: 20px;
-        color: #4E5969;
+        color: #4e5969;
 
-        &+& {
+        & + & {
             margin-top: 2px;
         }
     }
@@ -308,7 +436,7 @@ const handleShare = () => {
     align-items: center;
     gap: 10px;
     margin-bottom: 10px;
-    color: #1D2129;
+    color: #1d2129;
     font-size: 14px;
     font-weight: 500;
     line-height: 20px;
@@ -331,7 +459,7 @@ const handleShare = () => {
     border-radius: 8px;
     background: #f7f8fa;
 
-    &+& {
+    & + & {
         margin-top: 10px;
     }
 
@@ -349,7 +477,7 @@ const handleShare = () => {
         margin-top: 4px;
         font-size: 12px;
         line-height: 20px;
-        color: #4E5969;
+        color: #4e5969;
     }
 }
 
@@ -370,15 +498,15 @@ const handleShare = () => {
     &__row {
         font-size: 12px;
         line-height: 20px;
-        color: #4E5969;
+        color: #4e5969;
 
-        &+& {
+        & + & {
             margin-top: 8px;
         }
     }
 
     &__label {
-        color: #000000;
+        color: #000;
     }
 }
 
@@ -393,11 +521,11 @@ const handleShare = () => {
     box-sizing: border-box;
     padding: 0 30px;
     border-radius: 22px;
-    background: linear-gradient(180deg, #72C1FF 0%, #2199F8 100%);
+    background: linear-gradient(180deg, #72c1ff 0%, #2199f8 100%);
     color: #fff;
     font-size: 14px;
     line-height: 40px;
     text-align: center;
-    box-shadow: 0px 4px 4px 0px rgba(0, 0, 0, 0.1);
+    box-shadow: 0 4px 4px 0 rgba(0, 0, 0, 0.1);
 }
 </style>

+ 4 - 0
src/views/old_mini/growth_report/index.vue

@@ -58,6 +58,9 @@
         />
     </div>
 
+    <!-- 恢复种植弹窗 -->
+    <restore-planting-popup />
+
     <!-- 录入信息邀请弹窗:分享链接带 showInviteEntry 时展示 -->
     <invite-entry-popup ref="invitePopupRef" />
 </template>
@@ -69,6 +72,7 @@ import { useStore } from "vuex";
 import { Close, LocationFilled } from "@element-plus/icons-vue";
 import { convertPointToArray } from "@/utils/index";
 import GrowthReportMap from "./growthReportMap.js";
+import RestorePlantingPopup from "@/components/popup/restorePlantingPopup.vue";
 import CropAlertPanel from "./components/CropAlertPanel.vue";
 import inviteEntryPopup from "@/views/old_mini/entry_information/components/inviteEntryPopup.vue";
 import wx from "weixin-js-sdk";

+ 23 - 10
src/views/old_mini/work_execute/index.vue

@@ -25,8 +25,8 @@
                     :class="{ 'is-risk': item.isHighRisk }"
                 >
                     <div v-if="item.status === 'pending' && item.cornerTip" class="task-card__corner">{{ item.cornerTip }}</div>
-                    <div v-if="item.status === 'completed' && item.cornerTip" class="task-card__corner done-corner" :class="{ 'myself-corner': item.isByMyself }">
-                        <el-icon size="14" class="myself-corner__icon" :class="{ 'myself-corner__icon-success': item.isByMyself }"><SuccessFilled /></el-icon>
+                    <div v-if="item.status === 'completed' && item.cornerTip" class="task-card__corner done-corner">
+                        <el-icon size="14" class="myself-corner__icon"><SuccessFilled /></el-icon>
                         {{ item.cornerTip }}
                     </div>
 
@@ -49,17 +49,18 @@
                     </div>
 
                     <div class="task-card__footer">
-                        <div class="footer-l">
+                        <!-- <div class="footer-l">
                             <img class="footer-l__icon" src="@/assets/img/home/user-icon-fill.png" alt="">
                             外包农事
-                        </div>
+                        </div> -->
                         <div class="footer-r">
-                            <div class="card-btn card-btn--ghost" @click.stop="handleForward(item)">
+                            <!-- <div class="card-btn card-btn--ghost" @click.stop="handleForward(item)">
                                 {{ t("workExecute.forward") }}
+                            </div> -->
+                            <div v-if="item.status === 'completed'" class="card-btn card-btn--secondary" @click.stop="handleViewDetail(item)">
+                                查看详情
                             </div>
-                            <div class="card-btn card-btn--primary" @click.stop="handleViewDetail(item)">
-                                我已完成
-                            </div>
+                            <div v-else class="card-btn card-btn--primary" @click.stop="handleComplete(item)">我已完成</div>
                         </div>
                     </div>
                 </div>
@@ -157,7 +158,7 @@ const taskList = ref([
         isAbnormal: false,
         isWeather: false,
         isByMyself: true,
-        cornerTip: "本人完成",
+        cornerTip: "已完成",
         analysisSummary: "新梢与果实表面未见明显异常斑点",
         areaCode: "D-05 / 全园",
         farmName: "病虫害巡查",
@@ -257,6 +258,11 @@ const handleViewDetail = (item) => {
     });
 };
 
+const handleComplete = (item) => {
+    // TODO: 对接完成农事接口
+    ElMessage.success("农事已完成");
+};
+
 const goCompleteFarmInfo = () => {
     router.push("/entry_information");
 };
@@ -482,7 +488,8 @@ onActivated(() => {
         &__footer {
             display: flex;
             align-items: center;
-            justify-content: space-between;
+            // justify-content: space-between;
+            justify-content: flex-end;
             gap: 12px;
             margin-top: 14px;
             .footer-l {
@@ -526,6 +533,12 @@ onActivated(() => {
                 color: #FFFFFF;
                 padding: 0 16px;
             }
+
+            &--secondary {
+                background: rgba(33, 153, 248, 0.1);
+                color: #2199F8;
+                padding: 0 16px;
+            }
         }
     }