lxf преди 2 дни
родител
ревизия
99dd307352

+ 0 - 24
src/router/globalRoutes.js

@@ -29,12 +29,6 @@ export default [
         name: "FarmDetails",
         component: () => import("@/views/old_mini/farmDetails/index.vue"),
     },
-    {
-        path: "/monitor",
-        name: "Monitor",
-        meta: { showTabbar: true, keepAlive: true },
-        component: () => import("@/views/old_mini/monitor/index.vue"),
-    },
     //个人中心
     {
         path: "/mine",
@@ -285,24 +279,6 @@ export default [
         name: "UserInfo",
         component: () => import("@/views/old_mini/mine/pages/userInfo.vue"),
     },
-    // 农事规划
-    {
-        path: "/plan",
-        name: "Plan",
-        component: () => import("@/views/old_mini/monitor/subPages/plan.vue"),
-    },
-    // 农事复核
-    {
-        path: "/review-results",
-        name: "ReviewResults",
-        component: () => import("@/views/old_mini/monitor/subPages/reviewResults.vue"),
-    },
-    // 农情采样详情
-    {
-        path: "/agricultural_detail",
-        name: "AgriculturalDetail",
-        component: () => import("@/views/old_mini/monitor/subPages/agriculturalDetail.vue"),
-    },
     // 农事方案
     {
         path: "/agricultural_plan",

+ 0 - 484
src/views/old_mini/monitor/index.vue

@@ -1,484 +0,0 @@
-<template>
-    <custom-header v-if="isHeaderShow" name="农场详情"></custom-header>
-    <div class="monitor-index" :style="{ height: `calc(100vh - ${tabBarHeight}px)` }">
-        <!-- 天气遮罩 -->
-        <div class="weather-mask" v-show="isExpanded" @click="handleMaskClick"></div>
-        <!-- 天气 -->
-        <weather-info ref="weatherInfoRef" class="weather-info" @weatherExpanded="weatherExpanded"
-            @changeGarden="changeGarden" :isGarden="true" :gardenId="defaultGardenId"></weather-info>
-        <!-- 作物档案 -->
-        <div class="archives-time-line">
-            <div class="archives-time-line-header" @click="handleJump">
-                <div class="line-title">作物档案</div>
-                <!-- <el-date-picker style="width: 110px" v-model="date" type="year" placeholder="全部日期" /> -->
-            </div>
-            <div class="archives-time-line-content">
-                <div class="report-box" v-if="hasReport">
-                    <div class="box-content">
-                        <div class="box-title" @click="handleReportClick">
-                            <span>农情互动报告</span>
-                            <el-icon>
-                                <CaretRight />
-                            </el-icon>
-                        </div>
-                        <span class="box-text">点击查看您的长势报告</span>
-                    </div>
-                    <img src="@/assets/img/monitor/report-icon.png" alt="" class="report-icon" />
-                </div>
-                <div class="time-line" :class="{ 'no-report': !hasReport }">
-                    <archives-farm-time-line :farmId="gardenId"></archives-farm-time-line>
-                </div>
-            </div>
-        </div>
-    </div>
-
-    <tip-popup v-model:show="showFarmPopup" type="success" text="农场领取成功"
-        :overlay-style="{ 'backdrop-filter': 'blur(4px)' }" :closeOnClickOverlay="false" :zIndex="9999" />
-
-    <!-- 农事执行弹窗 -->
-    <agri-execute-popup v-model:show="showAgriExecutePopup" :popupData="agriExecuteData"
-        @executed="handleAgriExecuted" @close="handleClosePopup" />
-</template>
-
-<script setup>
-import customHeader from "@/components/customHeader.vue";
-import { ref, computed, onActivated, onDeactivated, onMounted } from "vue";
-import { useStore } from "vuex";
-import { Badge, List } from "vant";
-import weatherInfo from "@/components/weatherInfo.vue";
-import { useRouter, useRoute } from "vue-router";
-import farmInfoPopup from "../home/components/farmInfoPopup.vue";
-import tipPopup from "@/components/popup/tipPopup.vue";
-import { ElMessage, ElMessageBox } from "element-plus";
-import ArchivesFarmTimeLine from "@/components/pageComponents/ArchivesFarmTimeLine.vue";
-import agriExecutePopup from "@/components/popup/agriExecutePopup.vue";
-
-const handleJump = () =>{
-    router.push('/create_farm?type=farmer&expertMiniUserId=81881&isReload=true')
-}
-
-const showAgriExecutePopup = ref(false); // 农事执行弹窗
-const agriExecuteData = ref({});
-const handleAgriExecuted = () => {
-    showAgriExecutePopup.value = false;
-    // router.push("/interaction_list?expertMiniUserId=81881&oldUser=true");
-    handleClosePopup();
-    router.push("/interaction_list?expertMiniUserId=81881");
-};
-
-const expertInfo = ref({});
-const checkHasUnrepliedTriggeredInteraction = async () => {
-    const { data } = await VE_API.home.hasUnrepliedTriggeredInteraction({ farmId: localStorage.getItem("selectedFarmId") });
-    if (data && data.id != null) {
-        expertInfo.value = data;
-        agriExecuteData.value = {
-            expertAvatar: data.expertAvatar,
-            expertName: data.expertName,
-            title: data.subjectName,
-            abnormalText: data.subjectRemark,
-            exampleImg: JSON.parse(data.exampleImagesJson)[0],
-            executedButtonText: '开始采集',
-        };
-        showAgriExecutePopup.value = true;
-    }
-};
-
-const showFarmPopup = ref(false); // 农场领取成功弹窗
-const date = ref(new Date());
-
-const defaultGardenId = ref(null);
-const isHeaderShow = ref(false);
-const isDefaultFarm = ref(false);
-const weatherInfoRef = ref(null);
-const hasReport = ref(false);
-onActivated(() => {
-    sessionStorage.removeItem('interactionListScrollTop');
-    // 用来接收我的农场跳转过来的农场详情逻辑
-    if (route.query.isHeaderShow) {
-        isHeaderShow.value = true;
-        defaultGardenId.value = route.query.farmId;
-        // 统一转换为布尔值
-        isDefaultFarm.value = route.query.defaultFarm === "true" || route.query.defaultFarm === true;
-    }
-    
-    getReport();
-});
-
-const receiveFarm = (json) => {
-    VE_API.monitor
-        .receiveFarm({
-            agriculturalStoreId: json.agriculturalStoreId,
-            farmId: json.farmId,
-        })
-        .then((res) => {
-            if (res.code === 0) {
-                showFarmPopup.value = true;
-                defaultGardenId.value = json.farmId;
-            } else {
-                ElMessage.warning(res.msg);
-            }
-            // 清空路由参数
-            router.replace({ path: route.path });
-        });
-};
-
-const store = useStore();
-const tabBarHeight = computed(() => store.state.home.tabBarHeight);
-const router = useRouter();
-const route = useRoute();
-
-const farmInfoRef = ref(null);
-
-function toFarmInfo() {
-    farmInfoRef.value.handleShow();
-}
-
-// 功能卡片数据
-const functionCards = ref([
-    {
-        title: "农事规划",
-        route: "/plan",
-    },
-    {
-        title: "农场报告",
-        status: "最新",
-        route: "/farm_report",
-        className: "blue",
-    },
-    {
-        title: "农事方案",
-        route: "/agricultural_plan",
-    },
-    {
-        title: "复核成效",
-        status: "最新",
-        route: "/review-results",
-        className: "yellow",
-    },
-]);
-
-const getStayCount = () => {
-    VE_API.monitor
-        .getCountByStatusAndFarmId({
-            farmId: gardenId.value,
-            startStatus: 1,
-            endStatus: 3,
-        })
-        .then((res) => {
-            functionCards.value[0].status = null;
-            if (res.data && res.data != 0) {
-                functionCards.value[0].status = res.data + " 待完成";
-            }
-        });
-};
-
-// 实时播报数据
-const broadcastList = ref([]);
-const loading = ref(false);
-const finished = ref(false);
-const currentPage = ref(1);
-const pageSize = ref(10);
-
-const getBroadcastList = async (page = 1, isLoadMore = false) => {
-    if (!gardenId.value) {
-        loading.value = false;
-        return;
-    }
-
-    // 如果正在加载,直接返回(避免重复请求)
-    if (loading.value) {
-        return;
-    }
-
-    loading.value = true;
-    try {
-        const res = await VE_API.monitor.broadcastPage({
-            farmId: gardenId.value,
-            limit: pageSize.value,
-            page: page,
-        });
-
-        const newData = res.data || [];
-        if (isLoadMore) {
-            broadcastList.value = [...broadcastList.value, ...newData];
-        } else {
-            broadcastList.value = newData;
-        }
-
-        // 判断是否还有更多数据
-        if (newData.length < pageSize.value) {
-            finished.value = true;
-        } else {
-            finished.value = false;
-            // 如果未完成,页码+1,为下次加载做准备
-            currentPage.value = page + 1;
-        }
-    } catch (error) {
-        console.error("获取播报列表失败:", error);
-        finished.value = true;
-    } finally {
-        // 确保 loading 状态被正确设置为 false
-        loading.value = false;
-    }
-};
-
-// 滚动加载更多
-const onLoad = async () => {
-    if (finished.value || loading.value) return;
-
-    // 判断是否是首次加载(页码为1)
-    const isLoadMore = currentPage.value > 1;
-    const pageToLoad = currentPage.value;
-
-    // 加载数据(页码会在 getBroadcastList 成功后自动更新)
-    await getBroadcastList(pageToLoad, isLoadMore);
-};
-
-// 卡片点击事件
-const handleCardClick = (card) => {
-    const params = {
-        farmId: gardenId.value,
-    };
-    router.push({
-        path: card.route,
-        query: { ...params, miniJson: JSON.stringify(params) },
-    });
-};
-
-// 播报相关事件
-const isSpeaking = ref(false);
-const speechSynthesis = window.speechSynthesis;
-
-const handleBroadcast = () => {
-    if (isSpeaking.value) {
-        // 如果正在播放,则停止
-        speechSynthesis.cancel();
-        isSpeaking.value = false;
-        return;
-    }
-
-    // 构建播报文本
-    let broadcastText = "实时播报:";
-
-    if (broadcastList.value.length === 0) {
-        broadcastText += "暂无更多播报";
-    } else {
-        broadcastList.value.forEach((item, index) => {
-            broadcastText += `${index + 1}、${item.title}。${item.content}。`;
-        });
-    }
-
-    // 创建语音合成对象
-    const utterance = new SpeechSynthesisUtterance(broadcastText);
-
-    // 设置语音参数
-    utterance.lang = "zh-CN";
-    utterance.rate = 0.8; // 语速
-    utterance.pitch = 1; // 音调
-    utterance.volume = 1; // 音量
-
-    // 播放开始事件
-    utterance.onstart = () => {
-        isSpeaking.value = true;
-    };
-
-    // 播放结束事件
-    utterance.onend = () => {
-        isSpeaking.value = false;
-    };
-
-    // 播放错误事件
-    utterance.onerror = (event) => {
-        isSpeaking.value = false;
-        console.error("播报错误:", event.error);
-    };
-
-    // 开始播报
-    speechSynthesis.speak(utterance);
-};
-
-// 组件卸载时停止语音播放
-onDeactivated(() => {
-    showFarmPopup.value = false;
-    isDefaultFarm.value = false;
-    if (isSpeaking.value) {
-        speechSynthesis.cancel();
-        isSpeaking.value = false;
-    }
-});
-
-const isExpanded = ref(false);
-const weatherExpanded = (isExpandedValue) => {
-    isExpanded.value = isExpandedValue;
-};
-
-// 点击遮罩时收起天气
-const handleMaskClick = () => {
-    if (weatherInfoRef.value && weatherInfoRef.value.toggleExpand) {
-        weatherInfoRef.value.toggleExpand();
-    }
-};
-
-const gardenId = ref(store.state.home.gardenId);
-
-// 初始化加载数据
-onMounted(() => {
-    if (gardenId.value) {
-        currentPage.value = 1;
-        finished.value = false;
-        broadcastList.value = [];
-    }
-});
-
-const changeGarden = ({ id }) => {
-    gardenId.value = id;
-    // 更新 store 中的状态
-    store.commit("home/SET_GARDEN_ID", id);
-    // 重置分页状态
-    currentPage.value = 1;
-    finished.value = false;
-    broadcastList.value = [];
-    getStayCount();
-    getBroadcastList(1, false);
-    checkHasUnrepliedTriggeredInteraction();
-    getReport();
-};
-
-function handlePage(url) {
-    const query = {
-        farmId: gardenId.value,
-    };
-    if (url === "/message_list") {
-        query.from = "monitor";
-    }
-    router.push({
-        path: url,
-        query: query,
-    });
-}
-
-function handleReportClick() {
-    router.push({
-        path: "/growth_report",
-        query: { farmId: gardenId.value },
-    });
-}
-
-const handleClosePopup = () => {
-    VE_API.monitor.closeTodayPopup({
-        interactionId: expertInfo.value.id,
-    });
-}
-
-const getReport = () => {
-    VE_API.farm.growthReportByFarm({farmId: gardenId.value, limit: 20}).then(({ data }) => {
-        if (data && data.length > 0) {
-            hasReport.value = true;
-        } else {
-            hasReport.value = false;
-        }
-    });
-}
-</script>
-
-<style scoped lang="scss">
-.monitor-index {
-    width: 100%;
-    height: 100%;
-    padding: 13px 10px;
-    box-sizing: border-box;
-    background: linear-gradient(180deg, #f9f9f9 0%, #f0f8ff 31.47%, #f9f9f9 46.81%, #f9f9f9 69.38%, #f9f9f9 100%);
-
-    .weather-mask {
-        position: fixed;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 100%;
-        background-color: rgba(0, 0, 0, 0.52);
-        z-index: 11;
-    }
-
-    .weather-info {
-        width: calc(100% - 20px);
-        position: absolute;
-        z-index: 12;
-    }
-
-    .archives-time-line {
-        position: relative;
-        margin-top: 96px;
-        height: calc(100% - 90px);
-
-        .archives-time-line-header {
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-
-            .line-title {
-                position: relative;
-                padding-left: 14px;
-                font-size: 16px;
-
-                &::before {
-                    content: "";
-                    position: absolute;
-                    left: 5px;
-                    top: 50%;
-                    transform: translateY(-50%);
-                    width: 4px;
-                    height: 15px;
-                    background: #2199f8;
-                    border-radius: 20px;
-                }
-            }
-        }
-
-        .archives-time-line-content {
-            margin-top: 10px;
-            height: calc(100% - 32px);
-            background: #fff;
-            border-radius: 8px;
-            padding: 10px;
-            box-sizing: border-box;
-
-            .report-box {
-                background: linear-gradient(120deg, #eef8ff, #bbe3ff);
-                border-radius: 4px;
-                padding: 6px 0 0 16px;
-                display: flex;
-                align-items: center;
-                justify-content: space-between;
-                margin-bottom: 12px;
-
-                .box-content {
-                    .box-title {
-                        font-size: 16px;
-                        color: #2199f8;
-                        font-weight: 500;
-                        margin-bottom: 4px;
-                        display: flex;
-                        align-items: center;
-                    }
-
-                    .box-text {
-                        color: #4e5969;
-                    }
-                }
-
-                .report-icon {
-                    width: 120px;
-                    height: 85px;
-                }
-            }
-
-            .time-line {
-                height: calc(100% - 100px);
-                &.no-report {
-                    height: 100%;
-                }
-            }
-        }
-    }
-}
-</style>

+ 0 - 149
src/views/old_mini/monitor/subPages/agriculturalDetail.vue

@@ -1,149 +0,0 @@
-<template>
-    <div class="agricultural-detail">
-        <custom-header name="农事详情"></custom-header>
-
-        <div class="detail-content">
-            <!-- 采样信息卡片 -->
-            <div class="card-wrap">
-                <div class="card-box sampling-card">
-                    <div class="sampling-title">采样时间: {{ samplingTime }}</div>
-                    <div class="sampling-desc">{{ samplingDesc }}</div>
-                </div>
-            </div>
-
-            <!-- 农情照片卡片 -->
-            <div class="card-wrap">
-                <div class="card-box photo-card">
-                    <div class="card-title">农情照片</div>
-                    <div class="area-btns">
-                        <div v-for="(area, idx) in areaList" :key="idx" class="area-btn"
-                            :class="{ active: activeAreaIndex === idx }" @click="activeAreaIndex = idx">
-                            {{ area }}区
-                        </div>
-                    </div>
-                    <div class="ratio-tip">出现新梢的果树占比为{{ currentRatio }}%</div>
-                </div>
-            </div>
-        </div>
-    </div>
-</template>
-
-<script setup>
-import { useRouter, useRoute } from "vue-router";
-import { ref, computed } from "vue";
-import customHeader from "@/components/customHeader.vue";
-
-const router = useRouter();
-const route = useRoute();
-
-// 从路由 miniJson 解析 id
-const detailId = ref(null);
-if (route.query.miniJson) {
-    try {
-        const mini = JSON.parse(route.query.miniJson);
-        detailId.value = mini.id ?? null;
-    } catch (_) {
-        console.error("解析 miniJson 失败", _);
-    }
-}
-
-// 采样时间、描述(后续可接接口)
-const samplingTime = ref("2025.05.06");
-const samplingDesc = ref("本次飞巡采样了1区、2区和5区,拍摄了120棵树");
-
-// 区域列表与当前选中
-const areaList = ref(["1", "2", "3", "3"]);
-const activeAreaIndex = ref(0);
-
-// 当前选中区的占比(示例数据,可按区区分)
-const ratioByArea = ["5", "8", "12", "12"];
-const currentRatio = computed(() => ratioByArea[activeAreaIndex.value] ?? "5");
-
-</script>
-
-<style lang="scss" scoped>
-.agricultural-detail {
-    min-height: 100vh;
-    width: 100%;
-    background: #f2f3f5;
-
-    .detail-content {
-        padding: 12px;
-        padding-bottom: 24px;
-        box-sizing: border-box;
-    }
-}
-
-.card-wrap {
-    margin-bottom: 12px;
-}
-
-.card-box {
-    background: #fff;
-    border-radius: 8px;
-    padding: 12px 16px;
-    box-sizing: border-box;
-}
-
-.sampling-card {
-    .sampling-title {
-        font-size: 16px;
-        font-weight: 500;
-        color: #333;
-        margin-bottom: 6px;
-    }
-
-    .sampling-desc {
-        font-size: 14px;
-        color: #767676;
-        line-height: 1.5;
-    }
-}
-
-.photo-card {
-    .card-title {
-        font-size: 16px;
-        font-weight: bold;
-        color: #333;
-        margin-bottom: 12px;
-    }
-
-    .area-btns {
-        display: flex;
-        flex-wrap: nowrap;
-        gap: 8px;
-        margin-bottom: 12px;
-        overflow-x: auto;
-        padding-bottom: 4px;
-        -webkit-overflow-scrolling: touch;
-
-        &::-webkit-scrollbar {
-            display: none;
-        }
-
-        .area-btn {
-            padding: 0 14px;
-            height: 32px;
-            line-height: 32px;
-            font-size: 14px;
-            color: #333;
-            background: #f2f3f5;
-            border-radius: 16px;
-            white-space: nowrap;
-
-            &.active {
-                background: #2199f8;
-                color: #fff;
-            }
-        }
-    }
-
-    .ratio-tip {
-        font-size: 14px;
-        color: #2199f8;
-        padding: 10px 12px;
-        background: rgba(33, 153, 248, 0.08);
-        border-radius: 8px;
-    }
-}
-</style>

+ 0 - 728
src/views/old_mini/monitor/subPages/plan copy 2.vue

@@ -1,728 +0,0 @@
-<template>
-    <div class="plan-page">
-        <custom-header name="农事规划"></custom-header>
-        <div class="plan-content">
-            <div class="filter-wrap">
-                <div class="season-tabs">
-                    <div
-                        v-for="s in seasons"
-                        :key="s.value"
-                        class="season-tab"
-                        :class="{ active: s.value === activeSeason }"
-                        @click="handleSeasonClick(s.value)"
-                    >
-                        {{ s.label }}
-                    </div>
-                </div>
-                <div class="status-filter">
-                    <div v-for="status in statusList" :key="status.value" class="status-item" :class="status.color">
-                        <div class="status-dot"></div>
-                        <span class="status-text">{{ status.label }}</span>
-                    </div>
-                </div>
-            </div>
-            <div class="timeline-container" ref="timelineContainerRef">
-                <div class="timeline-list" :style="getListStyle">
-                    <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)"
-                    >
-                        <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" :class="{ 'no-wrap': getReproductiveItemHeight(p) <= 35 }">
-                                    <div
-                                        v-for="(fw, aIdx) in Array.isArray(r.farmWorkArrangeList)
-                                            ? r.farmWorkArrangeList
-                                            : []"
-                                        :key="fw.id ?? aIdx"
-                                        class="arrange-box"
-                                        :class="[
-                                            getArrangeStatusClass(fw),
-                                            {
-                                                'small-height': getReproductiveItemHeight(p) <= 35,
-                                                'two-chars': fw.farmWorkName && fw.farmWorkName.trim().length === 2,
-                                                'text-4-6': fw.farmWorkName && getTextLengthClass(fw.farmWorkName) === 'text-4-6',
-                                                'text-7-8': fw.farmWorkName && getTextLengthClass(fw.farmWorkName) === 'text-7-8'
-                                            }
-                                        ]"
-                                        @click="handleRowClick(fw)"
-                                    >
-                                        <span class="arrange-text">{{ formatTextWithLineBreak(fw.farmWorkName) }}</span>
-                                        <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>
-                        </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>
-            <div class="control-section">
-                <div class="toggle-group">
-                    <el-switch v-model="isDefaultEnabled" />
-                    <span class="toggle-label">{{ isDefaultEnabled ? "默认" : "" }}发起农情需求</span>
-                </div>
-                <div class="add-button-group">
-                    <div class="add-button button" @click="addNewTask">新增农事</div>
-                    <div class="button" @click="manageTask">农事管理</div>
-                </div>
-            </div>
-        </div>
-    </div>
-    <!-- 农事信息弹窗 -->
-    <detail-dialog ref="detailDialogRef" @triggerFarmWork="triggerFarmWork"></detail-dialog>
-    <!-- 新增:激活上传弹窗 -->
-    <active-upload-popup @handleUploadSuccess="getFarmWorkPlan"></active-upload-popup>
-</template>
-
-<script setup>
-import { reactive, ref, onMounted, computed, nextTick } from "vue";
-import customHeader from "@/components/customHeader.vue";
-import { useRouter, useRoute } from "vue-router";
-import detailDialog from "@/components/detailDialog.vue";
-import eventBus from "@/api/eventBus";
-import activeUploadPopup from "@/components/popup/activeUploadPopup.vue";
-import { ElMessage } from "element-plus";
-import { SuccessFilled, WarningFilled } from "@element-plus/icons-vue";
-const router = useRouter();
-const route = useRoute();
-
-// 状态列表数据
-const seasons = reactive([
-    { value: "spring", label: "春季" },
-    { value: "summer", label: "夏季" },
-    { value: "autumn", label: "秋季" },
-    { value: "winter", label: "冬季" },
-]);
-const activeSeason = ref("");
-
-const statusList = reactive([
-    { value: "pending", label: "待触发", color: "gray" },
-    { value: "executing", label: "待完成", color: "blue" },
-    { value: "completed", label: "已完成", color: "green" },
-    { value: "expired", label: "已过期", color: "orange" },
-]);
-
-const solarTerms = ref([]);
-const phenologyList = ref([]);
-
-// 获取当前季节
-const getCurrentSeason = () => {
-    const month = new Date().getMonth() + 1; // 1-12
-    if (month >= 3 && month <= 5) {
-        return "spring"; // 春季:3-5月
-    } else if (month >= 6 && month <= 8) {
-        return "summer"; // 夏季:6-8月
-    } else if (month >= 9 && month <= 10) {
-        return "autumn"; // 秋季:9-10月
-    } else {
-        return "winter"; // 冬季:12-2月
-    }
-};
-
-onMounted(() => {
-    getFarmWorkPlan();
-});
-
-const getFarmWorkPlan = () => {
-    // 如果不是首次加载,保存当前滚动位置
-    let savedScrollTop = 0;
-    if (!isInitialLoad.value && timelineContainerRef.value) {
-        savedScrollTop = timelineContainerRef.value.scrollTop || 0;
-    }
-    
-    VE_API.monitor
-        .farmWorkPlan({ farmId: route.query.farmId })
-        .then(({ data, code }) => {
-            if (code === 0) {
-                const list = Array.isArray(data?.solarTermsList) ? data.solarTermsList : [];
-                const filtered = list
-                    .filter((t) => t && t.type === 1)
-                    .map((t) => ({
-                        id:
-                            t.id ??
-                            t.solarTermsId ??
-                            t.termId ??
-                            `${t.name || t.solarTermsName || t.termName || "term"}-${t.createDate || ""}`,
-                        displayName: t.name || t.solarTermsName || t.termName || "节气",
-                        createDate: t.createDate || null,
-                        progress: Number(t.progress) || 0,
-                    }));
-                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 : [],
-                      }))
-                    : [];
-                
-                // 等待 DOM 更新后处理滚动
-                nextTick(() => {
-                    if (isInitialLoad.value) {
-                        // 首次加载:设置默认季节为当前季节,并自动滚动到对应位置
-                        const currentSeason = getCurrentSeason();
-                        handleSeasonClick(currentSeason);
-                        isInitialLoad.value = false;
-                    } else {
-                        // 非首次加载:恢复之前的滚动位置
-                        if (timelineContainerRef.value && savedScrollTop > 0) {
-                            timelineContainerRef.value.scrollTop = savedScrollTop;
-                        }
-                    }
-                });
-            }
-        })
-        .catch((error) => {
-            console.error("获取农事规划数据失败:", error);
-        });
-};
-
-// 切换开关状态
-const isDefaultEnabled = ref(true);
-// 新增农事
-const addNewTask = () => {
-    ElMessage.warning("该功能正在升级中,敬请期待");
-    // router.push({
-    //     path: "/modify_work",
-    //     query: { data: JSON.stringify(["生长异常"]), gardenId: 766, isAdd: true },
-    // });
-};
-
-const triggerFarmWork = () => {
-    eventBus.emit("activeUpload:show", {
-        gardenIdVal: route.query.farmId,
-        problemTitleVal: '请选择您出现' + curFarmObj.value.farmWorkName + '的时间',
-        arrangeIdVal: curFarmObj.value.id,
-    });
-};
-
-const curFarmObj = ref({});
-const handleRowClick = (item) => {
-    curFarmObj.value = item;
-    // 0:默认,1-4:正常,5:完成,6:预警
-    if (item.flowStatus === 5) {
-        router.push({
-            path: "/review_work",
-            query: {
-                miniJson: JSON.stringify({ id: item.farmWorkRecordId,goBack: true }),
-            },
-        });
-    } else if (item.flowStatus === null) {
-        detailDialogRef.value.showDialog(item.farmWorkId);
-    } else if (item.flowStatus === 6 || (item.flowStatus < 5 && item.flowStatus >= 0)) {
-        router.push({
-            path: "/completed_work",
-            query: {
-                miniJson: JSON.stringify({ id: item.farmWorkRecordId }),
-            },
-        });
-    }
-};
-
-const manageTask = () => {
-    router.push({
-        path: "/agri_services_manage",
-        query: {
-            type: "manage",
-        },
-    });
-};
-
-const detailDialogRef = ref(null);
-const timelineContainerRef = ref(null);
-// 标记是否为首次加载
-const isInitialLoad = ref(true);
-
-// 安全解析时间到时间戳(ms)
-const safeParseDate = (val) => {
-    if (!val) return NaN;
-    if (val instanceof Date) return val.getTime();
-    if (typeof val === "number") return val;
-    if (typeof val === "string") {
-        // 兼容 "YYYY-MM-DD HH:mm:ss" -> Safari
-        const s = val.replace(/-/g, "/").replace("T", " ");
-        const d = new Date(s);
-        return isNaN(d.getTime()) ? NaN : d.getTime();
-    }
-    return NaN;
-};
-
-// 计算节气列表容器高度与项位置
-const getListStyle = computed(() => {
-    const total = (solarTerms.value?.length || 0) * 100;
-    const minH = 50 + total + 50; // 上下各 50
-    return { minHeight: `${minH}px` };
-});
-
-const getTermStyle = (t) => {
-    const p = Math.max(0, Math.min(100, Number(t?.progress) || 0));
-    const total = (solarTerms.value?.length || 0) * 100;
-    const top = 50 + (p / 100) * total;
-    return {
-        position: "absolute",
-        top: `${top}px`,
-        left: 0,
-        transform: "translateY(-50%)",
-        width: "30px",
-        height: "20px",
-        display: "flex",
-        alignItems: "center",
-    };
-};
-
-// 点击季节 → 滚动到对应节气(立春/立夏/立秋/立冬)
-const handleSeasonClick = (seasonValue) => {
-    activeSeason.value = seasonValue;
-    const mapping = {
-        spring: "立春",
-        summer: "立夏",
-        autumn: "立秋",
-        winter: "立冬",
-    };
-    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 total = (solarTerms.value?.length || 0) * 100;
-    const targetTop = 50 + (p / 100) * total; // 内容内的像素位置
-    const wrap = timelineContainerRef.value;
-    if (!wrap) return;
-    const viewH = wrap.clientHeight || 0;
-    const maxScroll = Math.max(0, wrap.scrollHeight - viewH);
-    // 将目标位置稍微靠上(使用 0.35 视口高度做偏移)
-    let scrollTop = Math.max(0, targetTop - viewH * 0.1);
-    if (scrollTop > maxScroll) scrollTop = maxScroll;
-    wrap.scrollTo({ top: scrollTop, behavior: "smooth" });
-};
-
-// 物候期覆盖条样式(使用像素计算,避免 100% 高度为 0 的问题)
-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 total = (solarTerms.value?.length || 0) * 100; // 有效绘制区高度(px)
-    const topPx = 50 + (start / 100) * total;
-    const heightPx = Math.max(2, ((end - start) / 100) * total);
-    const now = Date.now();
-    const isFuture = Number.isFinite(item?.startTimeMs) ? item.startTimeMs > now : start > 0; // 无开始时间时按起点>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?.flowStatus;
-    if (t == null) return "status-default";
-    if (t >= 0 && t <= 4) return "status-normal";
-    if (t === 5) return "status-complete";
-    if (t === 6) return "status-warning";
-    return "status-default";
-};
-
-// 计算 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 total = (solarTerms.value?.length || 0) * 100;
-    const heightPx = Math.max(2, ((end - start) / 100) * total);
-    return heightPx;
-};
-
-// 计算 reproductive-item 的高度(px)
-// 由于 reproductive-list 使用 grid-auto-rows: 1fr,每个 item 会等分 phenology-bar 的高度
-const getReproductiveItemHeight = (phenologyItem) => {
-    const barHeight = getPhenologyBarHeight(phenologyItem);
-    const listLength = Array.isArray(phenologyItem?.reproductiveList) ? phenologyItem.reproductiveList.length : 1;
-    // 如果列表为空,返回 barHeight;否则等分
-    return listLength > 0 ? barHeight / listLength : barHeight;
-};
-
-// 根据文字长度返回对应的 class
-const getTextLengthClass = (text) => {
-    if (!text || typeof text !== "string") return "";
-    const len = text.trim().length;
-    if (len > 4 && len <= 6) return "text-4-6";
-    if (len > 6 && len <= 8) return "text-7-8";
-    return "";
-};
-
-// 处理文本,在括号前换行
-const formatTextWithLineBreak = (text) => {
-    if (!text || typeof text !== "string") return text;
-    // 在左括号前添加换行符
-    return text.replace(/([((])/g, "\n$1");
-};
-
-</script>
-
-<style scoped lang="scss">
-.plan-page {
-    width: 100%;
-    height: 100vh;
-    background: #fff;
-    .plan-content {
-        .filter-wrap {
-            background: #fff;
-            padding: 13px 12px;
-            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
-            border-radius: 0 0 20px 20px;
-            .status-filter {
-                background: #fff;
-                padding: 3px 17px;
-                display: flex;
-                align-items: center;
-                gap: 16px;
-                font-size: 12px;
-
-                .status-item {
-                    display: flex;
-                    align-items: center;
-                    justify-content: center;
-                    gap: 6px;
-                    flex: 1;
-                    &.gray {
-                        color: #c4c6c9;
-                        .status-dot {
-                            background-color: #c4c6c9;
-                        }
-                    }
-
-                    &.blue {
-                        color: #2199f8;
-                        .status-dot {
-                            background-color: #2199f8;
-                        }
-                    }
-
-                    &.green {
-                        color: #1ca900;
-                        .status-dot {
-                            background-color: #1ca900;
-                        }
-                    }
-
-                    &.orange {
-                        color: #ff953d;
-                        .status-dot {
-                            background-color: #ff953d;
-                        }
-                    }
-                    .status-dot {
-                        width: 6px;
-                        height: 6px;
-                        border-radius: 50%;
-                    }
-                }
-            }
-
-            .season-tabs {
-                display: flex;
-                gap: 8px;
-                margin-bottom: 12px;
-                .season-tab {
-                    flex: 1;
-                    padding: 7px;
-                    text-align: center;
-                    background: #f3f3f3;
-                    color: #898a8a;
-                    border-radius: 3px;
-                    border: 1px solid transparent;
-                    font-size: 12px;
-                }
-                .season-tab.active {
-                    background: #ffffff;
-                    color: #2199f8;
-                    border-color: #2199f8;
-                }
-            }
-        }
-        .timeline-container {
-            height: calc(100vh - 93px - 40px - 73px);
-            overflow: auto;
-            position: relative;
-            box-sizing: border-box;
-            padding: 0 12px;
-            .timeline-list {
-                position: relative;
-            }
-            .timeline-middle-line {
-                position: absolute;
-                left: 15px; /* 位于节气文字列中间(列宽约30px) */
-                top: 50px;
-                bottom: 50px;
-                width: 2px;
-                background: #e8e8e8;
-                z-index: 1;
-            }
-            .phenology-bar {
-                display: flex;
-                align-items: stretch;
-                justify-content: center;
-                box-sizing: border-box;
-                &::before {
-                    content: "";
-                    position: absolute;
-                    top: 0;
-                    left: 25px;
-                    width: calc(100vw - 100px);
-                    height: 100%;
-                    background: var(--bar-before-bg, rgba(201, 201, 201, 0.1));
-                    z-index: 1;
-                }
-                .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;
-                    word-break: break-all;
-                    writing-mode: vertical-rl;
-                    text-orientation: upright;
-                    letter-spacing: 3px;
-                    width: 100%;
-                    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;
-                    }
-                    .arranges {
-                        position: absolute;
-                        left: 48px; /* 列与中线右侧一段距离 */
-                        top: 50%;
-                        transform: translateY(-50%);
-                        z-index: 3;
-                        display: flex;
-                        flex-wrap: wrap;
-                        flex-direction: row;
-                        align-items: center;
-                        max-width: calc(100vw - 100px);
-                        gap: 16px;
-                        &.no-wrap {
-                            flex-wrap: nowrap;
-                        }
-                        .arrange-box {
-                            width: 36px;
-                            height: 36px;
-                            border: 1px solid rgba(199, 199, 199, 0.6);
-                            border-radius: 2px;
-                            background: #fff;
-                            color: #a5a7a9;
-                            display: flex;
-                            align-items: center;
-                            justify-content: center;
-                            box-sizing: border-box;
-                            position: relative;
-                            font-size: 12px;
-                            flex-shrink: 0;
-                            &.small-height {
-                                height: 20px;
-                                width: 70px;
-                                &.two-chars {
-                                    width: 35px;
-                                }
-                                &.text-4-6 {
-                                    width: 65px;
-                                }
-                                &.text-7-8 {
-                                    width: 66px;
-                                }
-                                .arrange-text {
-                                    writing-mode: vertical-lr;
-                                    white-space: pre-line;
-                                }
-                            }
-                            &.text-4-6 {
-                                width: 65px;
-                            }
-                            &.text-7-8 {
-                                width: 66px;
-                            }
-                            .arrange-text {
-                                writing-mode: horizontal-tb;
-                                line-height: 14px;
-                                text-align: center;
-                                padding-left: 3px;
-                                white-space: pre-line;
-                            }
-                            .status-icon {
-                                position: absolute;
-                                right: -10px;
-                                bottom: -10px;
-                                z-index: 3;
-                            }
-                            &::before {
-                                content: "";
-                                position: absolute;
-                                left: -4px;
-                                top: 50%;
-                                transform: translateY(-50%);
-                                width: 0;
-                                height: 0;
-                                border-top: 4px solid transparent;
-                                border-bottom: 4px solid transparent;
-                                border-right: 4px solid currentColor; /* 与文字/边框颜色一致 */
-                            }
-                        }
-                        .arrange-box.status-warning {
-                            border-color: #ff953d;
-                            color: #ff953d;
-                        }
-                        .arrange-box.status-complete {
-                            border-color: #1ca900;
-                            color: #1ca900;
-                        }
-                        .arrange-box.status-normal {
-                            border-color: #2199f8;
-                            color: #2199f8;
-                        }
-                    }
-                }
-            }
-            .reproductive-item + .reproductive-item {
-                border-top: 2px solid #fff;
-            }
-            .phenology-bar + .phenology-bar {
-                border-top: 2px solid #fff;
-            }
-            .timeline-term {
-                position: absolute;
-                width: 30px;
-                padding-right: 16px;
-                display: flex;
-                align-items: center;
-                z-index: 2; /* 置于中线之上 */
-                .term-name {
-                    display: inline-block;
-                    width: 100%;
-                    height: 46px;
-                    line-height: 30px;
-                    background: #fff;
-                    font-size: 13px;
-                    word-break: break-all;
-                    writing-mode: vertical-rl;
-                    text-orientation: upright;
-                    color: rgba(174, 174, 174, 0.6);
-                    letter-spacing: 2px;
-                    text-align: center;
-                }
-            }
-        }
-
-        // 控制区域样式
-        .control-section {
-            position: fixed;
-            width: 100%;
-            left: 0;
-            box-sizing: border-box;
-            bottom: 0px;
-            background: #fff;
-            padding: 16px 12px;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            border-top: 1px solid #f0f0f0;
-
-            .toggle-group {
-                display: flex;
-                align-items: center;
-                gap: 8px;
-
-                .toggle-label {
-                    font-size: 13px;
-                    color: #141414;
-                }
-            }
-
-            .add-button-group {
-                display: flex;
-                align-items: center;
-                gap: 8px;
-                .button {
-                    color: #2199f8;
-                    border-radius: 25px;
-                    padding: 9px 15px;
-                    border: 1px solid #2199f8;
-                }
-                .add-button {
-                    background: linear-gradient(120deg, #76c3ff 0%, #2199f8 100%);
-                    color: white;
-                    border: 1px solid transparent;
-                }
-            }
-        }
-    }
-}
-</style>

+ 0 - 1006
src/views/old_mini/monitor/subPages/plan copy.vue

@@ -1,1006 +0,0 @@
-<template>
-    <div class="plan-page">
-        <custom-header name="农事规划"></custom-header>
-        <div class="plan-content">
-            <div class="filter-wrap">
-                <div class="season-tabs">
-                    <div
-                        v-for="s in seasons"
-                        :key="s.value"
-                        class="season-tab"
-                        :class="{ active: s.value === activeSeason }"
-                        @click="activeSeason = s.value"
-                    >
-                        {{ s.label }}
-                    </div>
-                </div>
-                <div class="status-filter">
-                    <div v-for="status in statusList" :key="status.value" class="status-item" :class="status.color">
-                        <div class="status-dot"></div>
-                        <span class="status-text">{{ status.label }}</span>
-                    </div>
-                </div>
-            </div>
-
-            <!-- 三行循环时间线 -->
-            <div class="cycle-timeline-container">
-                <div class="cycle-timeline">
-                    <div
-                        v-for="(row, rowIndex) in timelineRows"
-                        :key="rowIndex"
-                        class="cycle-row"
-                        :class="{ 'odd-index': rowIndex % 2 === 1 }"
-                    >
-                        <div
-                            v-for="(item, itemIndex) in row.items"
-                            :key="itemIndex"
-                            class="cycle-item"
-                            @click="handleRowClick(item)"
-                            :class="[item.type + '-item']"
-                        >
-                            <!-- 节气节点 -->
-                            <template v-if="item.type === 'term'">
-                                <!-- <div class="cycle-term-dot"></div> -->
-                                <div class="cycle-term-label">{{ item.name || item.id }}</div>
-                            </template>
-                        </div>
-
-                        <!-- 生育期名称(根据时间范围显示在对应行) -->
-                        <div class="cycle-phenology-wrap" v-if="getPhenologyBarsForRow(rowIndex).length > 0">
-                            <div
-                                v-for="p in getPhenologyBarsForRow(rowIndex)"
-                                :key="p.id"
-                                class="cycle-label"
-                                :class="p.color"
-                                :style="
-                                    isOddVisualRow(rowIndex)
-                                        ? { right: p.left, width: p.width }
-                                        : { left: p.left, width: p.width }
-                                "
-                            >
-                                {{ p.name }}
-                                <div v-if="p.arranges && p.arranges.length" class="arranges">
-                                    <div v-for="a in p.arranges" :key="a.id" :class="['cycle-task-box', a.status]">
-                                        <div class="cycle-task-text">{{ a.farmWorkName || a.name }}</div>
-                                        <!-- 任务连接器 -->
-                                        <div class="cycle-task-connector"></div>
-                                        <div
-                                            v-if="a.status === 'complete' || a.status === 'warning'"
-                                            class="status-icon"
-                                            :class="a.status"
-                                        >
-                                            <el-icon v-if="a.status === 'complete'" size="16" color="#1CA900"
-                                                ><SuccessFilled
-                                            /></el-icon>
-                                            <el-icon v-else size="18" color="#FF953D"><WarnTriangleFilled /></el-icon>
-                                        </div>
-                                    </div>
-                                </div>
-                            </div>
-                        </div>
-                        <!-- 行连接器 -->
-                        <div
-                            v-if="rowIndex < timelineRows.length - 1"
-                            class="cycle-connector"
-                            :class="[
-                                rowIndex % 2 === 1 ? 'middle-connector' : 'top-connector',
-                                getConnectorColorClass(rowIndex),
-                            ]"
-                        >
-                            <img v-if="isConnectorGray(rowIndex)" src="@/assets/img/monitor/defalut-arrow.png" alt="" />
-                            <img v-else src="@/assets/img/monitor/arrow.png" alt="" />
-                        </div>
-                    </div>
-                </div>
-            </div>
-
-            <div class="control-section">
-                <div class="toggle-group">
-                    <el-switch v-model="isDefaultEnabled" />
-                    <span class="toggle-label">{{ isDefaultEnabled ? "默认" : "" }}发起农情需求</span>
-                </div>
-                <div class="add-button-group">
-                    <div class="add-button button" @click="addNewTask">新增农事</div>
-                    <div class="button" @click="manageTask">农事管理</div>
-                </div>
-            </div>
-        </div>
-    </div>
-    <!-- 农事信息弹窗 -->
-    <detail-dialog ref="detailDialogRef"></detail-dialog>
-    <!-- 新增:激活上传弹窗 -->
-    <active-upload-popup></active-upload-popup>
-</template>
-
-<script setup>
-import { reactive, ref, onMounted, nextTick, onBeforeUnmount } from "vue";
-import customHeader from "@/components/customHeader.vue";
-import { useRouter, useRoute } from "vue-router";
-import detailDialog from "@/components/detailDialog.vue";
-import activeUploadPopup from "@/components/popup/activeUploadPopup.vue";
-const router = useRouter();
-const route = useRoute();
-
-// 状态列表数据
-const seasons = reactive([
-    { value: "spring", label: "春季" },
-    { value: "summer", label: "夏季" },
-    { value: "autumn", label: "秋季" },
-    { value: "winter", label: "冬季" },
-]);
-const activeSeason = ref("spring");
-
-const statusList = reactive([
-    { value: "pending", label: "待触发", color: "gray" },
-    { value: "executing", label: "待完成", color: "blue" },
-    { value: "completed", label: "已完成", color: "green" },
-    { value: "expired", label: "已过期", color: "orange" },
-]);
-
-// 切换开关状态
-const isDefaultEnabled = ref(true);
-
-// 时间线行数据(由接口节气生成)
-const timelineRows = reactive([]);
-
-// 目标定位日期(当前生育期参考点)
-const targetDate = new Date("2025-04-04T00:00:00");
-// 每行“当前生育期”标记的位置样式(按行索引)
-const phenologyPositions = ref({});
-
-// 生育期条(按行分组)
-const phenologyBarsByRow = ref([]);
-// 每一行可视区域的实际像素宽度(用于将最小像素宽度换算为百分比)
-const rowWidths = ref([]);
-// 节气 id 到对象的索引,便于通过 id 查找节气日期
-let solarTermIdToTerm = {};
-// 接口返回的生育期数据
-const phenologyList = ref([]);
-
-// 安全日期解析(兼容 'YYYY-MM-DD HH:mm:ss' / 'YYYY/MM/DD HH:mm:ss')
-const parseDate = (val) => {
-    if (!val) return null;
-    if (val instanceof Date) return isNaN(val.getTime()) ? null : val;
-    if (typeof val === "number") return new Date(val);
-    if (typeof val === "string") {
-        // 统一到可被 Safari 解析的格式
-        const s = val.replace(/-/g, "/").replace("T", " ");
-        const d = new Date(s);
-        return isNaN(d.getTime()) ? null : d;
-    }
-    return null;
-};
-
-onMounted(() => {
-    getFarmWorkPlan();
-    window.addEventListener("resize", handleResize, { passive: true });
-});
-onBeforeUnmount(() => {
-    window.removeEventListener("resize", handleResize);
-});
-
-const handleResize = () => {
-    // 重新测量并基于最新宽度重算条目
-    nextTick(() => {
-        measureRowWidths();
-        // 需要基于最新数据重算
-        if (phenologyList.value && phenologyList.value.length && cachedValidSolarTerms.value) {
-            groupPhenologyBarsByRow(phenologyList.value, cachedValidSolarTerms.value);
-        }
-    });
-};
-
-// 缓存已过滤/排序后的节气用于重复计算
-const cachedValidSolarTerms = ref(null);
-
-const getFarmWorkPlan = () => {
-    const paramFarmId = Number(route.query.farmId) || undefined;
-    VE_API.monitor
-        .farmWorkPlan({ farmId: paramFarmId ?? 92844 }) // 优先使用路由传入的 farmId
-        .then(({ data, code }) => {
-            if (code === 0) {
-                const solarTermsList = data.solarTermsList;
-                // 仅保留 type === 1 的节气,按需要的顺序(示例:反转)
-                // 取 type===1 的节气,并按日期降序排序(晚到早)
-                const validSolarTerms = Array.isArray(solarTermsList)
-                    ? solarTermsList
-                          .filter((t) => t && t.type === 1 && t.createDate)
-                          .sort((a, b) => {
-                              const da = parseDate(a.createDate)?.getTime() ?? 0;
-                              const db = parseDate(b.createDate)?.getTime() ?? 0;
-                              return db - da;
-                          })
-                    : [];
-                cachedValidSolarTerms.value = validSolarTerms;
-                generateTimelineData(validSolarTerms);
-                computeCurrentPhenologyPositions(validSolarTerms, targetDate);
-                // 保存生育期数据并生成各行生育期条
-                phenologyList.value = Array.isArray(data.phenologyList) ? data.phenologyList : [];
-                // 生成 id->term 的索引
-                solarTermIdToTerm = {};
-                validSolarTerms.forEach((t) => {
-                    if (t && (t.id || t.solarTermsId)) solarTermIdToTerm[t.id ?? t.solarTermsId] = t;
-                });
-                // 先等待 DOM 渲染完成后测量每行宽度,再据此计算最小可显示宽度
-                nextTick(() => {
-                    measureRowWidths();
-                    groupPhenologyBarsByRow(phenologyList.value, validSolarTerms);
-                });
-            }
-        })
-        .catch((error) => {
-            console.error("获取农事规划数据失败:", error);
-        });
-};
-
-// 测量每一行生育期容器的实际宽度
-const measureRowWidths = () => {
-    const rows = document.querySelectorAll(".cycle-timeline .cycle-row");
-    const widths = [];
-    rows.forEach((rowEl, idx) => {
-        const wrap = rowEl.querySelector(".cycle-phenology-wrap");
-        widths[idx] = wrap ? wrap.offsetWidth : 0;
-    });
-    rowWidths.value = widths;
-};
-
-// 生成时间轴数据
-const generateTimelineData = (solarTerms) => {
-    // 清空
-    timelineRows.splice(0, timelineRows.length);
-
-    // 无数据则给一行示例
-    if (!solarTerms || solarTerms.length === 0) {
-        timelineRows.push({
-            items: [
-                { type: "task", status: "default", taskName: "梢期", taskDesc: "杀虫" },
-                { type: "term", name: "节气" },
-                { type: "task", status: "default", taskName: "梢期", taskDesc: "杀虫" },
-                { type: "term", name: "节气" },
-                { type: "task", status: "default", taskName: "梢期", taskDesc: "杀虫" },
-                { type: "term", name: "节气" },
-            ],
-        });
-        return;
-    }
-
-    const itemsPerRow = 6; // 任务/节气交替
-    const termsPerRow = 3; // 每行3个节气
-    const totalRows = Math.ceil(solarTerms.length / termsPerRow);
-
-    for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) {
-        const rowItems = [];
-        const startTermIndex = rowIndex * termsPerRow;
-
-        for (let i = 0; i < itemsPerRow; i++) {
-            if (i % 2 === 0) {
-                // 任务位
-                const taskData = getTaskDataForIndex(Math.floor(i / 2));
-                rowItems.push({
-                    type: "task",
-                    status: taskData.status,
-                    taskName: taskData.taskName,
-                    taskDesc: taskData.taskDesc,
-                    icon: taskData.icon,
-                });
-            } else {
-                // 节气位
-                const termIndex = startTermIndex + Math.floor(i / 2);
-                if (termIndex < solarTerms.length) {
-                    const term = solarTerms[termIndex] || {};
-                    rowItems.push({
-                        type: "term",
-                        status: "default",
-                        name: term.name || term.solarTermsName || term.termName || "节气",
-                        id: term.id,
-                        createDate: term.createDate,
-                    });
-                } else {
-                    // 不足时补任务
-                    rowItems.push({
-                        type: "task",
-                        status: "default",
-                        taskName: "梢期",
-                        taskDesc: "杀虫",
-                    });
-                }
-            }
-        }
-
-        timelineRows.push({ items: rowItems });
-    }
-};
-
-// 任务占位数据(可按需接后端)
-const getTaskDataForIndex = (index) => {
-    const defaultTasks = [
-        { status: "default", taskName: "梢期", taskDesc: "杀虫" },
-        { status: "active", taskName: "梢期", taskDesc: "营养" },
-        { status: "complete", taskName: "梢期", taskDesc: "修剪", icon: { type: "complete" } },
-        { status: "warning", taskName: "梢期", taskDesc: "施肥", icon: { type: "warning" } },
-        { status: "normal", taskName: "梢期", taskDesc: "灌溉", icon: { type: "normal" } },
-    ];
-    return defaultTasks[index % defaultTasks.length];
-};
-
-// 计算“当前生育期”在各行的定位(只在包含目标日期的那一行显示)
-const computeCurrentPhenologyPositions = (solarTerms, date) => {
-    phenologyPositions.value = {};
-    if (!Array.isArray(solarTerms) || solarTerms.length === 0 || !(date instanceof Date)) return;
-
-    const termsPerRow = 3;
-    const totalRows = Math.ceil(solarTerms.length / termsPerRow);
-
-    // 1) 找到最接近目标日期的节气(按时间升序)
-    const termsAsc = solarTerms
-        .filter((t) => t && t.createDate)
-        .slice()
-        .sort((a, b) => (parseDate(a.createDate)?.getTime() ?? 0) - (parseDate(b.createDate)?.getTime() ?? 0));
-    if (termsAsc.length === 0) return;
-
-    const targetMs = date.getTime();
-    let nearest = termsAsc[0];
-    let bestDiff = Math.abs((parseDate(nearest.createDate)?.getTime() ?? 0) - targetMs);
-    for (let i = 1; i < termsAsc.length; i++) {
-        const ms = parseDate(termsAsc[i].createDate)?.getTime() ?? 0;
-        const diff = Math.abs(ms - targetMs);
-        if (diff < bestDiff) {
-            bestDiff = diff;
-            nearest = termsAsc[i];
-        }
-    }
-
-    // 2) 将该节气映射回当前(降序)数组中的索引与行
-    const nearestIdxDesc = solarTerms.findIndex((t) => t && nearest && t.id === nearest.id);
-    const rowIndex = Math.max(0, Math.floor(nearestIdxDesc / termsPerRow));
-
-    const startIdx = rowIndex * termsPerRow;
-    const endIdx = Math.min(startIdx + termsPerRow - 1, solarTerms.length - 1);
-    if (startIdx > endIdx) return;
-
-    const rowTerms = solarTerms.slice(startIdx, endIdx + 1);
-    // 视觉顺序用于方向(偶数行正向,奇数行反向),但时间范围应取该行真实最早/最晚
-    const rowDates = rowTerms
-        .map((t) => parseDate(t?.createDate))
-        .filter((d) => d && !isNaN(d.getTime()))
-        .map((d) => d.getTime());
-    if (rowDates.length === 0) return;
-    const minMs = Math.min(...rowDates);
-    const maxMs = Math.max(...rowDates);
-    const rowStart = new Date(minMs);
-    const rowEnd = new Date(maxMs);
-
-    // 3) 若目标日期不在该行范围内,则就近夹到边界(避免跨行导致丢失)
-    let anchorMs = targetMs;
-    if (anchorMs < minMs) anchorMs = minMs;
-    if (anchorMs > maxMs) anchorMs = maxMs;
-
-    // 4) 计算在该行范围内的比例
-    const total = Math.max(1, maxMs - minMs);
-    const ratio = Math.max(0, Math.min(1, (anchorMs - minMs) / total));
-    const percent = `${(ratio * 100).toFixed(2)}%`;
-
-    // 5) 偶数行用 left,奇数行用 right,与 Z 字方向一致
-    if (rowIndex % 2 === 1) {
-        phenologyPositions.value[rowIndex] = { right: percent };
-    } else {
-        phenologyPositions.value[rowIndex] = { left: percent };
-    }
-};
-// moved above with other refs
-
-// 将生育期条按行计算定位与宽度
-const groupPhenologyBarsByRow = (phenologyList, solarTerms) => {
-    phenologyBarsByRow.value = [];
-    if (
-        !Array.isArray(phenologyList) ||
-        phenologyList.length === 0 ||
-        !Array.isArray(solarTerms) ||
-        solarTerms.length === 0
-    ) {
-        return;
-    }
-
-    const termsPerRow = 3;
-    const totalRows = Math.ceil(solarTerms.length / termsPerRow);
-
-    // 行范围:使用该行包含的节气最早/最晚时间(按真实时间线性映射)
-    const rowRanges = [];
-    for (let rowIndex = 0; rowIndex < totalRows; rowIndex++) {
-        const startIdx = rowIndex * termsPerRow;
-        const endIdx = Math.min(startIdx + termsPerRow - 1, solarTerms.length - 1);
-        const rowTerms = solarTerms.slice(startIdx, endIdx + 1);
-        const rowDates = rowTerms
-            .map((t) => parseDate(t?.createDate))
-            .filter((d) => d && !isNaN(d.getTime()))
-            .map((d) => d.getTime());
-        if (rowDates.length === 0) continue;
-        const minMs = Math.min(...rowDates);
-        const maxMs = Math.max(...rowDates);
-        const rowStart = new Date(minMs);
-        const rowEnd = new Date(maxMs);
-        const totalMs = Math.max(1, rowEnd.getTime() - rowStart.getTime());
-        rowRanges.push({ rowIndex, rowStart, rowEnd, totalMs });
-        phenologyBarsByRow.value[rowIndex] = [];
-    }
-
-    // 中点归属法:每条生育期归属到中点所在的行;在行内按 Z 字方向计算 left/right 与 width
-    phenologyList.forEach((p, pIndex) => {
-        const list = Array.isArray(p?.reproductiveList) ? p.reproductiveList : [];
-        const baseColorClass = pIndex % 2 === 0 ? "blue" : "orange";
-        list.forEach((r) => {
-            // 优先使用节气 id 对应的节气日期
-            let sTermDate = null;
-            let eTermDate = null;
-            if (r?.startSolarTermId && solarTermIdToTerm[r.startSolarTermId]?.createDate) {
-                sTermDate = parseDate(solarTermIdToTerm[r.startSolarTermId].createDate);
-            }
-            if (r?.endSolarTermId && solarTermIdToTerm[r.endSolarTermId]?.createDate) {
-                eTermDate = parseDate(solarTermIdToTerm[r.endSolarTermId].createDate);
-            }
-
-            const s = sTermDate || parseDate(r?.startDate);
-            const e = eTermDate || parseDate(r?.endDate);
-            if (!s || !e) return;
-            const start = new Date(Math.min(s.getTime(), e.getTime()));
-            const end = new Date(Math.max(s.getTime(), e.getTime()));
-            if (end < start) return;
-            const mid = new Date(start.getTime() + (end.getTime() - start.getTime()) / 2);
-
-            // 找到中点所在行;若不在任何行,则归最近行
-            let target = rowRanges.find(({ rowStart, rowEnd }) => mid >= rowStart && mid <= rowEnd);
-            if (!target) {
-                target = rowRanges.reduce((best, curr) => {
-                    const dist =
-                        mid < curr.rowStart
-                            ? curr.rowStart.getTime() - mid.getTime()
-                            : mid.getTime() - curr.rowEnd.getTime();
-                    if (!best || dist < best.dist) return { dist, curr };
-                    return best;
-                }, null)?.curr;
-            }
-            if (!target) return;
-
-            // 位置:基于真实的 startDate(不截断),确保相邻条的间距 = (后一个startDate - 前一个endDate) 的时间差映射
-            const startRatio = (start.getTime() - target.rowStart.getTime()) / target.totalMs;
-
-            // 宽度:基于真实的 endDate - startDate 的时间差
-            const actualDuration = end.getTime() - start.getTime();
-            const widthRatio = actualDuration / target.totalMs;
-
-            // 限制到行范围内
-            const leftRatio = Math.max(0, Math.min(1, startRatio));
-            const rightRatio = Math.max(0, Math.min(1, (end.getTime() - target.rowStart.getTime()) / target.totalMs));
-            let clampedWidthRatio = Math.max(0.001, Math.min(widthRatio, rightRatio - leftRatio));
-
-            // 强制最小显示宽度:若换算到像素后小于 CSS 中的 min-width:22px,则使用最小可见宽度
-            const MIN_LABEL_PX = 22; // 与样式 .cycle-label 的最小宽度保持一致
-            const rowWidthPx = rowWidths.value?.[target.rowIndex] || 0;
-            let leftPercent = leftRatio * 100;
-            let widthPercent = clampedWidthRatio * 100;
-            if (rowWidthPx > 0) {
-                const minPercent = (MIN_LABEL_PX / rowWidthPx) * 100;
-                if (widthPercent < minPercent) {
-                    widthPercent = minPercent;
-                }
-                // 若越界则左移以保证完全可见
-                if (leftPercent + widthPercent > 100) {
-                    leftPercent = Math.max(0, 100 - widthPercent);
-                }
-                // 回填为比例供后续使用
-                clampedWidthRatio = widthPercent / 100;
-            } else {
-                // 无法测量时,保底给一个不至于 0 的最小显示比例(以 360px 近似,22px/360≈6.1%)
-                if (widthPercent < 6.2) {
-                    widthPercent = 6.2;
-                    if (leftPercent + widthPercent > 100) leftPercent = Math.max(0, 100 - widthPercent);
-                    clampedWidthRatio = widthPercent / 100;
-                }
-            }
-
-            const isFuture = start.getTime() > Date.now();
-            const colorToUse = isFuture ? "" : baseColorClass;
-            // 组装农事安排:按 reproductiveId 归属到当前生育期
-            const arrangeList = Array.isArray(r.farmWorkArrangeList)
-                ? r.farmWorkArrangeList.filter((fw) => !fw.reproductiveId || fw.reproductiveId === r.id)
-                : [];
-            const arrangeItems = arrangeList.map((fw) => {
-                let status = "default";
-                const t = fw.farmWorkType;
-                if (t == null || t === 0) {
-                    status = "default";
-                } else if (t >= 1 && t <= 4) {
-                    status = "normal";
-                } else if (t === 5) {
-                    status = "complete";
-                } else if (t === 6) {
-                    status = "warning";
-                }
-                return {
-                    id: fw.id,
-                    name: fw.farmWorkName,
-                    status,
-                };
-            });
-
-            // // 附加两条测试数据:已完成、已过期
-            // arrangeItems.push(
-            //     { id: `${r.id}-test-complete`, name: "测试完成", status: "complete" },
-            //     { id: `${r.id}-test-warning`, name: "测试过期", status: "warning" }
-            // );
-
-            phenologyBarsByRow.value[target.rowIndex].push({
-                id: r.id || `${p.id || "p"}-${start.getTime()}-${end.getTime()}`,
-                name: r.name && r.name.trim() ? r.name.trim() : r.phenologyName || "生育期",
-                left: `${leftPercent.toFixed(4)}%`,
-                width: `${(clampedWidthRatio * 100).toFixed(4)}%`,
-                startTime: start.getTime(), // 用于排序,确保相邻条的顺序正确
-                color: colorToUse,
-                arranges: arrangeItems,
-            });
-        });
-    });
-
-    // 每行内部按 startTime 排序,确保相邻条的间距正确反映时间差
-    phenologyBarsByRow.value.forEach((rowBars) => {
-        rowBars.sort((a, b) => (a.startTime || 0) - (b.startTime || 0));
-    });
-};
-
-// 获取指定行的生育期条
-const getPhenologyBarsForRow = (rowIndex) => {
-    return phenologyBarsByRow.value[rowIndex] || [];
-};
-
-// 视觉奇偶:自下而上计算奇偶(与 UI Z 字一致)
-const isOddVisualRow = (rowIndex) => {
-    const total = timelineRows.length;
-    if (total <= 0) return rowIndex % 2 === 1;
-    const visualIndex = total - 1 - rowIndex;
-    return visualIndex % 2 === 1;
-};
-
-// 新增农事
-const addNewTask = () => {
-    router.push({
-        path: "/modify_work",
-        query: { data: JSON.stringify(["生长异常"]), gardenId: 766, isAdd: true },
-    });
-};
-
-const manageTask = () => {
-    router.push({
-        path: "/agri_services_manage",
-        query: {
-            type: "manage",
-        },
-    });
-};
-
-const detailDialogRef = ref(null);
-
-const handleRowClick = (item) => {
-    if (item.status === "complete") {
-        router.push({
-            path: "/review_work",
-            query: {
-                miniJson: JSON.stringify({ id: item.id,goBack: true })
-            },
-        });
-    } else if (item.type !== "term" && item.status === "default") {
-        detailDialogRef.value.showDialog();
-    } else if (item.status === "warning" || item.status === "normal") {
-        router.push({
-            path: "/completed_work",
-            query: {
-                miniJson: JSON.stringify({ id: item.id })
-            },
-        });
-        // router.push({
-        //     path: "/services_agri",
-        //     query: {
-        //         id: item.id,
-        //         status: item.status,
-        //     },
-        // });
-    }
-};
-
-// 行连接器颜色:若后续生育期未开始则灰色,否则保持其颜色(蓝/橙)
-const getConnectorColorClass = (rowIndex) => {
-    const nextIndex = rowIndex + 1;
-    const bars = getPhenologyBarsForRow(nextIndex);
-    if (!bars || bars.length === 0) return "";
-
-    const nextIsOddIndex = nextIndex % 2 === 1; // 奇数行为左侧连接器
-
-    const parsePercent = (val) => {
-        if (typeof val !== "string") return 0;
-        const n = parseFloat(val.replace("%", ""));
-        return isNaN(n) ? 0 : n;
-    };
-
-    let target = bars[0];
-    if (nextIsOddIndex) {
-        // 左侧:选最靠左的条
-        target = bars.reduce((best, cur) => (parsePercent(cur.left) < parsePercent(best.left) ? cur : best), bars[0]);
-    } else {
-        // 右侧:选最靠右的条(left + width 最大)
-        const score = (b) => parsePercent(b.left) + parsePercent(b.width);
-        target = bars.reduce((best, cur) => (score(cur) > score(best) ? cur : best), bars[0]);
-    }
-
-    // 未来(未开始)时,color 为空串;过去/当前一律显示蓝色
-    const hasStarted = !!target?.color;
-    return hasStarted ? "" : "connector-gray";
-};
-
-// 行连接器是否为灰色(用于切换箭头图片)
-const isConnectorGray = (rowIndex) => getConnectorColorClass(rowIndex) === "connector-gray";
-</script>
-
-<style scoped lang="scss">
-.plan-page {
-    width: 100%;
-    height: 100vh;
-    background: #fff;
-    .plan-content {
-        .filter-wrap {
-            background: #fff;
-            padding: 13px 12px;
-            box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
-            border-radius: 0 0 20px 20px;
-            .status-filter {
-                background: #fff;
-                padding: 3px 17px;
-                display: flex;
-                align-items: center;
-                gap: 16px;
-                font-size: 12px;
-
-                .status-item {
-                    display: flex;
-                    align-items: center;
-                    justify-content: center;
-                    gap: 6px;
-                    flex: 1;
-                    &.gray {
-                        color: #c4c6c9;
-                        .status-dot {
-                            background-color: #c4c6c9;
-                        }
-                    }
-
-                    &.blue {
-                        color: #2199f8;
-                        .status-dot {
-                            background-color: #2199f8;
-                        }
-                    }
-
-                    &.green {
-                        color: #1ca900;
-                        .status-dot {
-                            background-color: #1ca900;
-                        }
-                    }
-
-                    &.orange {
-                        color: #ff953d;
-                        .status-dot {
-                            background-color: #ff953d;
-                        }
-                    }
-                    .status-dot {
-                        width: 6px;
-                        height: 6px;
-                        border-radius: 50%;
-                    }
-                }
-            }
-
-            .season-tabs {
-                display: flex;
-                gap: 8px;
-                margin-bottom: 12px;
-                .season-tab {
-                    flex: 1;
-                    padding: 7px;
-                    text-align: center;
-                    background: #f3f3f3;
-                    color: #898a8a;
-                    border-radius: 3px;
-                    border: 1px solid transparent;
-                    font-size: 12px;
-                }
-                .season-tab.active {
-                    background: #ffffff;
-                    color: #2199f8;
-                    border-color: #2199f8;
-                }
-            }
-        }
-
-        // 循环时间线样式
-        .cycle-timeline-container {
-            padding: 35px 15px 25px;
-            height: calc(100vh - 135px - 69px - 60px);
-            overflow-y: auto;
-            overflow-x: hidden;
-            .cycle-timeline {
-                position: relative;
-                .cycle-row {
-                    position: relative;
-                    display: flex;
-                    justify-content: space-between;
-                    align-items: center;
-                    margin-bottom: 60px;
-                    padding-right: 30px;
-                    &.odd-index {
-                        padding: 0;
-                        padding-left: 30px;
-                        flex-direction: row-reverse;
-                        .cycle-phenology-wrap {
-                            left: 6px;
-                            width: calc(100% - 13px);
-                        }
-                    }
-
-                    &:last-child {
-                        margin-bottom: 0;
-                        .cycle-phenology-wrap {
-                            left: 20px;
-                            width: calc(100% - 10px);
-                        }
-                    }
-
-                    // 水平时间线
-                    &::before {
-                        content: "";
-                        position: absolute;
-                        top: 0;
-                        left: 6px;
-                        right: 6px;
-                        height: 5px;
-                        border-left: 2px solid #fff;
-                        border-right: 2px solid #fff;
-                        background: #e8e8e8;
-                        transform: translateY(-50%);
-                        z-index: 1;
-                    }
-
-                    .cycle-item {
-                        position: relative;
-                        z-index: 2;
-                        top: 12px;
-
-                        &.term-item {
-                            display: flex;
-                            flex-direction: column;
-                            align-items: center;
-                            top: -11px;
-
-                            .cycle-term-dot {
-                                width: 6px;
-                                height: 6px;
-                                background: #c7c7c7;
-                                border-radius: 50%;
-                                margin-bottom: 4px;
-                            }
-
-                            .cycle-term-label {
-                                font-size: 11px;
-                                color: #c7c7c7;
-                                margin-top: 16px;
-                            }
-
-                            &.active {
-                                .cycle-term-dot {
-                                    background: #858383;
-                                }
-                                .cycle-term-label {
-                                    color: #858383;
-                                }
-                            }
-                        }
-                    }
-
-                    .cycle-phenology-wrap {
-                        position: absolute;
-                        top: -23px;
-                        left: 6px;
-                        width: calc(100% - 10px);
-                        z-index: 3;
-                        height: 100px;
-                        overflow: hidden;
-                        .cycle-label {
-                            position: absolute;
-                            color: #4e4e4e;
-                            font-size: 12px;
-                            min-width: 24px;
-                            height: 20px;
-                            line-height: 20px;
-                            text-align: center;
-                            background: rgba(180, 182, 183, 0.1);
-                            border-bottom: 6px solid #e8e8e8;
-                        }
-                        .cycle-label + .cycle-label {
-                            border-right: 1px solid #fff;
-                        }
-                        .cycle-label.blue {
-                            color: #2199f8;
-                            background: rgba(33, 153, 248, 0.1);
-                            border-bottom-color: #2199f8;
-                        }
-                        .cycle-label.orange {
-                            color: #ff953d;
-                            background: #fff2e7;
-                            border-bottom-color: #ff953d;
-                        }
-                        .arranges {
-                            display: flex;
-                            gap: 8px;
-                            padding-top: 26px;
-                            justify-content: center;
-                            flex-wrap: nowrap;
-                            // 使用与任务框一致的视觉风格
-                            .cycle-task-box {
-                                border: 1px solid rgba(199, 199, 199, 0.5);
-                                border-radius: 2px;
-                                width: 36px;
-                                height: 36px;
-                                min-width: 36px;
-                                line-height: 15px;
-                                font-size: 12px;
-                                box-sizing: border-box;
-                                padding: 2px 0;
-                                text-align: center;
-                                position: relative;
-                                color: #c7c7c7;
-                                .status-icon {
-                                    position: absolute;
-                                    bottom: -10px;
-                                    right: -10px;
-                                }
-                            }
-
-                            .cycle-task-connector {
-                                position: absolute;
-                                top: -4px;
-                                left: 50%;
-                                transform: translateX(-50%);
-                                width: 0;
-                                height: 0;
-                                border-left: 4px solid transparent;
-                                border-right: 4px solid transparent;
-                                border-bottom: 4px solid #dde1e7;
-                            }
-
-                            .cycle-task-box.warning {
-                                border-color: #ff953d;
-                            }
-                            .cycle-task-box.warning .cycle-task-text {
-                                color: #ff953d;
-                            }
-                            .cycle-task-box.warning + .cycle-task-connector,
-                            .cycle-task-box.warning .cycle-task-connector {
-                                border-bottom-color: #ff953d;
-                            }
-
-                            .cycle-task-box.complete {
-                                border-color: #1ca900;
-                            }
-                            .cycle-task-box.complete .cycle-task-text {
-                                color: #1ca900;
-                            }
-                            .cycle-task-box.complete + .cycle-task-connector,
-                            .cycle-task-box.complete .cycle-task-connector {
-                                border-bottom-color: #1ca900;
-                            }
-
-                            .cycle-task-box.normal {
-                                border-color: #2199f8;
-                            }
-                            .cycle-task-box.normal .cycle-task-text {
-                                color: #2199f8;
-                            }
-                            .cycle-task-box.normal + .cycle-task-connector,
-                            .cycle-task-box.normal .cycle-task-connector {
-                                border-bottom-color: #2199f8;
-                            }
-                        }
-                    }
-
-                    .cycle-connector {
-                        position: absolute;
-                        right: 0;
-                        top: 45.5px;
-                        transform: translateY(-50%);
-                        width: 2px;
-                        height: 87px;
-                        border: 5px solid #9dcaf7;
-                        border-left: none;
-                        background: transparent;
-                        img{
-                            width: 13px;
-                            height: 13px;
-                            position: absolute;
-                            top: 50%;
-                            transform: translateY(-50%);
-                            left: -8px;
-                            z-index: 1;
-                        }
-
-                        &.top-connector {
-                            border-top-right-radius: 5px;
-                            border-bottom-right-radius: 5px;
-                            img{
-                                left: -2px;
-                            }
-                        }
-
-                        &.middle-connector {
-                            border-top-left-radius: 5px;
-                            border-bottom-left-radius: 5px;
-                            left: 0;
-                            border-right: none;
-                            border-left: 5px solid #9dcaf7;
-                        }
-
-                        // 动态颜色
-                        &.connector-gray {
-                            border-color: #c4c6c9;
-                        }
-                        &.connector-gray.middle-connector {
-                            border-left-color: #c4c6c9;
-                        }
-                    }
-                }
-            }
-        }
-
-        // 控制区域样式
-        .control-section {
-            position: fixed;
-            width: 100%;
-            left: 0;
-            box-sizing: border-box;
-            bottom: 0px;
-            background: #fff;
-            padding: 16px 12px;
-            display: flex;
-            justify-content: space-between;
-            align-items: center;
-            border-top: 1px solid #f0f0f0;
-
-            .toggle-group {
-                display: flex;
-                align-items: center;
-                gap: 8px;
-
-                .toggle-label {
-                    font-size: 13px;
-                    color: #141414;
-                }
-            }
-
-            .add-button-group {
-                display: flex;
-                align-items: center;
-                gap: 8px;
-                .button {
-                    color: #2199f8;
-                    border-radius: 25px;
-                    padding: 9px 15px;
-                    border: 1px solid #2199f8;
-                }
-                .add-button {
-                    background: linear-gradient(120deg, #76c3ff 0%, #2199f8 100%);
-                    color: white;
-                    border: 1px solid transparent;
-                }
-            }
-        }
-    }
-}
-</style>

+ 0 - 779
src/views/old_mini/monitor/subPages/plan.vue

@@ -1,779 +0,0 @@
-<template>
-    <div class="plan-page">
-        <custom-header
-            :name="headerTitle"
-            isGoBack
-            @goback="goback"
-            :isClose="route.query.headerTitle ? true : false"
-        ></custom-header>
-        <div class="plan-content">
-            <div class="plan-content-header" v-if="pageType === 'plant'">
-                <el-select
-                    class="select-item"
-                    v-model="specieValue"
-                    placeholder="选择品类"
-                    @change="() => getListMySchemes('left')"
-                >
-                    <el-option
-                        v-for="item in options"
-                        :key="item.id"
-                        :label="item.name"
-                        :value="item.defaultContainerId"
-                    />
-                </el-select>
-                <tab-list
-                    class="tabs-list"
-                    type="light"
-                    v-model="active"
-                    :tabs="tabs"
-                    :scrollType="scrollType"
-                    @change="handleTabChange"
-                />
-            </div>
-            <div class="tip-box">
-                <Highlight
-                    :keywords="['关注农事/托管农事', '取消关注']"
-                    source-string="提示:关注农事/托管农事 会触发农情互动,并且为您推送农事,取消关注 则不会为您推送任何与该农事相关的内容"
-                />
-            </div>
-            <div
-                class="timeline-wrap"
-                :class="{
-                    'timeline-container-plant-wrap': pageType == 'plant',
-                    'timeline-container-no-permission-wrap': !hasPlanPermission,
-                    'no-default-plan-wrap':
-                        active !== tabs[0]?.id && pageType == 'plant' && tabs[currentTabIndex]?.enabled == 0,
-                }"
-            >
-                <farm-work-plan-timeline
-                    :pageType="pageType"
-                    :farmId="route.query.farmId"
-                    :containerId="containerIdData"
-                    @row-click="handleRowClick"
-                    :disableClick="!hasPlanPermission || active === tabs[0]?.id"
-                    :isStandard="active === tabs[0]?.id"
-                    :schemeId="route.query.schemeId || active"
-                    ref="farmWorkPlanTimelineRef"
-                />
-            </div>
-        </div>
-        <div
-            class="custom-bottom-fixed-btns"
-            :class="{ center: active === tabs[0]?.id && pageType == 'plant' }"
-            v-has-permission="'农事规划'"
-        >
-            <div class="bottom-btn-group-wrap">
-                <div
-                    class="bottom-btn-group"
-                    :class="{ 'justify-center': active === tabs[0]?.id && pageType == 'plant' }"
-                >
-                    <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" v-show="active !== tabs[0]?.id">新增农事</div>
-            </div>
-            <template v-if="active !== tabs[0]?.id && pageType == 'plant' && tabs[currentTabIndex]?.enabled == 0">
-                <div class="bottom-btn-divider"></div>
-                <div class="bottom-btn primary-btn submit-btn" @click="handleSubmitPlan">提交方案</div>
-            </template>
-        </div>
-    </div>
-    <!-- 农事信息弹窗 -->
-    <detail-dialog ref="detailDialogRef" @triggerFarmWork="triggerFarmWork"></detail-dialog>
-    <!-- 复制方案弹窗 -->
-    <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 === tabs[0]?.id ? "复制为" : "方案名称" }}</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 === tabs[0]?.id ? "取消复制" : "删除方案" }}
-            </div>
-            <div class="btn btn-confirm" @click="handleConfirmCopyPlan">
-                {{ active === tabs[0]?.id ? "确定复制" : "确定设置" }}
-            </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"
-                        value-format="YYYY-MM-DD"
-                        v-model="item.startDate"
-                        :clearable="false"
-                        :editable="false"
-                        type="date"
-                        placeholder="选择日期"
-                        @change="(date) => handleStartDateChange(date, index)"
-                    />
-                </div>
-            </div>
-            <div class="phenology-footer-tip">
-                <span>注:</span>
-                <span class="text">请从上往下按照时间顺序填写日期</span>
-            </div>
-        </div>
-        <div class="phenology-footer" @click="handleConfirmPhenologySetting">确认设置</div>
-    </Popup>
-
-    <tip-popup
-        v-model:show="showTipPopup"
-        type="warning"
-        text2="还未完善此方案不可用"
-        :highlightText="highlightText"
-        buttonText="我知道了"
-        @confirm="handleBtn"
-    />
-</template>
-
-<script setup>
-import { ref, onMounted, computed, watch } from "vue";
-import { Popup, Highlight } from "vant";
-import customHeader from "@/components/customHeader.vue";
-import tabList from "@/components/pageComponents/TabList.vue";
-import FarmWorkPlanTimeline from "@/components/pageComponents/FarmWorkPlanTimeline.vue";
-import { useRouter, useRoute } from "vue-router";
-import detailDialog from "@/components/detailDialog.vue";
-import eventBus from "@/api/eventBus";
-import tipPopup from "@/components/popup/tipPopup.vue";
-import { ElMessage, ElMessageBox } from "element-plus";
-const router = useRouter();
-const route = useRoute();
-
-const showTipPopup = ref(false);
-const highlightText = ref("");
-const handleBtn = () => {
-    showTipPopup.value = false;
-    setTimeout(() => {
-        router.go(-1);
-    }, 10);
-};
-
-const goback = () => {
-    if (
-        tabs.value[currentTabIndex.value]?.enabled == 0 &&
-        pageType.value === "plant" &&
-        active.value !== tabs.value[0]?.id
-    ) {
-        highlightText.value = tabs.value[currentTabIndex.value]?.name;
-        showTipPopup.value = true;
-    } else {
-        router.go(-1);
-    }
-};
-
-const userInfoStr = localStorage.getItem("localUserInfo");
-const userInfo = userInfoStr ? JSON.parse(userInfoStr) : {};
-// 检查是否有"农事规划"权限
-const hasPlanPermission = computed(() => {
-    try {
-        const permissions = userInfo?.agriculturalPermissions || [];
-        return permissions.includes("农事规划");
-    } catch (error) {
-        console.error("解析用户信息失败:", error);
-        return false;
-    }
-});
-
-const pageType = ref("");
-const headerTitle = ref("");
-
-onMounted(() => {
-    pageType.value = route.query.pageType || "";
-    headerTitle.value = route.query.headerTitle || (pageType.value === "plant" ? "种植方案" : "农事规划");
-    if (!pageType.value) {
-        getFarmWorkPlanForPhenology();
-    } else {
-        getSpecieList();
-    }
-});
-
-// 获取品类列表
-const specieValue = ref(null);
-const options = ref([]);
-const getSpecieList = () => {
-    VE_API.farm.fetchSpecieList({ agriculturalId: userInfo?.agriculturalId }).then(({ data }) => {
-        if (data && data.length) {
-            options.value = data || [];
-            let schemeType = "left"; // 默认使用left
-            
-            if (sessionStorage.getItem("specieValue")) {
-                specieValue.value = sessionStorage.getItem("specieValue");
-                currentTabIndex.value = sessionStorage.getItem("currentTabIndex");
-                sessionStorage.removeItem("specieValue");
-                sessionStorage.removeItem("currentTabIndex");
-                
-                // 判断恢复的specieValue是否是最后一项
-                const currentIndex = data.findIndex(item => item.defaultContainerId === specieValue.value);
-                if (currentIndex === data.length - 1) {
-                    schemeType = "right";
-                }
-            } else {
-                specieValue.value = data[0]?.defaultContainerId;
-            }
-            getListMySchemes(schemeType);
-        }
-    });
-};
-
-// 获取方案列表
-const active = ref(null);
-const tabs = ref([]);
-// 控制标签滚动方向:'left' | 'right' | 'auto' | ''
-const scrollType = ref("auto");
-const containerIdData = ref(null);
-const getListMySchemes = (type = "auto") => {
-    VE_API.home.listMySchemes({ containerId: specieValue.value }).then(({ data }) => {
-        tabs.value = data || [];
-        containerIdData.value = tabs.value[0]?.containerId;
-        if (sessionStorage.getItem("active")) {
-            active.value = sessionStorage.getItem("active");
-            sessionStorage.removeItem("active");
-            currentTab.value = data[currentTabIndex.value];
-
-        } else {
-            if (type === "right") {
-                active.value = data[data.length - 1].id;
-            } else if (type === "left") {
-                active.value = data[0]?.id;
-            } else {
-                if(currentTab.value) {
-                    currentTab.value = data.filter((item) => item.id === currentTab.value?.id)?.[0];
-                } else {
-                    currentTab.value = data[0];
-                }
-                copyPlanName.value = currentTab.value.name;
-            }
-        }
-        scrollType.value = type;
-        getFarmWorkPlanForPhenology();
-    });
-};
-
-const currentTab = ref(null);
-const currentTabIndex = ref(0);
-const handleTabChange = (id, item, index) => {
-    active.value = id;
-    currentTab.value = item;
-    currentTabIndex.value = index;
-    getFarmWorkPlanForPhenology();
-};
-
-const mergedReproductiveList = ref([]);
-const containerSpaceTimeId = ref(null);
-const schemeId = ref(null);
-
-const getPhenologyList = async () => {
-    const params = {
-        containerSpaceTimeId: containerSpaceTimeId.value,
-        agriculturalId: userInfo?.agriculturalId,
-        farmId: route.query.farmId,
-    };
-    const res = await VE_API.monitor.listPhenology(params);
-    if (res.code === 0) {
-        // 将intervalDaysArr合并到mergedReproductiveList中
-        if (intervalDaysArr.value.length > 0 && res.data.length > 0) {
-            mergedReproductiveList.value = res.data.map((item, index) => {
-                return {
-                    ...item,
-                    intervalDays: intervalDaysArr.value[index],
-                };
-            });
-        }
-    }
-};
-
-const farmWorkIds = ref([]);
-const intervalDaysArr = ref([]);
-// 获取农事规划数据以获取 containerSpaceTimeId
-const getFarmWorkPlanForPhenology = async () => {
-    try {
-        const { data, code } = await VE_API.monitor.farmWorkPlan({
-            containerId: specieValue.value,
-            farmId: route.query.farmId,
-            schemeId: active.value,
-        });
-        if (code === 0 && data?.phenologyList?.[0]?.containerSpaceTimeId) {
-            containerSpaceTimeId.value = data.phenologyList[0].containerSpaceTimeId;
-            schemeId.value = data.schemeId;
-
-            // 收集所有farmWorkId
-            farmWorkIds.value = [];
-            intervalDaysArr.value = [];
-            data.phenologyList.forEach((phenology) => {
-                intervalDaysArr.value.push(phenology.intervalDays);
-                if (Array.isArray(phenology.reproductiveList)) {
-                    phenology.reproductiveList.forEach((reproductive) => {
-                        if (Array.isArray(reproductive.farmWorkArrangeList)) {
-                            reproductive.farmWorkArrangeList.forEach((farmWork) => {
-                                if (farmWork.farmWorkId && farmWork.isFollow !== 0) {
-                                    farmWorkIds.value.push(farmWork.farmWorkId);
-                                }
-                            });
-                        }
-                    });
-                }
-            });
-        }
-    } catch (error) {
-        console.error("获取农事规划数据失败:", error);
-    }
-};
-
-// 复制方案弹窗相关
-const showCopyPlan = ref(false);
-const showPhenologySetting = ref(false);
-const copyPlanName = ref("");
-const openCopyPlanPopup = async () => {
-    copyPlanName.value = "";
-    showCopyPlan.value = true;
-    if (active.value !== tabs.value[0]?.id) {
-        copyPlanName.value = currentTab.value.name;
-    }
-};
-
-// 物候期设置弹窗
-const handlePhenologySetting = async () => {
-    await getPhenologyList();
-    showPhenologySetting.value = true;
-};
-
-/**
- * 处理物候期开始时间变化
- * 当修改某个物候期的开始时间时,自动更新后续所有物候期的开始时间
- */
-const handleStartDateChange = (date, currentIndex) => {
-    if (!date || !mergedReproductiveList.value || mergedReproductiveList.value.length === 0) {
-        return;
-    }
-    
-    // 从当前修改的物候期开始,更新后续所有物候期的开始时间
-    for (let i = currentIndex; i < mergedReproductiveList.value.length - 1; i++) {
-        const currentItem = mergedReproductiveList.value[i];
-        const nextItem = mergedReproductiveList.value[i + 1];
-        
-        // 获取当前物候期的间隔天数
-        const intervalDays = currentItem.intervalDays || 0;
-        
-        if (intervalDays > 0 && currentItem.startDate) {
-            // 将日期字符串转换为时间戳(毫秒)
-            const currentStartDateTimestamp = new Date(currentItem.startDate).getTime();
-            
-            // 在时间戳基础上加上间隔天数(转换为毫秒:天数 * 24小时 * 60分钟 * 60秒 * 1000毫秒)
-            const nextStartDateTimestamp = currentStartDateTimestamp + (intervalDays * 24 * 60 * 60 * 1000);
-            
-            // 将时间戳转换回日期对象,然后格式化为 YYYY-MM-DD 格式
-            const nextStartDate = new Date(nextStartDateTimestamp);
-            const year = nextStartDate.getFullYear();
-            const month = String(nextStartDate.getMonth() + 1).padStart(2, '0');
-            const day = String(nextStartDate.getDate()).padStart(2, '0');
-            const nextStartDateStr = `${year}-${month}-${day}`;
-            
-            // 更新下一个物候期的开始时间
-            nextItem.startDate = nextStartDateStr;
-        }
-    }
-};
-
-/**
- * 确认物候期设置
- */
-const farmWorkPlanTimelineRef = ref(null);
-const handleConfirmPhenologySetting = async () => {
-    const params = {
-        farmId: route.query.farmId,
-        agriculturalId: userInfo?.agriculturalId,
-        items: mergedReproductiveList.value.map((item) => ({
-            phenologyId: item.id,
-            startDate: item.startDate,
-        })),
-    };
-    const res = await VE_API.monitor.batchSaveFarmPhenologyTime(params);
-    if (res.code === 0) {
-        ElMessage.success("设置成功");
-        showPhenologySetting.value = false;
-        getFarmWorkPlanForPhenology();
-        farmWorkPlanTimelineRef.value.updateFarmWorkPlan();
-    }
-};
-// 取消复制方案
-const handleCancelCopyPlan = () => {
-    if (active.value === tabs.value[0]?.id) {
-        showCopyPlan.value = false;
-    } else {
-        ElMessageBox.confirm("确定要删除该方案吗?", "提示", {
-            confirmButtonText: "确定",
-            cancelButtonText: "取消",
-            type: "warning",
-        }).then(() => {
-            VE_API.monitor
-                .deleteScheme({
-                    schemeId: active.value,
-                })
-                .then(({ code }) => {
-                    if (code === 0) {
-                        showCopyPlan.value = false;
-                        ElMessage.success("删除成功");
-                        getListMySchemes("left");
-                    }
-                });
-        });
-    }
-};
-
-// 确定复制方案
-const handleConfirmCopyPlan = () => {
-    if (!copyPlanName.value.trim()) {
-        ElMessage.warning("请输入方案名称");
-        return;
-    }
-    if (active.value === tabs.value[0]?.id) {
-        VE_API.monitor
-            .copyScheme({
-                containerId: specieValue.value,
-                sourceSchemeId: active.value,
-                schemeName: copyPlanName.value,
-            })
-            .then(({ code, data }) => {
-                if (code === 0) {
-                    showCopyPlan.value = false;
-                    ElMessage.success("复制成功");
-                    getListMySchemes("right");
-                    currentTab.value = data;
-                    currentTabIndex.value = (tabs.value.length - 1) + 1;
-                }
-            });
-    } else {
-        VE_API.monitor
-            .renameScheme({
-                schemeId: active.value,
-                name: copyPlanName.value,
-            })
-            .then(({ code }) => {
-                if (code === 0) {
-                    showCopyPlan.value = false;
-                    ElMessage.success("修改成功");
-                    getListMySchemes("auto");
-                }
-            });
-    }
-};
-
-// 验证药肥报价信息并返回结果
-// 返回: { allTrue: boolean, invalidIds: string[] }
-const validatePesticideFertilizerQuotes = async () => {
-    const { data, code } = await VE_API.monitor.batchValidatePesticideFertilizerQuotes({
-        ids: farmWorkIds.value,
-        schemeId: active.value,
-    });
-
-    if (code !== 0 || !data) {
-        return { allTrue: false, invalidIds: [] };
-    }
-
-    // 判断所有值是否都为true
-    const allTrue = Object.values(data).every((value) => value === true);
-
-    // 收集所有不为true的ID
-    const invalidIds = Object.keys(data).filter((key) => data[key] !== true);
-
-    return { allTrue, invalidIds };
-};
-
-// 提交方案
-const handleSubmitPlan = async () => {
-    // 调用验证方法,传入所有ids
-    if (farmWorkIds.value.length > 0) {
-        // 自动化完善农事处方中的药肥数据   用于本地测试代码  不要删除
-        // const res = await VE_API.monitor.completeMedicationData({
-        //     schemeId: active.value,
-        //     containerId: specieValue.value,
-        // });
-        // if (res.code === 0) {
-        //     ElMessage.success("自动化完善成功");
-        //     await getFarmWorkPlanForPhenology()
-        // }
-        const { allTrue } = await validatePesticideFertilizerQuotes();
-        if (allTrue) {
-            VE_API.monitor
-                .enableScheme({
-                    schemeId: active.value,
-                })
-                .then(({ code }) => {
-                    if (code === 0) {
-                        ElMessage.success("提交成功");
-                        if(route.query.headerTitle) {
-                            router.replace({
-                                path: "/home",
-                            });
-                        }else{
-                            getListMySchemes("auto");
-                        }
-                    }
-                });
-        } else {
-            ElMessage.warning("当前方案有未完善报价信息的农事,请先完善报价信息");
-        }
-    }
-};
-
-const savePlanPageInfo = () => {
-    if(route.query.pageType !== 'plant') {
-        return;
-    }
-    sessionStorage.setItem("specieValue", specieValue.value);
-    sessionStorage.setItem("active", active.value);
-    sessionStorage.setItem("currentTabIndex", currentTabIndex.value);
-};
-
-// 新增农事
-const addNewTask = () => {
-    savePlanPageInfo();
-    router.push({
-        path: "/add_work",
-        query: {
-            containerSpaceTimeId: containerSpaceTimeId.value,
-            schemeId: route.query.schemeId || schemeId.value,
-        },
-    });
-};
-
-const triggerFarmWork = () => {
-    eventBus.emit("activeUpload:show", {
-        gardenIdVal: route.query.farmId,
-        problemTitleVal: "请选择您出现" + curFarmObj.value.farmWorkName + "的时间",
-        arrangeIdVal: curFarmObj.value.id,
-        needExecutorVal: true,
-    });
-};
-
-const curFarmObj = ref({});
-const handleRowClick = (item) => {
-    curFarmObj.value = item;
-    savePlanPageInfo();
-    sessionStorage.setItem("farmWorkAndArrangeIds", JSON.stringify(item.invalidArr));
-    const enabled = tabs.value[currentTabIndex.value]?.enabled;
-    router.push({
-        path: "/modify",
-        query: {
-            id: item.id,
-            noPrice: pageType.value === "plant" ? true : false,
-            farmId: route.query.farmId,
-            farmWorkId: item.farmWorkId,
-            containerSpaceTimeId: item.containerSpaceTimeId,
-            isDefault: active.value === tabs.value[0]?.id,
-            enabled: enabled,
-            isEdit: item.isEdit,
-        },
-    });
-};
-</script>
-
-<style scoped lang="scss">
-.plan-page {
-    width: 100%;
-    height: 100vh;
-    background: #f5f7fb;
-    .plan-content {
-        .plan-content-header {
-            display: flex;
-            align-items: center;
-            gap: 12px;
-            margin: 10px 0 10px 12px;
-            .select-item {
-                width: 82px;
-                ::v-deep {
-                    .el-select__wrapper {
-                        box-shadow: none;
-                        border-radius: 25px;
-                        border: 0.5px solid rgba(153, 153, 153, 0.5);
-                    }
-                }
-            }
-            .tabs-list {
-                width: calc(100% - 94px);
-                margin-right: 8px;
-            }
-        }
-        .tip-box {
-            padding: 5px 10px;
-            background: rgba(33, 153, 248, 0.1);
-            border-radius: 5px;
-            font-size: 12px;
-            margin: 8px 10px;
-            color: #444;
-        }
-        .timeline-wrap {
-            height: calc(100vh - 90px - 80px);
-            padding: 0 10px 0 4px;
-            &.timeline-container-plant-wrap {
-                height: calc(100vh - 90px - 85px - 38px);
-            }
-            &.no-default-plan-wrap {
-                height: calc(100vh - 90px - 85px - 100px);
-            }
-            // 没有权限时,底部按钮不显示,高度增加 73px
-            &.timeline-container-no-permission-wrap {
-                height: calc(100vh - 90px - 60px);
-            }
-        }
-    }
-    // 控制区域样式
-    .custom-bottom-fixed-btns {
-        flex-direction: column;
-        .bottom-btn-group-wrap {
-            display: flex;
-            gap: 12px;
-            width: 100%;
-            justify-content: space-between;
-            .bottom-btn-group {
-                display: flex;
-                gap: 12px;
-                &.justify-center {
-                    justify-content: center;
-                    width: 100%;
-                }
-            }
-        }
-        &.center {
-            justify-content: center;
-            .bottom-btn {
-                padding: 10px 45px;
-            }
-        }
-        .bottom-btn-divider {
-            width: calc(100% + 24px);
-            height: 1px;
-            background: #f0f0f0;
-            margin: 10px -12px;
-        }
-        .submit-btn {
-            padding: 10px 83px;
-        }
-    }
-}
-.copy-plan-popup {
-    width: 100%;
-    padding: 50px 12px 20px 12px;
-    &::before {
-        content: "";
-        position: absolute;
-        top: 0;
-        left: 0;
-        width: 100%;
-        height: 136px;
-        pointer-events: none;
-        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;
-            }
-        }
-    }
-}
-
-.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-tip {
-        margin-top: 20px;
-        text-align: center;
-        .text{
-            color: rgba(0, 0, 0, 0.4);
-        }
-    }
-    .phenology-footer {
-        width: 100%;
-        text-align: center;
-        font-size: 16px;
-        margin-top: 10px;
-        color: #fff;
-        background: #2199f8;
-        border-radius: 25px;
-        padding: 10px 0;
-    }
-}
-</style>

+ 0 - 269
src/views/old_mini/monitor/subPages/reviewResults.vue

@@ -1,269 +0,0 @@
-<template>
-    <custom-header name="复核成效"></custom-header>
-    <div class="farm-dynamics">
-        <div class="task-content">
-            <div class="expert-content">
-                <template v-if="contentData.length > 0">
-                    <List
-                        v-model:loading="loading"
-                        :finished="finished"
-                        :offset="100"
-                        loading-text="加载中..."
-                        finished-text="没有更多了"
-                        @load="onLoad"
-                    >
-                        <div v-for="(section, index) in contentData" :key="index" class="content-section">
-                            <div class="section-id" :id="section.targetId"></div>
-                            <record-item
-                                :record-item-data="section"
-                                content-mode="serviceDetail"
-                                title-mode="default"
-                                class="recipe-item"
-                                titleRightDotText="全区"
-                                titleRightType="dot"
-                                showFarmImage
-                                @click="handleClick(section, index)"
-                            >
-                                <template #footer>
-                                    <div class="action-group" v-if="!section.reviewImage.length">
-                                        <div class="action-l">查看详情</div>
-                                        <div class="action-r">
-                                            <div
-                                                class="action-item warning-item"
-                                                :class="{ 'has-applied': section.hasApplied }"
-                                                @click.stop="handleApply(section, index)"
-                                            >
-                                                {{ section.hasApplied ? "已发起需求" : "发起需求" }}
-                                            </div>
-                                            <div class="action-item primary-item" @click.stop="handleUploadPhoto(section)">
-                                                上传照片
-                                            </div>
-                                        </div>
-                                    </div>
-                                </template>
-                            </record-item>
-                        </div>
-                    </List>
-                </template>
-                <empty
-                    v-else
-                    image="https://birdseye-img.sysuimars.com/birdseye-look-mini/custom-empty-image.png"
-                    image-size="80"
-                    description="暂无数据"
-                />
-            </div>
-        </div>
-    </div>
-    <!-- 需求发送成功弹窗 -->
-    <popup v-model:show="showApplyPopup" round class="apply-popup">
-        <img class="check-icon" src="@/assets/img/home/right.png" alt="" />
-        <div class="apply-text">需求发送成功</div>
-        <div class="apply-btn" @click="showApplyPopup = false">我知道了</div>
-    </popup>
-
-    <!-- 上传照片弹窗 -->
-    <review-upload-popup v-model="showUpload" :record-id="sectionId" @success="resetAndLoad" />
-</template>
-<script setup>
-import { ref, onMounted } from "vue";
-import recordItem from "@/components/recordItem.vue";
-import { Popup } from "vant";
-import customHeader from "@/components/customHeader.vue";
-import { useRouter, useRoute } from "vue-router";
-import { Empty,List } from "vant";
-import reviewUploadPopup from "@/components/popup/reviewUploadPopup.vue";
-
-const showApplyPopup = ref(false);
-const showUpload = ref(false);
-const router = useRouter();
-const route = useRoute();
-const sectionId = ref(null);
-// 上传照片处理函数
-const handleUploadPhoto = ({id}) => {
-    showUpload.value = true;
-    sectionId.value = id;
-};
-
-const handleApply = (section, index) => {
-    // // 更新当前项的发起需求状态
-    // showApplyPopup.value = true;
-    // section.hasApplied = true;
-};
-
-const contentData = ref([]);
-const loading = ref(false);
-const finished = ref(false);
-const page = ref(1);
-const limit = ref(10);
-
-const onLoad = () => {
-    if (finished.value) return;
-    loading.value = true;
-    const params = {
-        farmId: route.query.farmId,
-        flowStatus: 5,
-        page: page.value,
-        limit: limit.value,
-    };
-    VE_API.user.getDetailList(params)
-        .then((res) => {
-            const list = res?.data || [];
-            if (Array.isArray(list) && list.length > 0) {
-                contentData.value = contentData.value.concat(list);
-                page.value += 1;
-                if (list.length < limit.value) {
-                    finished.value = true;
-                }
-            } else {
-                finished.value = true;
-            }
-        })
-        .finally(() => {
-            loading.value = false;
-        });
-};
-
-const resetAndLoad = () => {
-    page.value = 1;
-    finished.value = false;
-    contentData.value = [];
-    onLoad();
-};
-
-onMounted(() => {
-    resetAndLoad();
-});
-
-const handleClick = (section) => {
-    router.push({
-        path: "/review_work",
-        query: {
-            miniJson: JSON.stringify({ id: section.id,goBack: true }),
-        },
-    });
-};
-</script>
-<style lang="scss" scoped>
-.farm-dynamics {
-    width: 100%;
-    height: 100vh;
-    background: #f5f7fb;
-    .task-content {
-        display: flex;
-        padding-top: 10px;
-        height: calc(100% - 40px);
-        .expert-content {
-            width: 100%;
-            height: 100%;
-            overflow: auto;
-            padding: 0 10px 10px;
-            box-sizing: border-box;
-            .content-section {
-                position: relative;
-                .section-id {
-                    position: absolute;
-                    top: 0;
-                    width: 100%;
-                    height: 1px;
-                }
-            }
-            // 空状态样式
-            :deep(.van-empty) {
-                display: flex;
-                align-items: center;
-                justify-content: center;
-                padding: 60px 0;
-                min-height: 200px;
-            }
-            .action-r {
-                color: #1d2129;
-            }
-            .action-group {
-                display: flex;
-                align-items: center;
-                justify-content: space-between;
-                padding-top: 8px;
-                margin-top: 8px;
-                border-top: 1px solid #f5f5f5;
-                .action-l {
-                    font-size: 12px;
-                }
-            }
-            .action-group {
-                .action-r {
-                    display: flex;
-                    align-items: center;
-                    .action-item {
-                        padding: 0 11px;
-                        height: 30px;
-                        line-height: 30px;
-                        border-radius: 20px;
-                        font-size: 12px;
-                        &.second-item {
-                            border: 1px solid #2199f8;
-                            color: #2199f8;
-                        }
-                        &.primary-item {
-                            background: #2199f8;
-                            color: #fff;
-                        }
-                        &.warning-item {
-                            background: rgba(255, 131, 29, 0.1);
-                            color: #ff831d;
-                            &.has-applied {
-                                background: transparent;
-                                color: #afafaf;
-                                pointer-events: none;
-                            }
-                        }
-                        &.cancel-item {
-                            color: #676767;
-                            border: 1px solid rgba(103, 103, 103, 0.2);
-                        }
-                    }
-                    .action-item + .action-item {
-                        margin-left: 5px;
-                    }
-                }
-                .apply-action {
-                    justify-content: flex-end;
-                    .default-item {
-                        border: 1px solid rgba(0, 0, 0, 0.4);
-                        color: rgba(0, 0, 0, 0.4);
-                    }
-                }
-            }
-        }
-    }
-}
-.apply-popup {
-    width: 75%;
-    padding: 28px 28px 20px;
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    justify-content: center;
-    .check-icon {
-        width: 68px;
-        height: 68px;
-        margin-bottom: 12px;
-    }
-    .apply-text {
-        font-size: 24px;
-        font-weight: 500;
-        margin-bottom: 32px;
-        text-align: center;
-    }
-    .apply-btn {
-        width: 100%;
-        box-sizing: border-box;
-        padding: 8px;
-        border-radius: 25px;
-        font-size: 16px;
-        background: #2199f8;
-        color: #fff;
-        text-align: center;
-    }
-}
-
-</style>

+ 15 - 15
src/views/old_mini/weather_detail/index.vue

@@ -19,6 +19,19 @@
       </div>
     </div>
 
+    <!-- 指标 Tab -->
+    <div class="tab-row">
+    <div
+        v-for="tab in tabs"
+        :key="tab.key"
+        class="tab-item"
+        :class="{ active: currentTab === tab.key }"
+        @click="currentTab = tab.key"
+    >
+        {{ tab.label }}
+    </div>
+    </div>
+
     <div class="weather-chart-container">
         <div class="weather-chart-wrapper">
             <div 
@@ -53,19 +66,6 @@
         </div>
     </div>
 
-    <!-- 指标 Tab -->
-    <div class="tab-row">
-      <div
-        v-for="tab in tabs"
-        :key="tab.key"
-        class="tab-item"
-        :class="{ active: currentTab === tab.key }"
-        @click="currentTab = tab.key"
-      >
-        {{ tab.label }}
-      </div>
-    </div>
-
     <!-- 风险列表 -->
     <div class="risk-list">
       <div
@@ -198,7 +198,6 @@ function getWeatherData() {
   background: #ffffff;
   border-radius: 8px;
   box-shadow: 0 2px 8px rgba(15, 35, 52, 0.06);
-  margin-bottom: 10px;
 
   .farm-left {
     flex: 1;
@@ -213,7 +212,7 @@ function getWeatherData() {
 
     .farm-name {
       font-size: 16px;
-      font-weight: 600;
+      font-weight: 500;
       color: #1d2129;
     }
 
@@ -249,6 +248,7 @@ function getWeatherData() {
   padding: 10px;
   box-shadow: 0 2px 8px rgba(15, 35, 52, 0.06);
   overflow: hidden;
+  margin-bottom: 10px;
 
   .weather-chart-wrapper {
     width: 100%;