Просмотр исходного кода

feat:修改农事规划样式和显示逻辑

wangsisi 1 неделя назад
Родитель
Сommit
806f5c94e1

+ 217 - 73
src/components/pageComponents/FarmWorkPlanTimeline copy.vue

@@ -39,7 +39,7 @@
                             >
                                 <div class="card-header">
                                     <div class="header-left">
-                                        <span class="farm-work-name">{{ fw.farmWorkName || "农事名称" }}</span>
+                                        <span class="farm-work-name">{{ fw.farmWorkName || "--" }}</span>
                                         <span class="tag-standard">标准农事</span>
                                     </div>
                                     <div class="header-right">
@@ -52,25 +52,6 @@
                                         >点击编辑</span
                                     >
                                 </div>
-                                <div
-                                    v-if="
-                                        getArrangeStatusClass(fw) === 'status-complete' ||
-                                        getArrangeStatusClass(fw) === 'status-warning'
-                                    "
-                                    class="status-icon"
-                                    :class="getArrangeStatusClass(fw)"
-                                >
-                                    <el-icon
-                                        v-if="getArrangeStatusClass(fw) === 'status-complete'"
-                                        size="16"
-                                        color="#1CA900"
-                                    >
-                                        <SuccessFilled />
-                                    </el-icon>
-                                    <el-icon v-else size="18" color="#FF953D">
-                                        <WarnTriangleFilled />
-                                    </el-icon>
-                                </div>
                             </div>
                         </div>
                     </div>
@@ -162,31 +143,214 @@ const maxProgress = computed(() => {
     return progresses.length > 0 ? Math.max(...progresses) : 100;
 });
 
+// 计算所有节气的调整位置,确保相邻节气之间至少有36px间隔
+const adjustedTermPositions = computed(() => {
+    if (!solarTerms.value || solarTerms.value.length === 0) return new Map();
+
+    const minP = minProgress.value;
+    const maxP = maxProgress.value;
+    const range = Math.max(1, maxP - minP);
+    const total = calculateTotalHeightByFarmWorks();
+    const termHeight = 46;
+    const minSpacing = 36; // 最小间隔36px
+
+    // 计算所有节气的初始位置
+    const termsWithPositions = solarTerms.value.map((term) => {
+        const p = Math.max(0, Math.min(100, Number(term?.progress) || 0));
+        const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
+        const originalTop = (normalizedP / 100) * total;
+        return {
+            ...term,
+            id:
+                term.id ??
+                term.solarTermsId ??
+                term.termId ??
+                `${term.name || term.solarTermsName || term.termName || "term"}-${term.createDate || ""}`,
+            progress: p,
+            originalTop,
+        };
+    });
+
+    // 按原始位置排序
+    const sortedTerms = [...termsWithPositions].sort((a, b) => a.originalTop - b.originalTop);
+
+    // 调整位置,确保相邻节气之间至少有36px间隔
+    const adjustedPositions = new Map();
+    const termPositions = new Array(sortedTerms.length);
+
+    // 先确定最后一个节气的位置(与物候期底部对齐)
+    const lastIndex = sortedTerms.length - 1;
+    const lastTerm = sortedTerms[lastIndex];
+    if (lastTerm.progress === maxP && range > 0) {
+        termPositions[lastIndex] = total - termHeight;
+    } else {
+        termPositions[lastIndex] = lastTerm.originalTop;
+    }
+    adjustedPositions.set(lastTerm.id, termPositions[lastIndex]);
+
+    // 从后往前调整,确保相邻节气之间至少有36px间隔
+    for (let i = lastIndex - 1; i >= 0; i--) {
+        const term = sortedTerms[i];
+        let adjustedTop = term.originalTop;
+
+        // 检查与后一个节气的间隔
+        const nextTop = termPositions[i + 1];
+        if (nextTop - adjustedTop < minSpacing) {
+            adjustedTop = nextTop - minSpacing;
+        }
+
+        termPositions[i] = adjustedTop;
+        adjustedPositions.set(term.id, adjustedTop);
+    }
+
+    return adjustedPositions;
+});
+
+// 计算物候期需要的实际高度(基于农事数量)
+const getPhenologyRequiredHeight = (item) => {
+    // 统计该物候期内的农事数量
+    let farmWorkCount = 0;
+
+    if (Array.isArray(item.reproductiveList)) {
+        item.reproductiveList.forEach((reproductive) => {
+            if (Array.isArray(reproductive.farmWorkArrangeList)) {
+                farmWorkCount += reproductive.farmWorkArrangeList.length;
+            }
+        });
+    }
+
+    // 如果没有农事,给一个最小高度
+    if (farmWorkCount === 0) {
+        return 50; // 最小50px
+    }
+
+    // 每个农事卡片的高度(根据实际内容,卡片高度可能因内容而异)
+    // 卡片包含:padding(8px*2) + header(约25px) + content margin(4px+2px) + content(约25-30px) = 约72-77px
+    // 考虑到内容可能换行,实际高度可能更高,设置为120px更安全,避免卡片重叠
+    const farmWorkCardHeight = 120; // 卡片高度估算,确保能容纳内容且不重叠
+    // 卡片之间的间距(与CSS中的gap保持一致)
+    const cardGap = 12;
+
+    // 计算总高度:卡片数量 * 卡片高度 + (卡片数量 - 1) * 间距
+    // 如果有多个卡片,需要加上它们之间的间距
+    const totalHeight = farmWorkCount * farmWorkCardHeight + (farmWorkCount > 1 ? (farmWorkCount - 1) * cardGap : 0);
+
+    // 返回精确的总高度,只保留最小高度限制,不添加额外余量
+    return Math.max(totalHeight, 50); // 最小50px,精确匹配农事卡片高度
+};
+
+// 计算所有物候期的累积位置和总高度
+const calculatePhenologyPositions = () => {
+    let currentTop = 10; // 起始位置,留出顶部间距
+    const positions = new Map();
+
+    // 按progress排序物候期,确保按时间顺序排列
+    const sortedPhenologyList = [...phenologyList.value].sort((a, b) => {
+        const aProgress = Math.min(Number(a?.progress) || 0, Number(a?.progress2) || 0);
+        const bProgress = Math.min(Number(b?.progress) || 0, Number(b?.progress2) || 0);
+        return aProgress - bProgress;
+    });
+
+    sortedPhenologyList.forEach((phenology) => {
+        const height = getPhenologyRequiredHeight(phenology);
+        // 使用与数据生成时相同的ID生成逻辑
+        const itemId =
+            phenology.id ?? phenology.phenologyId ?? phenology.name ?? `${phenology.progress}-${phenology.progress2}`;
+        positions.set(itemId, {
+            top: currentTop,
+            height: height,
+        });
+        currentTop += height; // 紧挨着下一个物候期,不留间距
+    });
+
+    return {
+        positions,
+        totalHeight: currentTop, // 总高度 = 最后一个物候期的底部位置,不添加额外间距
+    };
+};
+
+// 计算所有农事的总高度(基于物候期紧挨排列)
+const calculateTotalHeightByFarmWorks = () => {
+    const { totalHeight } = calculatePhenologyPositions();
+
+    // 计算最后一个物候期的底部位置
+    let lastPhenologyBottom = 0;
+    if (phenologyList.value && phenologyList.value.length > 0) {
+        const sortedPhenologyList = [...phenologyList.value].sort((a, b) => {
+            const aProgress = Math.min(Number(a?.progress) || 0, Number(a?.progress2) || 0);
+            const bProgress = Math.min(Number(b?.progress) || 0, Number(b?.progress2) || 0);
+            return aProgress - bProgress;
+        });
+
+        let currentTop = 10; // 起始位置
+        sortedPhenologyList.forEach((phenology) => {
+            const height = getPhenologyRequiredHeight(phenology);
+            currentTop += height;
+        });
+        lastPhenologyBottom = currentTop; // 最后一个物候期的底部位置
+    }
+
+    // 直接使用最后一个物候期的底部位置作为总高度,不添加额外余量
+    // 在getTermStyle中,我们已经调整了最后一个节气的top位置(total - 46)
+    // 这样最后一个节气的底部(total - 46 + 46 = total)就能与物候期底部对齐
+    if (lastPhenologyBottom > 0) {
+        const baseHeight = (solarTerms.value?.length || 0) * 50;
+        // 总高度 = 最后一个物候期的底部位置,精确匹配,不添加额外余量
+        return Math.max(lastPhenologyBottom, totalHeight, baseHeight);
+    }
+
+    // 基础高度:每个节气至少需要一定高度,确保节气标签能显示
+    const baseHeight = (solarTerms.value?.length || 0) * 50;
+
+    // 返回物候期总高度和基础高度的较大值,不添加最小高度限制
+    return Math.max(totalHeight, baseHeight);
+};
+
 // 列表高度
 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) * 1200;
+    const total = calculateTotalHeightByFarmWorks();
     const minH = range === 0 ? 0 : total;
     return { minHeight: `${minH}px` };
 });
 
 const getTermStyle = (t) => {
-    const p = Math.max(0, Math.min(100, Number(t?.progress) || 0));
-    const minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP); // 避免除0
-    const total = (solarTerms.value?.length || 0) * 1200;
-    // 将progress映射到0开始的位置,最小progress对应top: 0
-    const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
-    const top = (normalizedP / 100) * total;
+    // 生成与 adjustedTermPositions 中相同的 ID
+    const termId =
+        t.id ??
+        t.solarTermsId ??
+        t.termId ??
+        `${t.name || t.solarTermsName || t.termName || "term"}-${t.createDate || ""}`;
+
+    // 从调整后的位置映射中获取 top 值
+    const adjustedTop = adjustedTermPositions.value.get(termId);
+
+    // 如果找不到调整后的位置,使用原始计算方式作为后备
+    let top = adjustedTop;
+    if (adjustedTop === undefined) {
+        const p = Math.max(0, Math.min(100, Number(t?.progress) || 0));
+        const minP = minProgress.value;
+        const maxP = maxProgress.value;
+        const range = Math.max(1, maxP - minP);
+        const total = calculateTotalHeightByFarmWorks();
+        const termHeight = 46;
+        const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
+        top = (normalizedP / 100) * total;
+
+        // 如果是最后一个节气,调整top位置
+        if (p === maxP && range > 0) {
+            top = total - termHeight;
+        }
+    }
+
     return {
         position: "absolute",
         top: `${top}px`,
         left: 0,
         width: "30px",
-        height: "20px",
+        // height: "20px",
         display: "flex",
         alignItems: "flex-start",
     };
@@ -208,7 +372,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) * 1200;
+    const total = calculateTotalHeightByFarmWorks(); // 使用动态计算的总高度
     const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
     const targetTop = (normalizedP / 100) * total; // 内容内的像素位置
     const wrap = timelineContainerRef.value;
@@ -223,41 +387,28 @@ const handleSeasonClick = (seasonValue) => {
 
 // 物候期覆盖条样式
 const getPhenologyBarStyle = (item) => {
-    const p1 = Math.max(0, Math.min(100, Number(item?.progress) || 0));
-    const p2 = Math.max(0, Math.min(100, Number(item?.progress2) || 0));
-    const start = Math.min(p1, p2);
-    const end = Math.max(p1, p2);
-    const minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP);
-    const total = (solarTerms.value?.length || 0) * 1200; // 有效绘制区高度(px)
-    // 将progress映射到0开始的位置
-    const normalizedStart = range > 0 ? ((start - minP) / range) * 100 : 0;
-    const normalizedEnd = range > 0 ? ((end - minP) / range) * 100 : 0;
-    let topPx = (normalizedStart / 100) * total;
-    let heightPx = Math.max(2, ((normalizedEnd - normalizedStart) / 100) * total);
-
-    // 顶部对齐
-    const firstTermTop = 0;
-    const minTop = firstTermTop + 10;
-    if (topPx < minTop) {
-        const diff = minTop - topPx;
-        topPx = minTop;
-        heightPx = Math.max(2, heightPx - diff);
-    }
+    const { positions } = calculatePhenologyPositions();
+    // 使用与数据生成时相同的ID生成逻辑
+    const itemId = item.id ?? item.phenologyId ?? item.name ?? `${item.progress}-${item.progress2}`;
+    const position = positions.get(itemId);
+
+    // 如果找不到位置信息,使用默认值
+    let topPx = 10;
+    let heightPx = 50;
 
-    // 底部对齐
-    const lastTermTop = (100 / 100) * total;
-    const maxBottom = lastTermTop + 35;
-    const barBottom = topPx + heightPx;
-    if (barBottom > maxBottom) {
-        heightPx = Math.max(2, maxBottom - topPx);
+    if (position) {
+        topPx = position.top;
+        heightPx = position.height;
     }
 
+    const p1 = Math.max(0, Math.min(100, Number(item?.progress) || 0));
+    const p2 = Math.max(0, Math.min(100, Number(item?.progress2) || 0));
+    const start = Math.min(p1, p2);
     const now = Date.now();
     const isFuture = Number.isFinite(item?.startTimeMs) ? item.startTimeMs > now : start > 0;
     const barColor = isFuture ? "rgba(145, 145, 145, 0.1)" : "#2199F8";
     const beforeBg = isFuture ? "rgba(145, 145, 145, 0.1)" : "rgba(33, 153, 248, 0.1)";
+
     return {
         position: "absolute",
         left: "46px",
@@ -273,8 +424,8 @@ const getPhenologyBarStyle = (item) => {
 
 // 农事状态样式映射(0:默认,1-4:正常,5:完成,6:预警)
 const getArrangeStatusClass = (fw) => {
-    const t = fw?.flowStatus;
-    if (t == null) return "status-default";
+    const t = fw?.isFollow;
+    if (t == 0) return "normal-style";
     if (t >= 0 && t <= 4) return "status-normal";
     if (t === 5) return "status-complete";
     if (t === 6) return "status-warning";
@@ -283,18 +434,8 @@ const getArrangeStatusClass = (fw) => {
 
 // 计算 phenology-bar 的高度(px)
 const getPhenologyBarHeight = (item) => {
-    const p1 = Math.max(0, Math.min(100, Number(item?.progress) || 0));
-    const p2 = Math.max(0, Math.min(100, Number(item?.progress2) || 0));
-    const start = Math.min(p1, p2);
-    const end = Math.max(p1, p2);
-    const minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP);
-    const total = (solarTerms.value?.length || 0) * 1200;
-    const normalizedStart = range > 0 ? ((start - minP) / range) * 100 : 0;
-    const normalizedEnd = range > 0 ? ((end - minP) / range) * 100 : 0;
-    const heightPx = Math.max(2, ((normalizedEnd - normalizedStart) / 100) * total);
-    return heightPx;
+    // 直接使用基于农事数量的高度
+    return getPhenologyRequiredHeight(item);
 };
 
 // 计算 reproductive-item 的高度(px)
@@ -535,6 +676,9 @@ watch(
                         border-right: 5px solid #2199f8;
                     }
                 }
+                .arrange-card.normal-style {
+                    opacity: 0.4;
+                }
                 .arrange-card.status-warning {
                     border-color: #ff953d;
                     &::before {

+ 141 - 281
src/components/pageComponents/FarmWorkPlanTimeline.vue

@@ -1,65 +1,49 @@
 <template>
-    <div
-        class="timeline-container"
-        ref="timelineContainerRef"
-        :class="{ 'timeline-container-plant': pageType === 'plant' }"
-    >
-        <div class="timeline-list" :style="getListStyle">
+    <div class="timeline-container" ref="timelineContainerRef">
+        <div class="timeline-list" ref="timelineListRef">
             <div class="timeline-middle-line"></div>
-            <!-- 物候期覆盖条(progress 为起点,progress2 为终点,单位 %) -->
             <div
-                v-for="(p, idx) in phenologyList"
-                :key="p.id ?? idx"
-                class="phenology-bar"
-                :style="getPhenologyBarStyle(p)"
+                v-for="(t, tIdx) in solarTerms"
+                :key="`term-${uniqueTimestamp}-${tIdx}`"
+                class="timeline-term"
+                :style="getTermStyle(t, tIdx)"
             >
-                <div class="reproductive-list">
-                    <div
-                        v-for="(r, rIdx) in Array.isArray(p.reproductiveList) ? p.reproductiveList : []"
-                        :key="r.id ?? rIdx"
-                        class="reproductive-item"
-                        :class="{
-                            'horizontal-text': getReproductiveItemHeight(p) < 30,
-                            'vertical-lr-text': getReproductiveItemHeight(p) >= 30,
-                        }"
-                        :style="
-                            getReproductiveItemHeight(p) < 30
-                                ? { '--item-height': `${getReproductiveItemHeight(p)}px` }
-                                : {}
-                        "
-                    >
-                        {{ r.name }}
-                        <div class="arranges">
-                            <div
-                                v-for="(fw, aIdx) in Array.isArray(r.farmWorkArrangeList) ? r.farmWorkArrangeList : []"
-                                :key="fw.id ?? aIdx"
-                                class="arrange-card"
-                                :class="getArrangeStatusClass(fw)"
-                                @click="handleRowClick(fw)"
-                            >
-                                <div class="card-header">
-                                    <div class="header-left">
-                                        <span class="farm-work-name">{{ fw.farmWorkName || "--" }}</span>
-                                        <span class="tag-standard">标准农事</span>
-                                    </div>
-                                    <div class="header-right">
-                                        {{ fw.isFollow == 1 ? "已关注" : fw.isFollow == 2 ? "托管农事" : "" }}
-                                    </div>
+                <span class="term-name">{{ t.displayName }}</span>
+            </div>
+            <div v-for="(p, idx) in phenologyList" :key="`phenology-${uniqueTimestamp}-${idx}`" class="phenology-bar">
+                <div
+                    v-for="(r, rIdx) in Array.isArray(p.reproductiveList) ? p.reproductiveList : []"
+                    :key="`reproductive-${uniqueTimestamp}-${idx}-${rIdx}`"
+                    class="reproductive-item"
+                >
+                    <div class="arranges">
+                        <div
+                            v-for="(fw, aIdx) in Array.isArray(r.farmWorkArrangeList) ? r.farmWorkArrangeList : []"
+                            :key="`arrange-${uniqueTimestamp}-${idx}-${rIdx}-${aIdx}`"
+                            class="arrange-card"
+                            :class="getArrangeStatusClass(fw)"
+                            @click="handleRowClick(fw)"
+                        >
+                            <div class="card-header">
+                                <div class="header-left">
+                                    <span class="farm-work-name">{{ fw.farmWorkName || "--" }}</span>
+                                    <span class="tag-standard">{{ farmWorkType[fw.type] }}</span>
                                 </div>
-                                <div class="card-content">
-                                    <span>{{ fw.interactionQuestion || "暂无提示" }}</span>
-                                    <span v-if="!disableClick" class="edit-link" @click.stop="handleEdit(fw)"
-                                        >点击编辑</span
-                                    >
+                                <div class="header-right" v-if="!isStandard">
+                                    {{ fw.isFollow == 1 ? "已关注" : fw.isFollow == 2 ? "托管农事" : "" }}
                                 </div>
                             </div>
+                            <div class="card-content">
+                                <span>{{ fw.interactionQuestion || "暂无提示" }}</span>
+                                <span v-if="!disableClick" class="edit-link" @click.stop="handleEdit(fw)"
+                                    >点击编辑</span
+                                >
+                            </div>
                         </div>
                     </div>
+                    <div class="phenology-name">{{ r.name }}</div>
                 </div>
             </div>
-            <div v-for="t in solarTerms" :key="t.id" class="timeline-term" :style="getTermStyle(t)">
-                <span class="term-name">{{ t.displayName }}</span>
-            </div>
         </div>
     </div>
     <!-- 互动设置弹窗 -->
@@ -91,15 +75,38 @@ const props = defineProps({
         type: [Number, String],
         default: null,
     },
+    // 是否是标准农事
+    isStandard: {
+        type: Boolean,
+        default: false,
+    },
+    // 方案ID
+    schemeId: {
+        type: [Number, String],
+        default: null,
+    },
 });
 
+
+const farmWorkType = {
+    0: "预警农事",
+    1: "标准农事",
+    2: "建议农事",
+    3: "自建农事",
+};
+
 const emits = defineEmits(["row-click"]);
 
 const solarTerms = ref([]);
 const phenologyList = ref([]);
 const timelineContainerRef = ref(null);
+const timelineListRef = ref(null);
 // 标记是否为首次加载
 const isInitialLoad = ref(true);
+// 存储timeline-list的实际渲染高度
+const timelineListHeight = ref(0);
+// 生成唯一的时间戳,用于确保key的唯一性
+const uniqueTimestamp = ref(Date.now());
 
 // 获取当前季节
 const getCurrentSeason = () => {
@@ -129,83 +136,6 @@ const safeParseDate = (val) => {
     return NaN;
 };
 
-// 计算最小progress值(第一个节气的progress)
-const minProgress = computed(() => {
-    if (!solarTerms.value || solarTerms.value.length === 0) return 0;
-    const progresses = solarTerms.value.map((t) => Number(t?.progress) || 0).filter((p) => !isNaN(p));
-    return progresses.length > 0 ? Math.min(...progresses) : 0;
-});
-
-// 计算最大progress值
-const maxProgress = computed(() => {
-    if (!solarTerms.value || solarTerms.value.length === 0) return 100;
-    const progresses = solarTerms.value.map((t) => Number(t?.progress) || 0).filter((p) => !isNaN(p));
-    return progresses.length > 0 ? Math.max(...progresses) : 100;
-});
-
-// 计算所有节气的调整位置,确保相邻节气之间至少有36px间隔
-const adjustedTermPositions = computed(() => {
-    if (!solarTerms.value || solarTerms.value.length === 0) return new Map();
-
-    const minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP);
-    const total = calculateTotalHeightByFarmWorks();
-    const termHeight = 46;
-    const minSpacing = 36; // 最小间隔36px
-
-    // 计算所有节气的初始位置
-    const termsWithPositions = solarTerms.value.map((term) => {
-        const p = Math.max(0, Math.min(100, Number(term?.progress) || 0));
-        const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
-        const originalTop = (normalizedP / 100) * total;
-        return {
-            ...term,
-            id:
-                term.id ??
-                term.solarTermsId ??
-                term.termId ??
-                `${term.name || term.solarTermsName || term.termName || "term"}-${term.createDate || ""}`,
-            progress: p,
-            originalTop,
-        };
-    });
-
-    // 按原始位置排序
-    const sortedTerms = [...termsWithPositions].sort((a, b) => a.originalTop - b.originalTop);
-
-    // 调整位置,确保相邻节气之间至少有36px间隔
-    const adjustedPositions = new Map();
-    const termPositions = new Array(sortedTerms.length);
-
-    // 先确定最后一个节气的位置(与物候期底部对齐)
-    const lastIndex = sortedTerms.length - 1;
-    const lastTerm = sortedTerms[lastIndex];
-    if (lastTerm.progress === maxP && range > 0) {
-        termPositions[lastIndex] = total - termHeight;
-    } else {
-        termPositions[lastIndex] = lastTerm.originalTop;
-    }
-    adjustedPositions.set(lastTerm.id, termPositions[lastIndex]);
-
-    // 从后往前调整,确保相邻节气之间至少有36px间隔
-    for (let i = lastIndex - 1; i >= 0; i--) {
-        const term = sortedTerms[i];
-        let adjustedTop = term.originalTop;
-
-        // 检查与后一个节气的间隔
-        const nextTop = termPositions[i + 1];
-        if (nextTop - adjustedTop < minSpacing) {
-            adjustedTop = nextTop - minSpacing;
-        }
-
-        termPositions[i] = adjustedTop;
-        adjustedPositions.set(term.id, adjustedTop);
-    }
-
-    return adjustedPositions;
-});
-
 // 计算物候期需要的实际高度(基于农事数量)
 const getPhenologyRequiredHeight = (item) => {
     // 统计该物候期内的农事数量
@@ -225,7 +155,9 @@ const getPhenologyRequiredHeight = (item) => {
     }
 
     // 每个农事卡片的高度(根据实际内容,卡片高度可能因内容而异)
-    const farmWorkCardHeight = 100; // 增加卡片高度估算,确保能容纳内容
+    // 卡片包含:padding(8px*2) + header(约25px) + content margin(4px+2px) + content(约25-30px) = 约72-77px
+    // 考虑到内容可能换行,实际高度可能更高,设置为120px更安全,避免卡片重叠
+    const farmWorkCardHeight = 120; // 卡片高度估算,确保能容纳内容且不重叠
     // 卡片之间的间距(与CSS中的gap保持一致)
     const cardGap = 12;
 
@@ -233,8 +165,8 @@ const getPhenologyRequiredHeight = (item) => {
     // 如果有多个卡片,需要加上它们之间的间距
     const totalHeight = farmWorkCount * farmWorkCardHeight + (farmWorkCount > 1 ? (farmWorkCount - 1) * cardGap : 0);
 
-    // 返回总高度,并增加足够的余量(30px)确保所有农事都能完整显示,不会堆叠
-    return Math.max(totalHeight + 30, 50); // 最小50px,额外加30px余量确保不重叠
+    // 返回精确的总高度,只保留最小高度限制,不添加额外余量
+    return Math.max(totalHeight, 50); // 最小50px,精确匹配农事卡片高度
 };
 
 // 计算所有物候期的累积位置和总高度
@@ -263,7 +195,7 @@ const calculatePhenologyPositions = () => {
 
     return {
         positions,
-        totalHeight: currentTop + 12, // 总高度 = 最后一个物候期底部 + 底部间距
+        totalHeight: currentTop, // 总高度 = 最后一个物候期的底部位置,不添加额外间距
     };
 };
 
@@ -288,70 +220,43 @@ const calculateTotalHeightByFarmWorks = () => {
         lastPhenologyBottom = currentTop; // 最后一个物候期的底部位置
     }
 
-    // 直接使用最后一个物候期的底部位置作为总高度
+    // 直接使用最后一个物候期的底部位置作为总高度,不添加额外余量
     // 在getTermStyle中,我们已经调整了最后一个节气的top位置(total - 46)
     // 这样最后一个节气的底部(total - 46 + 46 = total)就能与物候期底部对齐
     if (lastPhenologyBottom > 0) {
         const baseHeight = (solarTerms.value?.length || 0) * 50;
-        // 总高度 = 最后一个物候期的底部位置
-        // 这样最后一个节气的底部就能对齐到物候期的底部
-        return Math.max(lastPhenologyBottom, totalHeight, baseHeight, 500);
+        // 总高度 = 最后一个物候期的底部位置,精确匹配,不添加额外余量
+        return Math.max(lastPhenologyBottom, totalHeight, baseHeight);
     }
 
     // 基础高度:每个节气至少需要一定高度,确保节气标签能显示
     const baseHeight = (solarTerms.value?.length || 0) * 50;
 
-    // 返回物候期总高度和基础高度的较大值
-    return Math.max(totalHeight, baseHeight, 500); // 最小500px
+    // 返回物候期总高度和基础高度的较大值,不添加最小高度限制
+    return Math.max(totalHeight, baseHeight);
 };
 
-// 列表高度
-const getListStyle = computed(() => {
-    const minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP); // 避免除0
-    const total = calculateTotalHeightByFarmWorks();
-    const minH = range === 0 ? 0 : total;
-    return { minHeight: `${minH}px` };
-});
+const getTermStyle = (t, index) => {
+    // 优先使用实际测量的timeline-list高度,如果没有测量到则使用计算值作为后备
+    const totalHeight = timelineListHeight.value > 0 ? timelineListHeight.value : calculateTotalHeightByFarmWorks();
 
-const getTermStyle = (t) => {
-    // 生成与 adjustedTermPositions 中相同的 ID
-    const termId =
-        t.id ??
-        t.solarTermsId ??
-        t.termId ??
-        `${t.name || t.solarTermsName || t.termName || "term"}-${t.createDate || ""}`;
-
-    // 从调整后的位置映射中获取 top 值
-    const adjustedTop = adjustedTermPositions.value.get(termId);
-
-    // 如果找不到调整后的位置,使用原始计算方式作为后备
-    let top = adjustedTop;
-    if (adjustedTop === undefined) {
-        const p = Math.max(0, Math.min(100, Number(t?.progress) || 0));
-        const minP = minProgress.value;
-        const maxP = maxProgress.value;
-        const range = Math.max(1, maxP - minP);
-        const total = calculateTotalHeightByFarmWorks();
-        const termHeight = 46;
-        const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
-        top = (normalizedP / 100) * total;
-
-        // 如果是最后一个节气,调整top位置
-        if (p === maxP && range > 0) {
-            top = total - termHeight;
-        }
-    }
+    // 获取节气总数
+    const termCount = solarTerms.value?.length || 1;
+
+    // 等分高度:总高度 / 节气数量
+    const termHeight = totalHeight / termCount;
+
+    // 计算top位置:索引 * 每个节气的高度
+    const top = index * termHeight;
 
     return {
         position: "absolute",
         top: `${top}px`,
         left: 0,
         width: "30px",
-        // height: "20px",
+        height: `${termHeight}px`, // 高度等分,使用实际测量的高度
         display: "flex",
-        alignItems: "flex-start",
+        alignItems: "center",
     };
 };
 
@@ -365,62 +270,28 @@ const handleSeasonClick = (seasonValue) => {
     };
     const targetName = mapping[seasonValue];
     if (!targetName) return;
-    const target = (solarTerms.value || []).find((t) => (t?.displayName || "") === targetName);
-    if (!target) return;
-    const p = Math.max(0, Math.min(100, Number(target.progress) || 0));
-    const minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP);
-    const total = calculateTotalHeightByFarmWorks(); // 使用动态计算的总高度
-    const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
-    const targetTop = (normalizedP / 100) * total; // 内容内的像素位置
+
+    // 查找对应的节气
+    const targetIndex = solarTerms.value.findIndex((t) => (t?.displayName || "") === targetName);
+    if (targetIndex === -1) return;
+
+    // 计算目标节气的top位置
+    const totalHeight = timelineListHeight.value > 0 ? timelineListHeight.value : calculateTotalHeightByFarmWorks();
+    const termCount = solarTerms.value?.length || 1;
+    const termHeight = totalHeight / termCount;
+    const targetTop = targetIndex * termHeight;
+
+    // 滚动到目标位置
     const wrap = timelineContainerRef.value;
     if (!wrap) return;
     const viewH = wrap.clientHeight || 0;
     const maxScroll = Math.max(0, wrap.scrollHeight - viewH);
-    // 将目标位置稍微靠上(使用 0.35 视口高度做偏移)
+    // 将目标位置稍微靠上(使用 0.1 视口高度做偏移)
     let scrollTop = Math.max(0, targetTop - viewH * 0.1);
     if (scrollTop > maxScroll) scrollTop = maxScroll;
     wrap.scrollTo({ top: scrollTop, behavior: "smooth" });
 };
 
-// 物候期覆盖条样式
-const getPhenologyBarStyle = (item) => {
-    const { positions } = calculatePhenologyPositions();
-    // 使用与数据生成时相同的ID生成逻辑
-    const itemId = item.id ?? item.phenologyId ?? item.name ?? `${item.progress}-${item.progress2}`;
-    const position = positions.get(itemId);
-
-    // 如果找不到位置信息,使用默认值
-    let topPx = 10;
-    let heightPx = 50;
-
-    if (position) {
-        topPx = position.top;
-        heightPx = position.height;
-    }
-
-    const p1 = Math.max(0, Math.min(100, Number(item?.progress) || 0));
-    const p2 = Math.max(0, Math.min(100, Number(item?.progress2) || 0));
-    const start = Math.min(p1, p2);
-    const now = Date.now();
-    const isFuture = Number.isFinite(item?.startTimeMs) ? item.startTimeMs > now : start > 0;
-    const barColor = isFuture ? "rgba(145, 145, 145, 0.1)" : "#2199F8";
-    const beforeBg = isFuture ? "rgba(145, 145, 145, 0.1)" : "rgba(33, 153, 248, 0.1)";
-
-    return {
-        position: "absolute",
-        left: "46px",
-        width: "25px",
-        top: `${topPx}px`,
-        height: `${heightPx}px`,
-        background: barColor,
-        color: isFuture ? "#747778" : "#fff",
-        "--bar-before-bg": beforeBg,
-        zIndex: 2,
-    };
-};
-
 // 农事状态样式映射(0:默认,1-4:正常,5:完成,6:预警)
 const getArrangeStatusClass = (fw) => {
     const t = fw?.isFollow;
@@ -431,19 +302,6 @@ const getArrangeStatusClass = (fw) => {
     return "status-default";
 };
 
-// 计算 phenology-bar 的高度(px)
-const getPhenologyBarHeight = (item) => {
-    // 直接使用基于农事数量的高度
-    return getPhenologyRequiredHeight(item);
-};
-
-// 计算 reproductive-item 的高度(px)
-const getReproductiveItemHeight = (phenologyItem) => {
-    const barHeight = getPhenologyBarHeight(phenologyItem);
-    const listLength = Array.isArray(phenologyItem?.reproductiveList) ? phenologyItem.reproductiveList.length : 1;
-    return listLength > 0 ? barHeight / listLength : barHeight;
-};
-
 const handleRowClick = (item) => {
     emits("row-click", item);
 };
@@ -456,21 +314,20 @@ const handleEdit = (item) => {
     }
 };
 
-const containerIdData = ref(null);
-
 // 获取农事规划数据
 const getFarmWorkPlan = () => {
     if (!props.farmId && !props.containerId) return;
+    // 重置测量高度,等待重新测量
+    timelineListHeight.value = 0;
     let savedScrollTop = 0;
     if (!isInitialLoad.value && timelineContainerRef.value) {
         savedScrollTop = timelineContainerRef.value.scrollTop || 0;
     }
 
     VE_API.monitor
-        .farmWorkPlan({ farmId: props.farmId, containerId: props.containerId })
+        .farmWorkPlan({ farmId: props.farmId, containerId: props.containerId, schemeId: props.schemeId })
         .then(({ data, code }) => {
             if (code === 0) {
-                containerIdData.value = data.phenologyList[0].containerSpaceTimeId;
                 const list = Array.isArray(data?.solarTermsList) ? data.solarTermsList : [];
                 const filtered = list
                     .filter((t) => t && t.type === 1)
@@ -516,10 +373,30 @@ const getFarmWorkPlan = () => {
                     : [];
 
                 nextTick(() => {
+                    // 测量timeline-list的实际渲染高度
+                    if (timelineListRef.value) {
+                        requestAnimationFrame(() => {
+                            const height = timelineListRef.value.offsetHeight || timelineListRef.value.clientHeight;
+                            if (height > 0) {
+                                timelineListHeight.value = height;
+
+                                // 如果是首次加载,滚动到当前季节对应的节气
+                                if (isInitialLoad.value) {
+                                    const currentSeason = getCurrentSeason();
+                                    handleSeasonClick(currentSeason);
+                                    isInitialLoad.value = false;
+                                }
+                            }
+                        });
+                    }
+
                     if (isInitialLoad.value) {
-                        const currentSeason = getCurrentSeason();
-                        handleSeasonClick(currentSeason);
-                        isInitialLoad.value = false;
+                        // 如果测量失败,延迟一下再尝试滚动
+                        setTimeout(() => {
+                            const currentSeason = getCurrentSeason();
+                            handleSeasonClick(currentSeason);
+                            isInitialLoad.value = false;
+                        }, 100);
                     } else if (timelineContainerRef.value && savedScrollTop > 0) {
                         timelineContainerRef.value.scrollTop = savedScrollTop;
                     }
@@ -542,6 +419,13 @@ watch(
     },
     { immediate: true }
 );
+watch(
+    () => props.schemeId,
+    () => {
+        getFarmWorkPlan();
+    },
+    { immediate: true }
+);
 </script>
 
 <style scoped lang="scss">
@@ -563,19 +447,9 @@ watch(
         z-index: 1;
     }
     .phenology-bar {
-        display: flex;
         align-items: stretch;
         justify-content: center;
         box-sizing: border-box;
-        .reproductive-list {
-            display: grid;
-            grid-auto-rows: 1fr; /* 子项等高,整体等分父高度 */
-            align-items: stretch;
-            justify-items: center; /* 子项居中 */
-            width: 100%;
-            height: 100%;
-            box-sizing: border-box;
-        }
         .reproductive-item {
             font-size: 12px;
             text-align: center;
@@ -587,29 +461,21 @@ watch(
             line-height: 23px;
             color: inherit;
             position: relative;
-            &.horizontal-text {
-                writing-mode: horizontal-tb;
-                text-orientation: mixed;
-                letter-spacing: normal;
-                line-height: calc(var(--item-height, 15px) - 3px);
-            }
-            &.vertical-lr-text {
-                writing-mode: vertical-lr;
-                text-orientation: upright;
-                letter-spacing: 3px;
-                line-height: 26px;
+            .phenology-name {
+                width: 26px;
+                height: 100%;
+                background: #2199f8;
+                color: #fff;
+                margin-right: 18px;
+                border-radius: 2px;
             }
             .arranges {
-                position: absolute;
-                left: 40px; /* 列与中线右侧一段距离 */
-                top: 0;
-                z-index: 3;
                 display: flex;
-                max-width: calc(100vw - 100px);
-                gap: 12px;
+                max-width: calc(100vw - 86px);
+                gap: 5px;
                 letter-spacing: 0px;
                 .arrange-card {
-                    width: 97%;
+                    width: 93%;
                     border: 0.5px solid #2199f8;
                     border-radius: 8px;
                     background: #fff;
@@ -656,12 +522,6 @@ watch(
                             margin-left: 5px;
                         }
                     }
-                    .status-icon {
-                        position: absolute;
-                        right: -8px;
-                        bottom: -8px;
-                        z-index: 3;
-                    }
                     &::before {
                         content: "";
                         position: absolute;
@@ -700,10 +560,10 @@ watch(
         }
     }
     .reproductive-item + .reproductive-item {
-        border-top: 2px solid #fff;
+        padding-top: 3px;
     }
     .phenology-bar + .phenology-bar {
-        border-top: 2px solid #fff;
+        padding-top: 3px;
     }
     .timeline-term {
         position: absolute;
@@ -715,7 +575,7 @@ watch(
         .term-name {
             display: inline-block;
             width: 100%;
-            height: 46px;
+            min-height: 35px;
             line-height: 30px;
             background: #f5f7fb;
             font-size: 13px;

+ 7 - 5
src/views/old_mini/modify_work/modify.vue

@@ -522,10 +522,11 @@
                             </price-table>
                         </div>
                     </div>
-                    
-                    <div class="submit-btn center-btn" v-has-permission="'农事规划'">
-                        <div class="btn" @click.prevent="toEditPrescription">编辑处方</div>
-                    </div>
+                    <template v-if="!isDefault">
+                        <div class="submit-btn center-btn" v-has-permission="'农事规划'">
+                            <div class="btn" @click.prevent="toEditPrescription">编辑处方</div>
+                        </div>
+                    </template>
                 </template>
             </el-form>
         </div>
@@ -592,8 +593,9 @@ const phenologyList = ref([]);
 const isEdit = ref(false);
 const onlyPrice = ref(false);
 const noPrice = ref(false);
-
+const isDefault = ref(false);
 onActivated(() => {
+    isDefault.value = route.query.isDefault == 'true' ? true : false;
     isEdit.value = route.query.isEdit ? true : false;
     onlyPrice.value = route.query.onlyPrice ? true : false;
     noPrice.value = route.query.noPrice ? true : false;

+ 19 - 6
src/views/old_mini/monitor/subPages/plan.vue

@@ -38,17 +38,19 @@
                     :containerId="containerIdData"
                     @row-click="handleRowClick"
                     :disableClick="!hasPlanPermission || active === tabs[0]?.id"
+                    :isStandard="active === tabs[0]?.id"
+                    :schemeId="schemeIdData"
                 />
             </div>
         </div>
-        <div class="custom-bottom-fixed-btns" v-has-permission="'农事规划'">
+        <div class="custom-bottom-fixed-btns" :class="{'center':active === tabs[0]?.id}" v-has-permission="'农事规划'">
             <div class="bottom-btn-group">
-                <div class="bottom-btn secondary-btn" @click="handlePhenologySetting">物候期设置</div>
+                <div class="bottom-btn secondary-btn" @click="handlePhenologySetting" v-show="active !== tabs[0]?.id">物候期设置</div>
                 <div class="bottom-btn secondary-btn" v-if="pageType === 'plant'" @click="openCopyPlanPopup">
                     {{ active === tabs[0]?.id ? "复制方案" : "方案设置" }}
                 </div>
             </div>
-            <div class="bottom-btn primary-btn" @click="addNewTask">新增农事</div>
+            <div class="bottom-btn primary-btn" @click="addNewTask" v-show="active !== tabs[0]?.id">新增农事</div>
         </div>
     </div>
     <!-- 农事信息弹窗 -->
@@ -156,10 +158,12 @@ const tabs = ref([]);
 // 控制标签滚动方向:'left' | 'right' | 'auto' | ''
 const scrollType = ref("auto");
 const containerIdData = ref(null);
+const schemeIdData = ref(null);
 const getListMySchemes = (type = "auto") => {
     VE_API.home.listMySchemes({ containerId: specieValue.value }).then(({ data }) => {
         tabs.value = data || [];
         containerIdData.value = data[0]?.containerId;
+        schemeIdData.value = data[0]?.sourceSchemeId;
         if (type === "right") {
             active.value = data[data.length - 1].id;
         } else if (type === "left") {
@@ -177,6 +181,7 @@ const currentTab = ref(null);
 const handleTabChange = (id, item) => {
     active.value = id;
     currentTab.value = item;
+    schemeIdData.value = item.sourceSchemeId;
     getFarmWorkPlanForPhenology();
 };
 
@@ -202,6 +207,7 @@ const getFarmWorkPlanForPhenology = async () => {
         const { data, code } = await VE_API.monitor.farmWorkPlan({
             containerId: specieValue.value,
             farmId: route.query.farmId,
+            schemeId: schemeIdData.value,
         });
         if (code === 0 && data?.phenologyList?.[0]?.containerSpaceTimeId) {
             containerSpaceTimeId.value = data.phenologyList[0].containerSpaceTimeId;
@@ -343,6 +349,7 @@ const handleRowClick = (item) => {
             farmWorkId: item.farmWorkId,
             containerSpaceTimeId: item.containerSpaceTimeId,
             agriculturalStoreId: route.query.agriculturalStoreId,
+            isDefault: active.value === tabs.value[0]?.id,
         },
     });
 };
@@ -378,10 +385,10 @@ const handleRowClick = (item) => {
         }
 
         .timeline-wrap {
-            height: calc(100vh - 40px - 73px);
-            padding-left: 12px;
+            height: calc(100vh - 40px - 85px);
+            padding: 0 12px;
             &.timeline-container-plant-wrap {
-                height: calc(100vh - 40px - 73px - 38px);
+                height: calc(100vh - 40px - 85px - 38px);
             }
             // 没有权限时,底部按钮不显示,高度增加 73px
             &.timeline-container-no-permission-wrap {
@@ -395,6 +402,12 @@ const handleRowClick = (item) => {
             display: flex;
             gap: 12px;
         }
+        &.center{
+            justify-content: center;
+            .bottom-btn{
+                padding: 10px 45px;
+            }
+        }
     }
 }
 .copy-plan-popup {