Bläddra i källkod

feat:对接农事规划页面接口和添加逻辑弹窗

wangsisi 1 dag sedan
förälder
incheckning
96291f0ba9

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

@@ -38,4 +38,14 @@ module.exports = {
         url: config.base_dev_url + "container_phenology_sample_files_speak_title/getFarmSpeakInfo",
         type: "get",
     },
+    //根据containerSpaceTimeId查询物候期列表
+    listPhenology: {
+        url: config.base_dev_url + "container_space_time/listPhenologyByContainerSpaceTimeId",
+        type: "get",
+    },
+    //更新农事编排信息
+    updateFarmWorkArrange: {
+        url: config.base_dev_url + "container_farm_work_arrange/update",
+        type: "post",
+    },
 }

BIN
src/assets/img/monitor/popup-header-bg.png


+ 69 - 74
src/components/popup/interactPopup.vue

@@ -1,7 +1,7 @@
 <template>
     <popup class="interact-popup" v-model:show="show" closeable :close-on-click-overlay="false" @closed="handleClosed">
         <div class="interact-header">
-            <div class="interact-title">{{ interactTitle }}</div>
+            <div class="interact-title">{{ currentData.farmWorkName }}</div>
         </div>
         <div class="interact-form">
             <div class="form-item">
@@ -10,8 +10,13 @@
                     请选择互动时间
                 </div>
                 <div class="form-input-wrapper">
-                    <el-select v-model="interactTime" size="large" placeholder="请选择物候期" :editable="false">
-                        <el-option v-for="item in options" :key="item.value" :label="item.label" :value="item.value" />
+                    <el-select v-model="formData.phenologyId" size="large" placeholder="请选择物候期" :editable="false">
+                        <el-option
+                            v-for="item in phenologyList"
+                            :key="item.id"
+                            :label="item.name"
+                            :value="item.id"
+                        ></el-option>
                     </el-select>
                 </div>
             </div>
@@ -22,7 +27,7 @@
                 </div>
                 <div class="form-input-wrapper">
                     <el-date-picker
-                        v-model="forceTriggerTime"
+                        v-model="formData.interactionTime"
                         size="large"
                         style="width: 100%"
                         type="date"
@@ -37,7 +42,7 @@
                     请设置互动问题
                 </div>
                 <el-input
-                    v-model="interactQuestion"
+                    v-model="formData.interactionQuestion"
                     type="textarea"
                     :rows="4"
                     placeholder="请设置互动问题"
@@ -66,72 +71,64 @@ const emit = defineEmits(["handleSaveSuccess", "handleDeleteInteract"]);
 const show = ref(false);
 const arrangeId = ref(null);
 const isSaving = ref(false);
-const interactTitle = ref("梢期杀虫");
-const interactTime = ref("");
-const forceTriggerTime = ref("");
-const interactQuestion = ref("");
-
-const options = [
-    {
-        value: "Option1",
-        label: "Option1",
-    },
-    {
-        value: "Option2",
-        label: "Option2",
-    },
-    {
-        value: "Option3",
-        label: "Option3",
-    },
-    {
-        value: "Option4",
-        label: "Option4",
-    },
-    {
-        value: "Option5",
-        label: "Option5",
-    },
-];
+
+const formData = ref({
+    phenologyId: "",
+    interactionTime: "",
+    interactionQuestion: "",
+});
 
 // 计算属性
 const saveButtonText = computed(() => (isSaving.value ? "保存中..." : "保存修改"));
 
 // 工具函数
 const resetInteractData = () => {
-    interactTime.value = "";
-    forceTriggerTime.value = "";
-    interactQuestion.value = "";
+    formData.value = {
+        phenologyId: "",
+        interactionTime: "",
+        interactionQuestion: "",
+    };
 };
 
 // 验证函数
 const validateInteractForm = () => {
-    if (!interactTime.value) {
+    if (!formData.value.phenologyId) {
         ElMessage.warning("请选择互动时间");
         return false;
     }
-    if (!forceTriggerTime.value) {
+    if (!formData.value.interactionTime) {
         ElMessage.warning("请选择强制触发互动时间");
         return false;
     }
-    if (!interactQuestion.value?.trim()) {
+    if (!formData.value.interactionQuestion?.trim()) {
         ElMessage.warning("请设置互动问题");
         return false;
     }
     return true;
 };
 
+const phenologyList = ref(null);
+const getPhenologyList = async (containerSpaceTimeId) => {
+    if (!containerSpaceTimeId) {
+        phenologyList.value = [];
+        return;
+    }
+    const res = await VE_API.monitor.listPhenology({ containerSpaceTimeId });
+    if (res.code === 0) {
+        phenologyList.value = res.data || [];
+    }
+};
+
+const currentData = ref(null);
 // 显示弹窗方法
-const showPopup = ({ arrangeIdVal, interactTitleVal, interactTimeVal, forceTriggerTimeVal, interactQuestionVal }) => {
+const showPopup = async (data) => {
     // 重置数据
     resetInteractData();
+    await getPhenologyList(data.containerSpaceTimeId);
 
     // 设置数据
-    arrangeId.value = arrangeIdVal;
-    interactTitle.value = interactTitleVal || "梢期杀虫";
-    interactTime.value = interactTimeVal || "";
-    forceTriggerTime.value = forceTriggerTimeVal || "";
-    interactQuestion.value = interactQuestionVal || "";
+    currentData.value = data;
+
     isSaving.value = false;
 
     show.value = true;
@@ -154,44 +151,42 @@ const handleDeleteInteract = () => {
         });
 };
 
+function formatDate(date) {
+    let year = date.getFullYear();
+    let month = String(date.getMonth() + 1).padStart(2, "0");
+    let day = String(date.getDate()).padStart(2, "0");
+    return `${year}-${month}-${day}`;
+}
+
 const handleSaveInteract = () => {
     if (isSaving.value || !validateInteractForm()) return;
 
     isSaving.value = true;
 
     const paramsObj = {
-        arrangeId: arrangeId.value,
-        interactTime: interactTime.value,
-        forceTriggerTime: forceTriggerTime.value,
-        interactQuestion: interactQuestion.value.trim(),
+        id: currentData.value.id,
+        ...formData.value,
+        interactionTime: formatDate(formData.value.interactionTime),
     };
 
-    // TODO: 调用保存互动设置的API
-    // VE_API.monitor.saveInteractSetting(paramsObj)
-    //     .then((res) => {
-    //         if (res.code === 0) {
-    //             ElMessage.success("保存成功");
-    //             show.value = false;
-    //             emit("handleSaveSuccess", paramsObj);
-    //         } else {
-    //             ElMessage.error(res.message || "保存失败");
-    //         }
-    //     })
-    //     .catch((error) => {
-    //         console.error("保存互动设置失败:", error);
-    //         ElMessage.error("保存失败,请重试");
-    //     })
-    //     .finally(() => {
-    //         isSaving.value = false;
-    //     });
-
-    // 临时模拟保存成功
-    setTimeout(() => {
-        ElMessage.success("保存成功");
-        show.value = false;
-        emit("handleSaveSuccess", paramsObj);
-        isSaving.value = false;
-    }, 500);
+    VE_API.monitor
+        .updateFarmWorkArrange(paramsObj)
+        .then((res) => {
+            if (res.code === 0) {
+                ElMessage.success("保存成功");
+                show.value = false;
+                emit("handleSaveSuccess", paramsObj);
+            } else {
+                ElMessage.error(res.message || "保存失败");
+            }
+        })
+        .catch((error) => {
+            console.error("保存互动设置失败:", error);
+            ElMessage.error("保存失败,请重试");
+        })
+        .finally(() => {
+            isSaving.value = false;
+        });
 };
 
 const handleClosed = () => {

+ 46 - 50
src/views/old_mini/modify_work/modify.vue

@@ -12,7 +12,7 @@
             >
                 <div class="farm-card">
                     <div class="card-title between">
-                        <div>{{ detailData?.farmWorkName }}<span class="type-tag">标准农事</span></div>
+                        <div>{{ detailData?.name }}<span class="type-tag">标准农事</span></div>
                         <el-popover
                             title=""
                             v-if="isEdit"
@@ -91,7 +91,7 @@
                         <!-- <span class="del-tag">删除互动</span> -->
                     </div>
                     <div class="interact-content">
-                        温馨提示:在某某物候期之后,请密切关注荔枝,关注蒂蛀虫的出现!
+                        {{ detailData?.warmReminder }}
                         <span class="edit-tag" @click="handleEditInteract(detailData)">点击编辑</span>
                     </div>
                 </div>
@@ -438,19 +438,19 @@
                             </div>
                             <div
                                 class="new-table-wrap"
-                                v-for="(prescriptionItem, prescriptionI) in detailData?.prescriptionList"
+                                v-for="(subP, prescriptionI) in detailData?.prescriptionList"
                                 :key="prescriptionI"
                             >
-                                <div
+                                <!-- <div
                                     class="new-prescription"
                                     v-for="(subP, subI) in prescriptionItem.pesticideFertilizerList"
                                     :key="subI"
-                                >
+                                > -->
                                     <div class="new-table">
                                         <div class="line-l">
                                             <div class="line-1 title-1">{{ subP.typeName }}</div>
                                             <div class="line-2">
-                                                {{ subP.defaultName || subP.pesticideFertilizerName }}
+                                                {{ subP.name || subP.pesticideFertilizerName }}
                                             </div>
                                         </div>
                                         <div class="line-r">
@@ -459,19 +459,10 @@
                                                 <div class="sub-line title-4">{{ subP.ratio }}ML</div>
                                                 <div class="sub-line title-5">{{ subP.muUsage }}{{ subP.unit }}</div>
                                             </div>
-                                            <!-- <div class="line-4" v-if="detailData?.usageMode === '叶面施'">
-                                                <div class="sub-line title-3 execute-line">无人机</div>
-                                                <div class="sub-line title-4">
-                                                    {{ subP.ratio2 ? subP.ratio2 + subP.unit : "---" }}
-                                                </div>
-                                                <div class="sub-line title-5">
-                                                    {{ subP.muUsage2 ? subP.muUsage2 + subP.unit : "---" }}
-                                                </div>
-                                            </div> -->
                                         </div>
                                     </div>
                                     <div class="note-text" v-if="subP.remark">{{ subP.remark }}</div>
-                                </div>
+                                <!-- </div> -->
                             </div>
                         </div>
                     </div>
@@ -579,8 +570,12 @@ const isEdit = ref(false);
 onActivated(() => {
     const id = route.query.id;
     isEdit.value = route.query.isEdit ? true : false;
-    if (id) {
-        getDetail(id);
+    // if (id) {
+    //     getDetail(id);
+    // }
+    if (route.query.farmWorkId) {
+        // getFarmWorkDetail(route.query.farmWorkId);
+        getDetail(route.query.farmWorkId);
     }
     window.scrollTo(0, 0);
     if (route.query.data) {
@@ -604,6 +599,15 @@ onActivated(() => {
     }));
 });
 
+const farmWorkDetail = ref({});
+// 获取农事详情
+const getFarmWorkDetail = async (farmWorkId) => {
+    const { data, code } = await VE_API.farm.getFarmWorkLib({ id: farmWorkId });
+    if (code === 0) {
+        detailData.value = data || {};
+    }
+};
+
 const priceSheetPopupRef = ref(null);
 const showPriceSheetPopup = () => {
     priceSheetPopupRef.value.handleShowPopup(detailData.value);
@@ -611,25 +615,25 @@ const showPriceSheetPopup = () => {
 
 const detailData = ref({});
 const getDetail = async (id) => {
-    const { data } = await VE_API.z_farm_work_record.getDetail({ id });
-    const res = data[0];
-    detailData.value = res;
-    dynamicValidateForm.executeDate = res.executeDate;
-    dynamicValidateForm.usageMode = res.usageMode;
-
-    res.prescriptionList.forEach((item) => {
-        item.pesticideFertilizerList.forEach((pesticide) => {
-            pesticide.executionMethod = pesticide.executionMethod || 2; // 默认人工
-            pesticide.typeName = item.name;
-        });
-    });
-
-    dynamicValidateForm.prescriptionList = res.prescriptionList;
-    servicePricePerMu.value = detailData.value.farmWorkServiceCost || null;
-
-    getFarmWorkArrangeDetail(res.farmWorkArrangeId);
-
-    getQuotationData();
+    // const { data } = await VE_API.z_farm_work_record.getDetail({ id });
+    const { data, code } = await VE_API.farm.getFarmWorkLib({ id });
+    if(code === 0) {
+        detailData.value = data;
+        dynamicValidateForm.executeDate = data.executeDate;
+        dynamicValidateForm.usageMode = data.usageMode;
+        data.prescriptionList = data.prescription?.pesticideFertilizerList || [];
+    
+        // data.prescription?.pesticideFertilizerList.forEach((item) => {
+        //     // item.pesticideFertilizerList.forEach((pesticide) => {
+        //         item.executionMethod = item.executionMethod || 2; // 默认人工
+        //     // });
+        // });
+    
+        dynamicValidateForm.prescriptionList = data.prescriptionList;
+        servicePricePerMu.value = detailData.value.farmWorkServiceCost || null;
+    
+        getQuotationData();
+    }
 };
 
 const toEditPrescription = () => {
@@ -697,14 +701,6 @@ const getQuotationData = async () => {
     }
 };
 
-// 获取农场现状
-const farmStatusText = ref("");
-const getFarmWorkArrangeDetail = (id) => {
-    VE_API.farm.getFarmWorkArrangeDetail({ id }).then(({ data }) => {
-        farmStatusText.value = data.farmStatus;
-    });
-};
-
 // 根据执行方式获取单亩用量:1=无人机用muUsage2,2=人工用muUsage
 const getMuUsage = (pesticide) => {
     if (!pesticide) return 0;
@@ -1283,7 +1279,7 @@ const handleEditInteract = (item) => {
             .new-prescription + .new-prescription {
                 border-top: 1px solid rgba(225, 225, 225, 0.8);
             }
-            .new-prescription {
+            // .new-prescription {
                 .new-table {
                     display: flex;
                     align-items: center;
@@ -1330,10 +1326,10 @@ const handleEditInteract = (item) => {
                     text-align: left;
                     font-size: 11px;
                 }
-            }
-            .new-prescription + .new-prescription {
-                padding-top: 8px;
-            }
+            // }
+            // .new-prescription + .new-prescription {
+            //     padding-top: 8px;
+            // }
         }
     }
 

+ 230 - 38
src/views/old_mini/monitor/subPages/plan.vue

@@ -8,7 +8,11 @@
                 </el-select>
                 <tab-list type="light" v-model="active" :tabs="tabs" @change="handleTabChange" />
             </div>
-            <div class="timeline-container" ref="timelineContainerRef" :class="{ 'timeline-container-plant': pageType === 'plant' }">
+            <div
+                class="timeline-container"
+                ref="timelineContainerRef"
+                :class="{ 'timeline-container-plant': pageType === 'plant' }"
+            >
                 <div class="timeline-list" :style="getListStyle">
                     <div class="timeline-middle-line"></div>
                     <!-- 物候期覆盖条(progress 为起点,progress2 为终点,单位 %) -->
@@ -52,7 +56,7 @@
                                             <div class="header-right">托管农事</div>
                                         </div>
                                         <div class="card-content">
-                                            <span>温馨提示:在某某物候期之后,请密切关注荔枝,关注蒂蛀虫的出现!</span>
+                                            <span>{{ fw.warmReminder || "暂无提示" }}</span>
                                             <span class="edit-link" @click.stop="handleEdit(fw)">点击编辑</span>
                                         </div>
                                         <div
@@ -85,8 +89,13 @@
                 </div>
             </div>
         </div>
-        <div class="custom-bottom-fixed-btns" :class="{ 'center': pageType !== 'plant' }">
-            <div class="bottom-btn secondary-btn" v-if="pageType === 'plant'">{{ active === 1 ? '复制为新方案' : '编辑方案' }}</div>
+        <div class="custom-bottom-fixed-btns">
+            <div class="bottom-btn-group">
+                <div class="bottom-btn secondary-btn" @click="handlePhenologySetting">物候期设置</div>
+                <div class="bottom-btn secondary-btn" v-if="pageType === 'plant'" @click="openCopyPlanPopup">
+                    {{ active === 1 ? "复制方案" : "方案设置" }}
+                </div>
+            </div>
             <div class="bottom-btn primary-btn" @click="addNewTask">新增农事</div>
         </div>
     </div>
@@ -98,10 +107,56 @@
         @handleSaveSuccess="getFarmWorkPlan"
         @handleDeleteInteract="handleDeleteInteract"
     ></interact-popup>
+    <!-- 复制方案弹窗 -->
+    <Popup v-model:show="showCopyPlan" class="copy-plan-popup" round closeable :close-on-click-overlay="false">
+        <div class="copy-plan-content">
+            <div class="label">{{ active === 1 ? "复制为" : "方案名称" }}</div>
+            <el-input v-model="copyPlanName" size="large" placeholder="请输入方案名称" class="copy-plan-input" />
+        </div>
+        <div class="copy-plan-footer">
+            <div class="btn btn-cancel" @click="handleCancelCopyPlan">{{ active === 1 ? "取消复制" : "删除方案" }}</div>
+            <div class="btn btn-confirm" @click="handleConfirmCopyPlan">
+                {{ active === 1 ? "确定复制" : "确定设置" }}
+            </div>
+        </div>
+    </Popup>
+    <!-- 物候期设置弹窗 -->
+    <Popup
+        v-model:show="showPhenologySetting"
+        class="copy-plan-popup phenology-popup"
+        round
+        closeable
+        :close-on-click-overlay="false"
+    >
+        <div class="phenology-header">物候期时间设置</div>
+        <div class="phenology-list">
+            <div
+                class="phenology-item"
+                v-for="(item, index) in mergedReproductiveList"
+                :key="item.id || index"
+            >
+                <div class="item-label">
+                    <span class="label-text">{{ item.name }}</span>
+                    <span>起始时间</span>
+                </div>
+                <div class="item-value">
+                    <el-date-picker
+                        style="width: 100%"
+                        size="large"
+                        v-model="item.startDate"
+                        type="date"
+                        placeholder="选择日期"
+                    />
+                </div>
+            </div>
+        </div>
+        <div class="phenology-footer">确认设置</div>
+    </Popup>
 </template>
 
 <script setup>
 import { ref, onMounted, computed, nextTick } from "vue";
+import { Popup } from "vant";
 import customHeader from "@/components/customHeader.vue";
 import tabList from "@/components/pageComponents/TabList.vue";
 import { useRouter, useRoute } from "vue-router";
@@ -167,6 +222,44 @@ onMounted(() => {
     getFarmWorkPlan();
 });
 
+const mergedReproductiveList = ref([])
+const getPhenologyList = async (containerSpaceTimeId) => {
+    const res = await VE_API.monitor.listPhenology({ containerSpaceTimeId ,farmId: route.query.farmId });
+    if (res.code === 0) {
+        mergedReproductiveList.value = res.data || [];
+    }
+};
+
+// 复制方案弹窗相关
+const showCopyPlan = ref(false);
+const showPhenologySetting = ref(false);
+const copyPlanName = ref("");
+const openCopyPlanPopup = () => {
+    copyPlanName.value = "";
+    showCopyPlan.value = true;
+};
+
+// 物候期设置弹窗
+const handlePhenologySetting = () => {
+    showPhenologySetting.value = true;
+};
+
+// 取消复制方案
+const handleCancelCopyPlan = () => {
+    showCopyPlan.value = false;
+};
+
+// 确定复制方案
+const handleConfirmCopyPlan = () => {
+    if (!copyPlanName.value.trim()) {
+        ElMessage.warning("请输入方案名称");
+        return;
+    }
+    // TODO: 在此处调用复制方案的接口
+    ElMessage.success("复制成功");
+    showCopyPlan.value = false;
+};
+
 const getFarmWorkPlan = () => {
     // 如果不是首次加载,保存当前滚动位置
     let savedScrollTop = 0;
@@ -179,6 +272,7 @@ const getFarmWorkPlan = () => {
         .then(({ data, code }) => {
             if (code === 0) {
                 const list = Array.isArray(data?.solarTermsList) ? data.solarTermsList : [];
+                getPhenologyList(data.phenologyList[0]?.containerSpaceTimeId)
                 const filtered = list
                     .filter((t) => t && t.type === 1)
                     .map((t) => ({
@@ -194,16 +288,33 @@ const getFarmWorkPlan = () => {
                 solarTerms.value = filtered;
                 // 物候期数据
                 phenologyList.value = Array.isArray(data?.phenologyList)
-                    ? data.phenologyList.map((it) => ({
-                          id: it.id ?? it.phenologyId ?? it.name ?? `${it.progress}-${it.progress2}`,
-                          progress: Number(it.progress) || 0, // 起点 %
-                          progress2: Number(it.progress2) || 0, // 终点 %
-                          // 兼容多种可能的开始时间字段
-                          startTimeMs: safeParseDate(
-                              it.startDate || it.beginDate || it.startTime || it.start || it.start_at
-                          ),
-                          reproductiveList: Array.isArray(it.reproductiveList) ? it.reproductiveList : [],
-                      }))
+                    ? data.phenologyList.map((it) => {
+                          const reproductiveList = Array.isArray(it.reproductiveList)
+                              ? it.reproductiveList.map((r) => {
+                                    const farmWorkArrangeList = Array.isArray(r.farmWorkArrangeList)
+                                        ? r.farmWorkArrangeList.map((fw) => ({
+                                              ...fw,
+                                              containerSpaceTimeId: it.containerSpaceTimeId,
+                                          }))
+                                        : [];
+                                    return {
+                                        ...r,
+                                        farmWorkArrangeList,
+                                    };
+                                })
+                              : [];
+
+                          return {
+                              id: it.id ?? it.phenologyId ?? it.name ?? `${it.progress}-${it.progress2}`,
+                              progress: Number(it.progress) || 0, // 起点 %
+                              progress2: Number(it.progress2) || 0, // 终点 %
+                              // 兼容多种可能的开始时间字段
+                              startTimeMs: safeParseDate(
+                                  it.startDate || it.beginDate || it.startTime || it.start || it.start_at
+                              ),
+                              reproductiveList,
+                          };
+                      })
                     : [];
 
                 // 等待 DOM 更新后处理滚动
@@ -246,21 +357,16 @@ const handleRowClick = (item) => {
     curFarmObj.value = item;
     router.push({
         path: "/modify",
-        query: { id: 277983 },
+        query: { id: 277983,farmWorkId: item.farmWorkId },
     });
 };
 
 const interactPopupRef = ref(null);
 
 const handleEdit = (item) => {
+    console.log(item);
     if (interactPopupRef.value) {
-        interactPopupRef.value.showPopup({
-            arrangeIdVal: item.id,
-            interactTitleVal: item.farmWorkName || "梢期杀虫", // 使用农事名称作为标题
-            interactTimeVal: item.interactTime || "", // 如果有已保存的互动时间
-            forceTriggerTimeVal: item.forceTriggerTime || "", // 如果有已保存的强制触发时间
-            interactQuestionVal: item.interactQuestion, // 如果有已保存的互动问题
-        });
+        interactPopupRef.value.showPopup(item);
     }
 };
 
@@ -306,7 +412,7 @@ const getListStyle = computed(() => {
     const minP = minProgress.value;
     const maxP = maxProgress.value;
     const range = Math.max(1, maxP - minP); // 避免除0
-    const total = (solarTerms.value?.length || 0) * 450;
+    const total = (solarTerms.value?.length || 0) * 420;
     const minH = total; // 无上下留白
     return { minHeight: `${minH}px` };
 });
@@ -316,7 +422,7 @@ const getTermStyle = (t) => {
     const minP = minProgress.value;
     const maxP = maxProgress.value;
     const range = Math.max(1, maxP - minP); // 避免除0
-    const total = (solarTerms.value?.length || 0) * 450;
+    const total = (solarTerms.value?.length || 0) * 420;
     // 将progress映射到0开始的位置,最小progress对应top: 0
     const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
     const top = (normalizedP / 100) * total;
@@ -347,7 +453,7 @@ const handleSeasonClick = (seasonValue) => {
     const minP = minProgress.value;
     const maxP = maxProgress.value;
     const range = Math.max(1, maxP - minP);
-    const total = (solarTerms.value?.length || 0) * 450;
+    const total = (solarTerms.value?.length || 0) * 420;
     const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
     const targetTop = (normalizedP / 100) * total; // 内容内的像素位置
     const wrap = timelineContainerRef.value;
@@ -369,7 +475,7 @@ const getPhenologyBarStyle = (item) => {
     const minP = minProgress.value;
     const maxP = maxProgress.value;
     const range = Math.max(1, maxP - minP);
-    const total = (solarTerms.value?.length || 0) * 450; // 有效绘制区高度(px)
+    const total = (solarTerms.value?.length || 0) * 420; // 有效绘制区高度(px)
     // 将progress映射到0开始的位置
     const normalizedStart = range > 0 ? ((start - minP) / range) * 100 : 0;
     const normalizedEnd = range > 0 ? ((end - minP) / range) * 100 : 0;
@@ -435,7 +541,7 @@ const getPhenologyBarHeight = (item) => {
     const minP = minProgress.value;
     const maxP = maxProgress.value;
     const range = Math.max(1, maxP - minP);
-    const total = (solarTerms.value?.length || 0) * 450;
+    const total = (solarTerms.value?.length || 0) * 420;
     // 将progress映射到0开始的位置
     const normalizedStart = range > 0 ? ((start - minP) / range) * 100 : 0;
     const normalizedEnd = range > 0 ? ((end - minP) / range) * 100 : 0;
@@ -466,7 +572,7 @@ const getReproductiveItemHeight = (phenologyItem) => {
             gap: 12px;
             margin-bottom: 10px;
             margin-left: 12px;
-            .select-item{
+            .select-item {
                 width: 82px;
                 ::v-deep {
                     .el-select__wrapper {
@@ -477,14 +583,14 @@ const getReproductiveItemHeight = (phenologyItem) => {
                 }
             }
         }
-        
+
         .timeline-container {
             height: calc(100vh - 40px - 73px);
             overflow: auto;
             position: relative;
             box-sizing: border-box;
             padding: 0 12px;
-            &.timeline-container-plant{
+            &.timeline-container-plant {
                 height: calc(100vh - 40px - 73px - 38px);
             }
             .timeline-list {
@@ -538,7 +644,7 @@ const getReproductiveItemHeight = (phenologyItem) => {
                     }
                     .arranges {
                         position: absolute;
-                        left: 48px; /* 列与中线右侧一段距离 */
+                        left: 40px; /* 列与中线右侧一段距离 */
                         top: 0;
                         z-index: 3;
                         display: flex;
@@ -546,7 +652,7 @@ const getReproductiveItemHeight = (phenologyItem) => {
                         gap: 12px;
                         letter-spacing: 0px;
                         .arrange-card {
-                            width: 94%;
+                            width: 97%;
                             border: 0.5px solid #2199f8;
                             border-radius: 8px;
                             background: #fff;
@@ -590,6 +696,7 @@ const getReproductiveItemHeight = (phenologyItem) => {
                                 margin: 4px 0 2px 0;
                                 .edit-link {
                                     color: #2199f8;
+                                    margin-left: 5px;
                                 }
                             }
                             .status-icon {
@@ -663,16 +770,101 @@ const getReproductiveItemHeight = (phenologyItem) => {
     }
     // 控制区域样式
     .custom-bottom-fixed-btns {
-        .bottom-btn {
-            width: 124px;
+        .bottom-btn-group {
+            display: flex;
+            gap: 12px;
+        }
+    }
+}
+.copy-plan-popup {
+    width: 100%;
+    padding: 50px 12px 20px 12px;
+    &::before {
+        content: "";
+        position: absolute;
+        top: 0;
+        left: 0;
+        width: 100%;
+        height: 136px;
+        background: url("@/assets/img/monitor/popup-header-bg.png") no-repeat center center / 100% 100%;
+    }
+    .copy-plan-content {
+        display: flex;
+        align-items: center;
+        gap: 12px;
+        .label {
+            font-size: 16px;
+            font-weight: 500;
+        }
+        .copy-plan-input {
+            width: calc(100% - 80px);
+        }
+    }
+    .copy-plan-footer {
+        display: flex;
+        gap: 12px;
+        margin-top: 20px;
+        .btn {
+            flex: 1;
+            color: #666666;
+            border: 1px solid #999999;
+            border-radius: 25px;
             padding: 10px 0;
+            font-size: 16px;
+            text-align: center;
+            &.btn-confirm {
+                color: #fff;
+                border: 1px solid #2199f8;
+                background: #2199f8;
+            }
         }
-        &.center{
-            justify-content: center;
-            .bottom-btn {
-                padding: 10px 50px;
+    }
+}
+
+.phenology-popup {
+    padding: 28px 20px 20px;
+    .phenology-header {
+        font-size: 24px;
+        text-align: center;
+        margin-bottom: 20px;
+        font-family: "PangMenZhengDao";
+    }
+    .phenology-list {
+        width: 100%;
+        .phenology-item {
+            width: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            .item-label {
+                display: flex;
+                align-items: center;
+                gap: 4px;
+                font-size: 15px;
+                color: rgba(0, 0, 0, 0.4);
+                .label-text {
+                    color: #000;
+                    font-size: 16px;
+                    font-weight: 500;
+                }
+            }
+            .item-value {
+                width: calc(100% - 156px);
             }
         }
+        .phenology-item + .phenology-item {
+            margin-top: 10px;
+        }
+    }
+    .phenology-footer{
+        width: 100%;
+        text-align: center;
+        font-size: 16px;
+        margin-top: 20px;
+        color: #fff;
+        background: #2199f8;
+        border-radius: 25px;
+        padding: 10px 0;
     }
 }
 </style>