Browse Source

Merge branch 'master' of http://www.sysuimars.cn:3000/feiniao/feiniao-farm-h5

lxf 1 week ago
parent
commit
a744225a1a

+ 23 - 31
src/views/old_mini/mine/pages/serviceDetail.vue

@@ -1,15 +1,17 @@
 <template>
     <div class="service-detail-page">
-        <custom-header name="服务详情"></custom-header>
+        <custom-header name="农场详情"></custom-header>
         <div class="service-detail-content">
             <farm-info-card
-                v-if="farmInfoData.farmName !== '' || farmInfoData.area !== '' || farmInfoData.variety !== '' || farmInfoData.address !== ''"
+                v-if="
+                    farmInfoData.farmName !== '' ||
+                    farmInfoData.area !== '' ||
+                    farmInfoData.variety !== '' ||
+                    farmInfoData.address !== ''
+                "
                 class="record-box"
                 :data="farmInfoData"
             >
-                <template #right>
-                    <div @click="handleChatFarm">在线沟通</div>
-                </template>
             </farm-info-card>
             <div class="farm-service-box">
                 <div class="service-title">
@@ -17,12 +19,16 @@
                     <span>农事服务</span>
                 </div>
                 <stats-box :stats-data="serviceStatsData" />
-                <div v-for="(section, index) in detailList" :key="index" class="content-section" @click="handleClick(section)">
+                <div
+                    v-for="(section, index) in detailList"
+                    :key="index"
+                    class="content-section"
+                >
                     <record-item
                         :record-item-data="section"
                         content-mode="serviceDetail"
                         title-mode="default"
-                        title-right-text="分享成果"
+                        title-right-text="生成成果报告"
                         class="recipe-item"
                         showFarmImage
                         @titleRightClick="handleTitleRightClick"
@@ -47,14 +53,12 @@ import customHeader from "@/components/customHeader.vue";
 import FarmInfoCard from "@/components/pageComponents/FarmInfoCard.vue";
 import StatsBox from "@/components/pageComponents/StatsBox.vue";
 import { ref, onMounted, computed } from "vue";
-import { useRoute, useRouter } from "vue-router";
+import { useRoute } from "vue-router";
 import { base_img_url2 } from "@/api/config";
 import recordItem from "@/components/recordItem.vue";
-import { ElMessage } from "element-plus";
 import reviewPopup from "@/views/old_mini/task_condition/components/reviewPopup.vue";
 import { Empty } from "vant";
 const route = useRoute();
-const router = useRouter();
 const farmIdVal = ref(null);
 onMounted(() => {
     farmIdVal.value = route.query.farmId;
@@ -73,11 +77,11 @@ const getFarmDetail = () => {
 // 计算属性,确保数据符合 FarmInfoCard 的验证要求
 const farmInfoData = computed(() => {
     return {
-        farmName: farmDetail.value.name || '',
-        area: (farmDetail.value.mianji ? farmDetail.value.mianji + '亩' : ''),
-        variety: farmDetail.value.typeName || '',
-        address: farmDetail.value.address || '',
-        maxWidth: '58px',
+        farmName: farmDetail.value.name || "",
+        area: farmDetail.value.mianji ? farmDetail.value.mianji + "亩" : "",
+        variety: farmDetail.value.typeName || "",
+        address: farmDetail.value.address || "",
+        maxWidth: "58px",
     };
 });
 
@@ -105,23 +109,11 @@ const getDetailList = () => {
     });
 };
 
-const handleClick = (section) => {
-    router.push(`/review_work?miniJson=${JSON.stringify({id:section.id,goBack: true})}`);
-};
-
-const handleChatFarm = () => {
-    if(farmDetail.value.farmersMiniUserId != null){
-        router.push(`/chat_frame?userId=${farmDetail.value.farmersMiniUserId}&farmId=${farmIdVal.value}`);
-    }else{
-        ElMessage.warning('尚未绑定用户,暂时无法沟通');
-    }
-};
-
 const reviewPopupRef = ref(null);
-const handleTitleRightClick = ({id, reviewImage}) => {
-    VE_API.z_farm_work_record.getTriggerImg({ farmWorkRecordId:id }).then(({ data }) => {
-        const preImg = data.length ? base_img_url2 + data[data.length - 1].cloudFilename : '';
-        const resImg = reviewImage?.length ? base_img_url2 + reviewImage[reviewImage.length - 1] : '';
+const handleTitleRightClick = ({ id, reviewImage }) => {
+    VE_API.z_farm_work_record.getTriggerImg({ farmWorkRecordId: id }).then(({ data }) => {
+        const preImg = data.length ? base_img_url2 + data[data.length - 1].cloudFilename : "";
+        const resImg = reviewImage?.length ? base_img_url2 + reviewImage[reviewImage.length - 1] : "";
         reviewPopupRef.value.handleShowPopup(id, preImg, resImg);
     });
 };

+ 93 - 65
src/views/old_mini/mine/pages/serviceRecords.vue

@@ -1,36 +1,37 @@
 <template>
     <div class="service-records-page">
         <custom-header name="服务记录"></custom-header>
-        <div class="select-group">
-            <el-select class="select-item" v-model="dateValue" placeholder="Select">
-                <el-option v-for="item in dateOptions" :key="item.value" :label="item.label" :value="item.value" />
-            </el-select>
-            <el-select class="select-item" v-model="areaValue" placeholder="Select">
-                <el-option v-for="item in areaOptions" :key="item.value" :label="item.label" :value="item.value" />
-            </el-select>
-        </div>
         <div class="record-list">
-            <farm-info-card
-                v-for="(item, index) in recordList"
+            <div
+                v-for="(item, index) in renderList"
                 :key="index"
-                :data="{
-                    farmName: item.farmName,
-                    area: item.mianji + '亩',
-                    variety: item.typeName,
-                    address: item.address,
-                    serviceCount: item.serviceCount,
-                }"
-                @click="handleItemClick(item)"
-                class="record-item"
-            />
+                class="record-card"
+                @click="handleItemClick(item.raw)"
+            >
+                <img class="thumb" :src="item.thumb" alt="农场缩略图" @error="handleImgError" />
+                <div class="card-body">
+                    <div class="card-header">
+                        <div class="title van-ellipsis">{{ item.title }}</div>
+                        <div class="date">2025.05.28</div>
+                    </div>
+                    <div class="line">
+                        <span class="label">农场名称:</span>
+                        <span class="text van-ellipsis">{{ item.farmName }}</span>
+                    </div>
+                    <div class="line">
+                        <span class="label">药肥处方:</span>
+                        <span class="text van-ellipsis">{{ item.prescription }}</span>
+                    </div>
+                </div>
+            </div>
         </div>
     </div>
 </template>
 <script setup>
-import { ref, onMounted } from "vue";
+import { ref, onMounted, computed } from "vue";
 import customHeader from "@/components/customHeader.vue";
-import FarmInfoCard from "@/components/pageComponents/FarmInfoCard.vue";
 import { useRouter } from "vue-router";
+import defaultThumb from "@/assets/img/home/farm.png";
 const router = useRouter();
 
 // 服务记录列表数据
@@ -43,64 +44,91 @@ onMounted(() => {
 const getUserList = async () => {
     const { data } = await VE_API.user.userList();
     if (data.length) {
-        recordList.value = data.filter(item => item.serviceCount != 0);
+        recordList.value = data.filter((item) => item.serviceCount != 0);
     }
 };
+const formatDate = (dateStr) => {
+    if (!dateStr) return "--";
+    const date = new Date(dateStr);
+    if (Number.isNaN(date.getTime())) return dateStr;
+    const y = date.getFullYear();
+    const m = `${date.getMonth() + 1}`.padStart(2, "0");
+    const d = `${date.getDate()}`.padStart(2, "0");
+    return `${y}.${m}.${d}`;
+};
+const renderList = computed(() =>
+    recordList.value.map((item) => {
+        const title = item.workName || item.typeName || item.variety || "农事服务";
+        const farmName = item.farmName || item.name || "未填写";
+        const prescription = item.prescription || item.drugPrescription || item.drugName || item.remark || "暂无信息";
+        const date = formatDate(item.serviceDate || item.planDate || item.updateTime || item.createTime);
+        const thumb = item.cover || item.imageUrl || item.imgUrl || item.thumb || defaultThumb;
+        return {
+            raw: item,
+            title,
+            farmName,
+            prescription,
+            date,
+            thumb,
+        };
+    })
+);
 // 处理列表项点击
 const handleItemClick = (data) => {
-    router.push(`/service_detail?farmId=${data.farmId}`);
+    router.push(`/review_work?miniJson={"id":"275579","goBack":true}`);
+};
+const handleImgError = (e) => {
+    e.target.src = defaultThumb;
 };
-
-const dateValue = ref("1");
-const dateOptions = [
-    { value: "1", label: "农事类型" },
-    { value: "2", label: "2" },
-    { value: "3", label: "3" },
-];
-const areaValue = ref("1");
-const areaOptions = [
-    { value: "1", label: "区域筛选" },
-    { value: "2", label: "2" },
-    { value: "3", label: "3" },
-];
 </script>
 <style lang="scss" scoped>
 .service-records-page {
     width: 100%;
     min-height: 100vh;
-    background: #f7f7f7;
-    .select-group {
+    background: #f5f5f5;
+    .record-list {
+        padding: 10px 12px 20px;
         display: flex;
-        padding-top: 6px;
-        .select-item {
-            ::v-deep {
-                .el-select__wrapper {
-                    text-align: center;
-                    gap: 2px;
-                    box-shadow: none;
-                    justify-content: center;
-                    background: none;
-                }
-                .el-select__selection {
-                    flex: none;
-                    width: fit-content;
-                }
-                .el-select__placeholder {
-                    position: static;
-                    transform: none;
-                    width: fit-content;
-                    color: rgba(0, 0, 0, 0.2);
+        flex-direction: column;
+        gap: 14px;
+    }
+    .record-card {
+        display: flex;
+        gap: 16px;
+        padding: 10px;
+        background: #ffffff;
+        border-radius: 16px;
+        align-items: center;
+        .thumb {
+            width: 64px;
+            height: 64px;
+            border-radius: 8px;
+            object-fit: cover;
+        }
+        .card-body {
+            width: calc(100% - 64px - 16px);
+            .card-header {
+                display: flex;
+                justify-content: space-between;
+                align-items: center;
+                .title {
+                    font-size: 16px;
+                    font-weight: 600;
+                    margin-bottom: 4px;
                 }
-                .el-select__caret {
-                    color: rgba(0, 0, 0, 0.2);
+                .date {
+                    font-size: 12px;
+                    color: #999999;
                 }
             }
+            .line {
+                font-size: 12px;
+                color: #666;
+            }
+            .label {
+                color: #bbb;
+            }
         }
     }
-    .record-list {
-        padding: 6px 12px;
-        height: calc(100vh - 100px);
-        overflow: auto;
-    }
 }
 </style>

+ 19 - 4
src/views/old_mini/modify_work/reviewWork.vue

@@ -1,6 +1,6 @@
 <template>
     <div class="work-wrap">
-        <custom-header name="农事成效" :isClose="paramsPage.goBack ? false : true"></custom-header>
+        <custom-header name="农事详情" :isClose="paramsPage.goBack ? false : true"></custom-header>
         <div class="work-content recheck-title" :class="{ 'no-bottom': curRole == '0' && (!workItem.reviewImage ||!workItem.reviewImage.length) }" v-loading="loading">
             <div class="tabs-content-item">
                 <div class="common-card-title">
@@ -235,11 +235,16 @@
             </div>
 
             <div
-                class="fixed-btn-wrap center"
+                class="fixed-btn-wrap"
                  v-if="curRole == '2'"
             >
-                <div class="fixed-btn excute" @click="handleShare" v-if="workItem.reviewImage && workItem.reviewImage.length">分享成果</div>
-                <div class="fixed-btn second" @click="handleRemindUser" v-else>提醒农户拍照</div>
+                <template v-if="workItem.reviewImage && workItem.reviewImage.length">
+                    <div class="fixed-btn more" @click="handleMore">查看更多农事</div>
+                    <div class="fixed-btn excute" @click="handleShare">生成成果报告</div>
+                </template>
+                <template v-else>
+                    <div class="fixed-btn second" @click="handleRemindUser">提醒农户拍照</div>
+                </template>
             </div>
             <div
                 class="fixed-btn-wrap center"
@@ -272,6 +277,7 @@ import reviewPopup from "@/views/old_mini/task_condition/components/reviewPopup.
 import uploadExecute from "@/views/old_mini/task_condition/components/uploadExecute.vue";
 
 const route = useRoute();
+const router = useRouter();
 const uploadExecuteRef = ref(null);
 const workItem = ref({});
 const curRole = ref("");
@@ -348,6 +354,10 @@ const handleRemindUser = () => {
     uploadExecuteRef.value.showPopup({...workItem.value, type: 'remindUser'});
 }
 
+const handleMore = () => {
+    router.push(`/service_detail?farmId=${workItem.value.farmId}`);
+}
+
 // 清理数据的函数
 const clearData = () => {
     workItem.value = {};
@@ -480,6 +490,11 @@ const handleUpload = ({ imgArr }) => {
                 &.excute {
                     background: linear-gradient(180deg, #FFD887, #ED9E1E);
                 }
+                &.more {
+                    background: #FFFFFF;
+                    border: 1px solid rgba(153, 153, 153, 0.5);
+                    color: #666666;
+                }
                 &.second {
                     background: #FFFFFF;
                     border: 1px solid #2199F8;

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

@@ -0,0 +1,728 @@
+<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>

+ 205 - 271
src/views/old_mini/monitor/subPages/plan.vue

@@ -2,25 +2,6 @@
     <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>
@@ -38,30 +19,38 @@
                                 class="reproductive-item"
                                 :class="{
                                     'horizontal-text': getReproductiveItemHeight(p) < 30,
-                                    'vertical-lr-text': getReproductiveItemHeight(p) >= 30
+                                    'vertical-lr-text': getReproductiveItemHeight(p) >= 30,
                                 }"
-                                :style="getReproductiveItemHeight(p) < 30 ? { '--item-height': `${getReproductiveItemHeight(p)}px` } : {}"
+                                :style="
+                                    getReproductiveItemHeight(p) < 30
+                                        ? { '--item-height': `${getReproductiveItemHeight(p)}px` }
+                                        : {}
+                                "
                             >
                                 {{ r.name }}
-                                <div class="arranges" :class="{ 'no-wrap': getReproductiveItemHeight(p) <= 35 }">
+                                <div class="arranges">
                                     <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'
-                                            }
-                                        ]"
+                                        class="arrange-card"
+                                        :class="getArrangeStatusClass(fw)"
                                         @click="handleRowClick(fw)"
                                     >
-                                        <span class="arrange-text">{{ formatTextWithLineBreak(fw.farmWorkName) }}</span>
+                                        <div class="card-header">
+                                            <div class="header-left">
+                                                <span class="farm-work-name">{{ fw.farmWorkName || "农事名称" }}</span>
+                                                <span class="tag-standard">标准农事</span>
+                                            </div>
+                                            <div class="header-right">托管农事</div>
+                                        </div>
+                                        <div class="card-content">
+                                            <span>温馨提示:在某某物候期之后,请密切关注荔枝,关注蒂蛀虫的出现!</span>
+                                            <span class="edit-link" @click.stop="handleEdit(fw)">编辑</span>
+                                        </div>
+                                        <div class="card-divider"></div>
+                                        <div class="card-footer" @click.stop="handleRowClick(fw)">查看详情</div>
                                         <div
                                             v-if="
                                                 getArrangeStatusClass(fw) === 'status-complete' ||
@@ -91,52 +80,30 @@
                     </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 class="custom-bottom-fixed-btns">
+            <div class="bottom-btn primary-btn" @click="addNewTask">新增农事</div>
         </div>
     </div>
     <!-- 农事信息弹窗 -->
     <detail-dialog ref="detailDialogRef" @triggerFarmWork="triggerFarmWork"></detail-dialog>
     <!-- 新增:激活上传弹窗 -->
-    <active-upload-popup @handleUploadSuccess="getFarmWorkPlan"></active-upload-popup>
+    <active-upload-popup ref="activeUploadPopupRef" :needExecutor="true" @handleUploadSuccess="getFarmWorkPlan"></active-upload-popup>
 </template>
 
 <script setup>
-import { reactive, ref, onMounted, computed, nextTick } from "vue";
+import { 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([]);
 
@@ -164,7 +131,7 @@ const getFarmWorkPlan = () => {
     if (!isInitialLoad.value && timelineContainerRef.value) {
         savedScrollTop = timelineContainerRef.value.scrollTop || 0;
     }
-    
+
     VE_API.monitor
         .farmWorkPlan({ farmId: route.query.farmId })
         .then(({ data, code }) => {
@@ -196,7 +163,7 @@ const getFarmWorkPlan = () => {
                           reproductiveList: Array.isArray(it.reproductiveList) ? it.reproductiveList : [],
                       }))
                     : [];
-                
+
                 // 等待 DOM 更新后处理滚动
                 nextTick(() => {
                     if (isInitialLoad.value) {
@@ -232,7 +199,7 @@ const addNewTask = () => {
 const triggerFarmWork = () => {
     eventBus.emit("activeUpload:show", {
         gardenIdVal: route.query.farmId,
-        problemTitleVal: '请选择您出现' + curFarmObj.value.farmWorkName + '的时间',
+        problemTitleVal: "请选择您出现" + curFarmObj.value.farmWorkName + "的时间",
         arrangeIdVal: curFarmObj.value.id,
     });
 };
@@ -245,7 +212,7 @@ const handleRowClick = (item) => {
         router.push({
             path: "/review_work",
             query: {
-                miniJson: JSON.stringify({ id: item.farmWorkRecordId,goBack: true }),
+                miniJson: JSON.stringify({ id: item.farmWorkRecordId, goBack: true }),
             },
         });
     } else if (item.flowStatus === null) {
@@ -260,12 +227,17 @@ const handleRowClick = (item) => {
     }
 };
 
-const manageTask = () => {
-    router.push({
-        path: "/agri_services_manage",
-        query: {
-            type: "manage",
-        },
+const activeUploadPopupRef = ref(null);
+const handleEdit = (item) => {
+    eventBus.emit("activeUpload:show", {
+        gardenIdVal: route.query.farmId,
+        problemTitleVal: '请选择 ' + item.farmWorkName + ' 执行截至时间',
+        arrangeIdVal: item.id,
+        modeVal: 'interact', // 设置为互动设置模式
+        interactTitleVal: item.farmWorkName || '梢期杀虫', // 使用农事名称作为标题
+        interactTimeVal: item.interactTime || '', // 如果有已保存的互动时间
+        forceTriggerTimeVal: item.forceTriggerTime || '', // 如果有已保存的强制触发时间
+        interactQuestionVal: item.interactQuestion, // 如果有已保存的互动问题
     });
 };
 
@@ -288,26 +260,47 @@ const safeParseDate = (val) => {
     return NaN;
 };
 
+// 计算最小progress值(第一个节气的progress)
+const minProgress = computed(() => {
+    if (!solarTerms.value || solarTerms.value.length === 0) return 0;
+    const progresses = solarTerms.value.map((t) => Number(t?.progress) || 0).filter((p) => !isNaN(p));
+    return progresses.length > 0 ? Math.min(...progresses) : 0;
+});
+
+// 计算最大progress值
+const maxProgress = computed(() => {
+    if (!solarTerms.value || solarTerms.value.length === 0) return 100;
+    const progresses = solarTerms.value.map((t) => Number(t?.progress) || 0).filter((p) => !isNaN(p));
+    return progresses.length > 0 ? Math.max(...progresses) : 100;
+});
+
 // 计算节气列表容器高度与项位置
 const getListStyle = computed(() => {
-    const total = (solarTerms.value?.length || 0) * 100;
-    const minH = 50 + total + 50; // 上下各 50
+    const minP = minProgress.value;
+    const maxP = maxProgress.value;
+    const range = Math.max(1, maxP - minP); // 避免除0
+    const total = (solarTerms.value?.length || 0) * 320;
+    const minH = total; // 无上下留白
     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;
+    const minP = minProgress.value;
+    const maxP = maxProgress.value;
+    const range = Math.max(1, maxP - minP); // 避免除0
+    const total = (solarTerms.value?.length || 0) * 320;
+    // 将progress映射到0开始的位置,最小progress对应top: 0
+    const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
+    const top = (normalizedP / 100) * total;
     return {
         position: "absolute",
         top: `${top}px`,
         left: 0,
-        transform: "translateY(-50%)",
         width: "30px",
         height: "20px",
         display: "flex",
-        alignItems: "center",
+        alignItems: "flex-start",
     };
 };
 
@@ -325,8 +318,12 @@ const handleSeasonClick = (seasonValue) => {
     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 minP = minProgress.value;
+    const maxP = maxProgress.value;
+    const range = Math.max(1, maxP - minP);
+    const total = (solarTerms.value?.length || 0) * 320;
+    const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
+    const targetTop = (normalizedP / 100) * total; // 内容内的像素位置
     const wrap = timelineContainerRef.value;
     if (!wrap) return;
     const viewH = wrap.clientHeight || 0;
@@ -343,9 +340,38 @@ const getPhenologyBarStyle = (item) => {
     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 minP = minProgress.value;
+    const maxP = maxProgress.value;
+    const range = Math.max(1, maxP - minP);
+    const total = (solarTerms.value?.length || 0) * 320; // 有效绘制区高度(px)
+    // 将progress映射到0开始的位置
+    const normalizedStart = range > 0 ? ((start - minP) / range) * 100 : 0;
+    const normalizedEnd = range > 0 ? ((end - minP) / range) * 100 : 0;
+    let topPx = (normalizedStart / 100) * total;
+    let heightPx = Math.max(2, ((normalizedEnd - normalizedStart) / 100) * total);
+
+    // 计算第一个节气的位置(minProgress对应的位置,应该是0)
+    const firstTermTop = 0; // 第一个节气在top: 0
+    // 节气文字高度为46px,"小"字大约在文字的上半部分,约在15px位置
+    // 让蓝色条的顶部对齐到"小"字的位置(firstTermTop + 15px)
+    const minTop = firstTermTop + 10; // 对齐到"小"字位置(文字高度的约33%)
+    if (topPx < minTop) {
+        // 如果顶部小于最小位置,调整top和height
+        const diff = minTop - topPx;
+        topPx = minTop;
+        heightPx = Math.max(2, heightPx - diff);
+    }
+
+    // 计算最后一个节气的位置(maxProgress对应的位置)
+    const lastTermTop = (100 / 100) * total; // 因为normalizedEnd最大是100
+    // 节气文字高度为46px,"至"字大约在文字的下半部分,约在30px位置
+    // 让蓝色条的底部对齐到"至"字的位置(lastTermTop + 30px)
+    const maxBottom = lastTermTop + 35; // 对齐到"至"字位置(文字高度的约65%)
+    const barBottom = topPx + heightPx;
+    if (barBottom > maxBottom) {
+        heightPx = Math.max(2, maxBottom - topPx);
+    }
+
     const now = Date.now();
     const isFuture = Number.isFinite(item?.startTimeMs) ? item.startTimeMs > now : start > 0; // 无开始时间时按起点>0近似
     // 反转:大于当前时间用灰色,小于等于当前时间用蓝色
@@ -380,8 +406,14 @@ const getPhenologyBarHeight = (item) => {
     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);
+    const minP = minProgress.value;
+    const maxP = maxProgress.value;
+    const range = Math.max(1, maxP - minP);
+    const total = (solarTerms.value?.length || 0) * 320;
+    // 将progress映射到0开始的位置
+    const normalizedStart = range > 0 ? ((start - minP) / range) * 100 : 0;
+    const normalizedEnd = range > 0 ? ((end - minP) / range) * 100 : 0;
+    const heightPx = Math.max(2, ((normalizedEnd - normalizedStart) / 100) * total);
     return heightPx;
 };
 
@@ -409,7 +441,6 @@ const formatTextWithLineBreak = (text) => {
     // 在左括号前添加换行符
     return text.replace(/([((])/g, "\n$1");
 };
-
 </script>
 
 <style scoped lang="scss">
@@ -418,83 +449,9 @@ const formatTextWithLineBreak = (text) => {
     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;
-                }
-            }
-        }
+        padding: 12px 0;
         .timeline-container {
-            height: calc(100vh - 93px - 40px - 73px);
+            height: calc(100vh - 40px - 73px);
             overflow: auto;
             position: relative;
             box-sizing: border-box;
@@ -505,8 +462,8 @@ const formatTextWithLineBreak = (text) => {
             .timeline-middle-line {
                 position: absolute;
                 left: 15px; /* 位于节气文字列中间(列宽约30px) */
-                top: 50px;
-                bottom: 50px;
+                top: 0;
+                bottom: 0;
                 width: 2px;
                 background: #e8e8e8;
                 z-index: 1;
@@ -516,16 +473,16 @@ const formatTextWithLineBreak = (text) => {
                 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;
-                }
+                // &::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; /* 子项等高,整体等分父高度 */
@@ -561,92 +518,106 @@ const formatTextWithLineBreak = (text) => {
                     .arranges {
                         position: absolute;
                         left: 48px; /* 列与中线右侧一段距离 */
-                        top: 50%;
-                        transform: translateY(-50%);
+                        top: 0;
                         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;
+                        gap: 12px;
+                        letter-spacing: 0px;
+                        .arrange-card {
+                            width: 94%;
+                            border: 0.5px solid #2199f8;
+                            border-radius: 8px;
                             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;
+                            padding: 8px;
+                            writing-mode: horizontal-tb;
+                            .card-header {
+                                display: flex;
+                                justify-content: space-between;
+                                align-items: center;
+                                .header-left {
+                                    display: flex;
+                                    align-items: center;
+                                    gap: 8px;
+                                    .farm-work-name {
+                                        font-size: 14px;
+                                        font-weight: 500;
+                                        color: #1d2129;
+                                    }
+                                    .tag-standard {
+                                        padding: 0 8px;
+                                        background: rgba(119, 119, 119, 0.1);
+                                        border-radius: 25px;
+                                        font-weight: 400;
+                                        font-size: 11px;
+                                        color: #000;
+                                    }
                                 }
-                                &.text-4-6 {
-                                    width: 65px;
-                                }
-                                &.text-7-8 {
-                                    width: 66px;
-                                }
-                                .arrange-text {
-                                    writing-mode: vertical-lr;
-                                    white-space: pre-line;
+                                .header-right {
+                                    font-size: 11px;
+                                    color: #2199f8;
+                                    padding: 0 8px;
+                                    border-radius: 25px;
+                                    // background: rgba(33, 153, 248, 0.1);
                                 }
                             }
-                            &.text-4-6 {
-                                width: 65px;
+                            .card-content {
+                                color: #909090;
+                                text-align: left;
+                                line-height: 1.55;
+                                margin: 4px 0 10px 0;
+                                .edit-link {
+                                    color: #2199f8;
+                                }
                             }
-                            &.text-7-8 {
-                                width: 66px;
+                            .card-divider {
+                                height: 0.5px;
+                                background: rgba(0, 0, 0, 0.1);
                             }
-                            .arrange-text {
-                                writing-mode: horizontal-tb;
-                                line-height: 14px;
+                            .card-footer {
                                 text-align: center;
-                                padding-left: 3px;
-                                white-space: pre-line;
+                                font-size: 12px;
+                                color: #adadad;
+                                padding-top: 6px;
                             }
                             .status-icon {
                                 position: absolute;
-                                right: -10px;
-                                bottom: -10px;
+                                right: -8px;
+                                bottom: -8px;
                                 z-index: 3;
                             }
                             &::before {
                                 content: "";
                                 position: absolute;
-                                left: -4px;
+                                left: -6px;
                                 top: 50%;
                                 transform: translateY(-50%);
                                 width: 0;
                                 height: 0;
-                                border-top: 4px solid transparent;
-                                border-bottom: 4px solid transparent;
-                                border-right: 4px solid currentColor; /* 与文字/边框颜色一致 */
+                                border-top: 5px solid transparent;
+                                border-bottom: 5px solid transparent;
+                                border-right: 5px solid #2199f8;
                             }
                         }
-                        .arrange-box.status-warning {
+                        .arrange-card.status-warning {
                             border-color: #ff953d;
-                            color: #ff953d;
+                            &::before {
+                                border-right-color: #ff953d;
+                            }
                         }
-                        .arrange-box.status-complete {
+                        .arrange-card.status-complete {
                             border-color: #1ca900;
-                            color: #1ca900;
+                            &::before {
+                                border-right-color: #1ca900;
+                            }
                         }
-                        .arrange-box.status-normal {
+                        .arrange-card.status-normal {
                             border-color: #2199f8;
-                            color: #2199f8;
+                            &::before {
+                                border-right-color: #2199f8;
+                            }
                         }
                     }
                 }
@@ -662,7 +633,7 @@ const formatTextWithLineBreak = (text) => {
                 width: 30px;
                 padding-right: 16px;
                 display: flex;
-                align-items: center;
+                align-items: flex-start;
                 z-index: 2; /* 置于中线之上 */
                 .term-name {
                     display: inline-block;
@@ -675,53 +646,16 @@ const formatTextWithLineBreak = (text) => {
                     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;
-                }
-            }
+    }
+    // 控制区域样式
+    .custom-bottom-fixed-btns {
+        justify-content: center;
+        .primary-btn {
+            padding: 10px 34px;
         }
     }
 }

+ 231 - 536
src/views/old_mini/plan/index copy.vue

@@ -1,581 +1,276 @@
 <template>
-    <div class="plan-page">
+    <div class="farm-card-page">
         <custom-header name="农事方案"></custom-header>
-        <div class="plan-title">
-            <div class="tabs">
-                <div class="tab" :class="{ active: activeTab === 'left' }" @click="setActiveTab('left')">专家方案</div>
-                <div class="tab" :class="{ active: activeTab === 'right' }" @click="setActiveTab('right')">
-                    我的方案
-                    <span class="badge-dot">2</span>
+        <!-- <Tabs v-model:active="activeTab" class="tabs-wrap" v-if="!route.query.containerId">
+            <Tab title="专家方案">
+                <expert-list :isShowHeader="true"></expert-list>
+            </Tab>
+            <Tab title="我的方案">
+                <div class="farm-card-content">
+                    <tab-list
+                        v-if="curRole == 2"
+                        type="light"
+                        v-model="active"
+                        :tabs="tabs"
+                        @change="handleTabChange"
+                        class="tabs-list"
+                    />
+                    <plan-list :schemeId="active" :farmId="route.query.farmId" :containerId="containerId" :isEdit="isEditVal"> </plan-list>
                 </div>
-                <div class="slider" :style="sliderStyle"></div>
+            </Tab>
+        </Tabs>
+        <div v-else class="system-generated">
+            <div class="tip-box">
+                <el-icon size="18"><CircleCheckFilled /></el-icon>
+                <span>系统已生成多套方案,请选择最佳方案</span>
             </div>
+            <tab-list
+                v-if="curRole == 2"
+                type="light"
+                v-model="active"
+                :tabs="tabs"
+                @change="handleTabChange"
+                class="tabs-list"
+            />
+            <plan-list :schemeId="active" :containerId="containerId" :isEdit="isEditVal"> </plan-list>
+        </div> -->
+        <div class="farm-card-content" v-if="!route.query.containerId">
+            <tab-list
+                v-if="curRole == 2"
+                type="light"
+                v-model="active"
+                :tabs="tabs"
+                @change="handleTabChange"
+                class="tabs-list"
+            />
+            <plan-list :schemeId="active" :farmId="route.query.farmId" :containerId="containerId" :isEdit="true"> </plan-list>
         </div>
-        <div class="plan-content" v-if="activeTab === 'left'">
-            <div class="filter-wrap">
-                <div class="filter-l">指导专家:韦帮稳</div>
-                <div class="filter-r" @click="toPage">更多专家<el-icon><ArrowRightBold /></el-icon></div>
+        <div v-else class="system-generated">
+            <div class="tip-box">
+                <el-icon size="18"><CircleCheckFilled /></el-icon>
+                <span>系统已生成多套方案,请选择最佳方案</span>
             </div>
-            <div class="expert-prescription">
-                <div class="plan-menu">
-                    <el-anchor :container="containerRef" direction="vertical" type="default" @click="handleClick">
-                        <el-menu :default-active="defaultActive" class="el-menu-vertical-demo">
-                            <el-sub-menu v-for="(menu, index) in menuData" :key="index" :index="String(menu.id)">
-                                <template #title>
-                                    <img class="menu-icon" :src="require(`@/assets/img/gallery/icon-${index}.png`)" />
-                                    <span class="menu-text">{{ menu.name }}</span>
-                                </template>
-                                <el-menu-item v-for="item in menu.farmWorkArrangeList" :key="item.id" :index="`${menu.id}-${item.id}`">
-                                    <el-anchor-link :href="'#'+menu.name+item.farmWorkDetail?.name" :title="item.farmWorkDetail?.name || '摇花落花'" />
-                                </el-menu-item>
-                            </el-sub-menu>
-                        </el-menu>
-                    </el-anchor>
-                </div>
-                <div class="expert-content" ref="containerRef">
-                    <div v-for="(section, index) in menuData" :key="index" class="content-section">
-                        <div class="section-item" v-for="(sub, subI) in section.farmWorkArrangeList" :key="index+'-'+subI">
-                            <div class="section-id" :id="section.name+sub.farmWorkDetail?.name"></div>
-                            <record-item :record-item-data="sub">
-                                <template #title>
-                                    <div class="box-title">
-                                        <div class="title-l">
-                                            {{ sub.farmWorkDetail?.name }}
-                                            <span class="parent-text">{{ section.name }}</span>
-                                        </div>
-                                        <!-- <div class="title-btn" v-if="sub.selected == 0" @click="addToMyPlan">
-                                            <el-icon color="#fff" size="14"><Plus /></el-icon>
-                                        </div> -->
-                                    </div>
-                                </template>
-                            </record-item>
-                        </div>
-                    </div>
-                </div>
-            </div>
-            <!-- 底部 -->
-            <div class="fixed-bottom">
-                <div class="bottom-l">
-                    <div class="l-btn">
-                        <el-icon color="#666666" class="btn-icon" size="16"><Download /></el-icon>
-                        下载处方
-                    </div>
-                    <div class="l-btn">
-                        <el-icon color="#666666" class="btn-icon" size="16"><ChatDotRound /></el-icon>
-                        咨询专家
-                    </div>
-                </div>
-                <div class="bottom-r">全部添加</div>
-            </div>
-        </div>
-        <div class="plan-content my-recipe" v-if="activeTab === 'right'">
-            <my-prescription :isSubPage="true"></my-prescription>
-            <!-- <div class="fixed-bottom">
-                <div class="bottom-l">
-                    <div class="l-btn">
-                        <img class="btn-icon calculator-icon" src="@/assets/img/home/calculator.png" alt="" />
-                        投入产出计算器
-                    </div>
-                </div>
-                <div class="bottom-r">新增农事</div>
-            </div> -->
+            <tab-list
+                v-if="curRole == 2"
+                type="light"
+                v-model="active"
+                :tabs="tabs"
+                @change="handleTabChange"
+                class="tabs-list"
+            />
+            <plan-list :schemeId="active" :containerId="containerId" :isEdit="false"> </plan-list>
         </div>
     </div>
-    <add-group ref="addGroupRef" />
+    <div v-if="route.query.containerId" class="custom-bottom-fixed-btns">
+        <div class="bottom-btn primary-btn" @click="handleConfirmPlan">确认方案</div>
+    </div>
+
+    <tip-popup
+        v-model:show="showFarmPopup"
+        type="success"
+        text="农场创建成功"
+        buttonText="分享微信"
+        @confirm="handleShareFarm"
+        @handleClickOverlay="handleClickOverlay"
+    />
 </template>
 
 <script setup>
-import { computed, onMounted, ref } from "vue";
-import recordItem from "@/components/recordItem.vue";
-import addGroup from "./components/addGroup.vue";
-import myPrescription from "./components/myPrescription.vue";
 import customHeader from "@/components/customHeader.vue";
-import { useStore } from "vuex";
-import { useRouter, useRoute } from "vue-router";
-const store = useStore();
-const router = useRouter()
-const route = useRoute()
+import { onActivated, ref,onDeactivated } from "vue";
+import { useRoute, useRouter } from "vue-router";
+import { Tab, Tabs } from "vant";
+import wx from "weixin-js-sdk";
+import expertList from "@/views/old_mini/home/subPages/expertList.vue";
+import tabList from "@/components/pageComponents/TabList.vue";
+import PlanList from "@/components/pageComponents/PlanList.vue";
+import tipPopup from "@/components/popup/tipPopup.vue";
+import { ElMessage } from "element-plus";
 
-const tabBarHeight = computed(() => store.state.home.tabBarHeight);
+const activeTab = ref(1);
+const router = useRouter();
+const route = useRoute();
 
-const containerRef = ref(null);
-const handleClick = (e) => {
-    e.preventDefault();
-};
-const activeTab = ref("left");
-// const tabBarHeight = computed(() => store.state.home.tabBarHeight);
-const sliderStyle = computed(() => {
-    // 根据当前激活的选项卡计算滑动条位置
-    const position = activeTab.value === "left" ? "25%" : "75%";
-    return {
-        left: `calc(${position} - 12px)`, // 减去滑动条宽度的一半以实现居中
-    };
-});
-function setActiveTab(tab) {
-    activeTab.value = tab;
-}
-const typeVal = ref([1, 3]);
+const curRole = localStorage.getItem("SET_USER_CUR_ROLE");
 
-const typeOptions = ref([
-    {
-        value: 1,
-        label: "荔枝",
-        children: [
-            {
-                value: 2,
-                label: "井岗红糯",
-            },
-            {
-                value: 3,
-                label: "桂味",
-            },
-            {
-                value: 4,
-                label: "妃子笑",
-            },
-            {
-                value: 5,
-                label: "黑叶",
-            },
-        ],
-    },
-    {
-        value: 6,
-        label: "龙眼",
-        children: [
-            {
-                value: 7,
-                label: "龙眼1",
-            },
-            {
-                value: 8,
-                label: "龙眼2",
-            },
-            {
-                value: 9,
-                label: "龙眼3",
-            },
-            {
-                value: 10,
-                label: "龙眼4",
-            },
-        ],
-    },
-    {
-        value: 11,
-        label: "枇杷",
-        children: [
-            {
-                value: 12,
-                label: "枇杷1",
-            },
-            {
-                value: 13,
-                label: "枇杷2",
-            },
-            {
-                value: 14,
-                label: "枇杷3",
-            },
-            {
-                value: 15,
-                label: "枇杷4",
-            },
-        ],
-    },
-]);
-
-const proviceVal = ref("广东");
-const proviceOptions = ref([
-    {
-        value: "广东",
-        label: "广东省",
-    },
-    {
-        value: "广西",
-        label: "广西省",
-    },
-    {
-        value: "福建",
-        label: "福建省",
-    },
-    {
-        value: "海南",
-        label: "海南省",
-    },
-]);
-
-// 菜单
-const defaultActive = ref("1-1");
-
-const menuData = ref([])
-
-const activePlanIndex = ref(0);
-const handlePlanClick = (index) => {
-    activePlanIndex.value = index;
+const tabs = ref([]);
+const active = ref(null);
+const containerId = ref(null);
+const handleTabChange = (id, item) => {
+    containerId.value = item.containerId;
+    active.value = id;
 };
 
-const addGroupRef = ref(null);
-// 新增方案
-function newPlan() {
-    addGroupRef.value.openClientPopup();
-}
-
-// 将专家处方添加到我的处方
-function addToMyPlan() {
-    addGroupRef.value.openClientPopup({type: "edit"});
-}
-
-function toPage() {
-    router.push("/expert_list?isToSelect=true")
-}
-
-function getWorkList() {
-    VE_API.home.getPhenologyFarmWorkList({farmId: 93301, containerId: route.query.containerId || 2}).then(({data}) => {
-        menuData.value = data
-    })
-}
-
-onMounted(() => {
-    getWorkList()
-})
-</script>
-
-<style lang="scss" scoped>
-.plan-page {
-    position: relative;
-    height: 100vh;
-    .plan-title {
-        width: 158px;
-        margin: 0 auto;
-        padding: 6px 0;
-        .tabs {
-            display: flex;
-            position: relative;
-            height: 36px;
-            line-height: 36px;
-            .tab {
-                flex: 1;
-                text-align: center;
-                cursor: pointer;
-                color: rgba(0, 0, 0, 0.5);
-                z-index: 2;
-                transition: color 0.3s ease;
-                position: relative;
-                &.active {
-                    color: #000000;
-                    font-weight: bold;
-                }
-                .badge-dot {
-                    position: absolute;
-                    top: 0;
-                    right: -6px;
-                    width: 16px;
-                    height: 16px;
-                    background-color: #ff0000;
-                    border-radius: 50%;
-                    font-size: 12px;
-                    line-height: 16px;
-                    color: #fff;
-                }
-            }
-            .slider {
-                position: absolute;
-                height: 4px;
-                width: 24px;
-                background: #2199f8;
-                border-radius: 16px;
-                bottom: 0;
-                left: calc(25% - 12px); /* 初始位置在第一个选项卡中间 */
-                transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
-            }
-        }
+onActivated(() => {
+    if (route.query.containerId || curRole == 2) {
+        getListMySchemes();
+    }
+});
 
-        .content {
-            padding: 20px;
-            background: #f8f9fa;
-            border-radius: 8px;
-            min-height: 200px;
-        }
+onDeactivated(() => {
+    active.value = null;
+    containerId.value = null;
+});
 
-        .content-section {
-            display: none;
-            animation: fadeIn 0.5s ease;
-            
-            .section-item {
-                position: relative;
+const getListMySchemes = () => {
+    VE_API.home.listMySchemes().then(({ data }) => {
+        if (data.length) {
+            tabs.value = data || [];
+            if (route.query.containerId) {
+                const index = data.findIndex((item) => item.containerId == 9);
+                active.value = data[index].id;
+                containerId.value = 9;
+            } else {
+                active.value = data[0].id;
+                containerId.value = data[0].containerId;
             }
+        } else {
+            getSchemes();
         }
+    });
+};
 
-        .content-section.active {
-            display: block;
+const getSchemes = () => {
+    VE_API.home.batchInitSchemes({ containerIds: [3], schemeName: "农资荔枝方案" }).then(({ code }) => {
+        if (code == 0) {
+            getListMySchemes();
         }
+    });
+};
 
-        @keyframes fadeIn {
-            from {
-                opacity: 0;
-                transform: translateY(10px);
-            }
-            to {
-                opacity: 1;
-                transform: translateY(0);
+const showFarmPopup = ref(false);
+const shareData = ref({});
+const handleConfirmPlan = () => {
+    // 从路由参数中获取农场数据
+    let geomValue = route.query.geom;
+    
+    // 处理 geom 参数,可能是 JSON 字符串或数组
+    if (typeof geomValue === 'string') {
+        try {
+            // 尝试解析 JSON 字符串
+            const parsed = JSON.parse(geomValue);
+            if (Array.isArray(parsed)) {
+                geomValue = parsed;
             }
+        } catch (e) {
+            // 如果不是 JSON 字符串,保持原值
+            console.warn('geom 参数解析失败,使用原值:', e);
         }
     }
-    .plan-content {
-        background: #f5f7fb;
-        .filter-wrap {
-            padding: 10px 12px;
-            width: 100%;
-            box-sizing: border-box;
-            display: flex;
-            align-items: center;
-            justify-content: space-between;
-            .filter-r {
-                height: 32px;
-                display: flex;
-                align-items: center;
-                padding: 0 18px;
-                background: rgba(33, 153, 248, 0.1);
-                border-radius: 20px;
-                color: #2199F8;
-                font-size: 14px;
-            }
-        }
+    
+    const farmParams = {
+        ...route.query,
+        containerId: containerId.value,
+        geom: geomValue,
+        defaultFarm: Boolean(route.query.defaultFarm),
+        agriculturalCreate: route.query.agriculturalCreate * 1,
+    };
+    
+    // 验证必填字段
+    if (!farmParams.wkt || !farmParams.speciesId || !farmParams.containerId || !farmParams.address || !farmParams.mu || !farmParams.name || !farmParams.fzr || !farmParams.tel) {
+        ElMessage.error('农场信息不完整,请返回重新填写');
+        return;
     }
-    .fixed-bottom {
-        position: absolute;
-        bottom: 0;
-        left: 0;
-        width: 100%;
-        display: flex;
-        align-items: center;
-        justify-content: space-between;
-        padding: 14px 12px;
-        background: #fff;
-        box-sizing: border-box;
-        border-top: 1px solid rgba(153, 153, 153, 0.2);
-        .bottom-l {
-            display: flex;
-            align-items: center;
-            .l-btn {
-                border: 1px solid rgba(153, 153, 153, 0.5);
-                border-radius: 30px;
-                padding: 0 8px 0 12px;
-                height: 32px;
-                line-height: 32px;
-                box-sizing: border-box;
-                display: flex;
-                align-items: center;
-                justify-content: center;
-                color: #666666;
-                .btn-icon {
-                    padding-right: 3px;
+    
+    delete farmParams.from;
+    // 调用创建农场接口
+    VE_API.farm.saveFarm(farmParams).then((res) => {
+        if (res.code === 0) {
+            shareData.value = res.data;
+            //选择方案
+            VE_API.home.selectSchemes({ schemeId: active.value ,farmId:res.data.id}).then(({code}) => {
+                if (code === 0) {
+                    showFarmPopup.value = true;
+                } else {
+                    ElMessage.error(res.msg || '创建失败');
                 }
-                .calculator-icon {
-                    width: 12px;
-                }
-            }
-            .l-btn + .l-btn {
-                margin-left: 10px;
-            }
-        }
-        .bottom-r {
-            height: 32px;
-            line-height: 32px;
-            background: #2199f8;
-            border-radius: 20px;
-            color: #fff;
-            padding: 0 12px;
+            });
+        } else {
+            ElMessage.error(res.msg || '创建失败');
         }
+    }).catch((err) => {
+        console.error('创建农场失败:', err);
+        ElMessage.error('创建失败,请稍后重试');
+    });
+};
+
+const handleShareFarm = () => {
+    const query = {
+        agriculturalStoreId: shareData.value.agriculturalStoreId,
+        farmId: shareData.value.id,
+        speciesName: route.query.speciesName,
+        containerId: shareData.value.containerId,
+    };
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=shareFarm`,
+    });
+};
+
+const handleClickOverlay = () => {
+    // 根据 from 参数跳转回原页面
+    const fromPage = route.query.from;
+    if (fromPage) {
+        router.replace(`/${fromPage}`);
+    } else {
+        // 如果没有 from 参数,默认跳转到首页
+        router.replace('/home');
     }
-    .expert-prescription {
-        display: flex;
-        width: 100%;
-        height: calc(100vh - 52px - 48px - 50px);
-        .plan-menu {
-            width: 100px;
-            height: 100%;
-            overflow: auto;
-            padding: 10px 0;
+};
+</script>
+
+<style scoped lang="scss">
+.farm-card-page {
+    width: 100%;
+    height: 100vh;
+    background: #f5f7fb;
+    .system-generated{
+        padding: 17px 10px 10px;
+        height: calc(100vh - 150px);
+        .tip-box {
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            padding: 10px;
+            background: rgba(33, 153, 248, 0.1);
+            border-radius: 8px;
+            color: #2199f8;
             box-sizing: border-box;
-            background: #fff;
-            border-radius: 0 10px 10px 0;
-            .menu-icon {
-                width: 13px;
-            }
-            .menu-text {
-                padding: 0 4px;
-            }
-            ::v-deep {
-                .el-anchor {
-                    height: 100%;
-                    background: none;
-                }
-                .el-anchor__marker {
-                    display: none;
-                }
-                .el-menu {
-                    background: none;
-                    border: none;
-                    .el-sub-menu__title {
-                        background: none;
-                        padding: 0 2px;
-                        justify-content: center;
-                    }
-                    .el-sub-menu__title {
-                        height: 32px;
-                    }
-                    .el-sub-menu .el-sub-menu__icon-arrow {
-                        position: static;
-                        padding-top: 6px;
-                    }
-                    .el-sub-menu {
-                        margin-bottom: 16px;
-                        &.is-opened {
-                            .el-sub-menu__icon-arrow {
-                                padding-bottom: 6px;
-                                padding-top: 0;
-                            }
-                        }
-                        .el-menu-item {
-                            height: 32px;
-                            line-height: 32px;
-                            margin: 4px 8px;
-                            padding: 0 2px;
-                            justify-content: center;
-                            background: none;
-                        }
-                        .el-menu-item.is-active {
-                            background: none;
-                            color: #fff;
-                        }
-                        .el-anchor__item {
-                            width: 100%;
-                            text-align: center;
-                        }
-                        .el-anchor__link {
-                            color: #666666;
-                        }
-                        .el-anchor__link.is-active {
-                            background: rgba(33, 153, 248, 0.1);
-                            border-radius: 20px;
-                            color: #2199F8;
-                            border: 1px solid #2199F8;
-                        }
-                    }
-                }
-                .el-anchor__list {
-                    padding-left: 0;
-                }
+            span {
+                margin-left: 5px;
             }
         }
-        .expert-content {
-            width: calc(100% - 100px);
-            height: 100%;
-            overflow: auto;
-            padding-bottom: 80px;
-            box-sizing: border-box;
-            .content-section {
-                position: relative;
-                .section-item {
-                    position: relative;
-                }
-                .section-id {
-                    position: absolute;
-                    // top: -6px;
-                    top: 0;
-                    width: 100%;
-                    height: 1px;
-                }
-            }
-            .box-title {
-                display: flex;
-                align-items: center;
-                justify-content: space-between;
-                padding-bottom: 8px;
-                border-bottom: 1px solid #f5f5f5;
-                margin-bottom: 8px;
-                .title-l {
-                    font-size: 16px;
-                    font-weight: 600;
-                    color: #000;
-                    .parent-text {
-                        margin-left: 5px;
-                        font-size: 12px;
-                        font-weight: normal;
-                        padding: 4px 6px;
-                        border-radius: 14px;
-                        background: rgba(119, 119, 119, 0.1);
-                    }
-                }
-                .title-btn {
-                    width: 24px;
-                    height: 24px;
-                    border-radius: 50%;
-                    background: #2199f8;
-                    display: flex;
-                    align-items: center;
-                    justify-content: center;
-                }
-            }
+        .tabs-list{
+            margin: 10px 0;
         }
     }
-
-    .my-recipe {
-        .filter-wrap {
-            .plan-box {
-                display: flex;
-                overflow: auto;
-                white-space: nowrap;
-                align-items: center;
-                padding-left: 12px;
-                .plan-item {
-                    color: #000000;
-                    background: #f1f1f1;
-                    padding: 0 12px;
-                    height: 32px;
-                    line-height: 32px;
-                    border-radius: 20px;
-                    &.active {
-                        background: rgba(33, 153, 248, 0.2);
-                        color: #2199f8;
-                    }
-                }
-                .plan-item + .plan-item {
-                    margin-left: 10px;
-                }
+    .tabs-wrap {
+        ::v-deep {
+            .van-tabs__line {
+                width: 24px;
+                height: 4px;
             }
-            .plan-add {
-                width: 80px;
-                height: 30px;
-                border: 1px solid #2199f8;
-                border-radius: 20px;
+            .van-tab {
+                width: 100px;
                 flex: none;
-                line-height: 32px;
-                text-align: center;
-                margin: 0 12px;
-                color: #2199f8;
-                font-size: 14px;
             }
-        }
-        .title-r {
-            display: flex;
-            align-items: center;
-            .btn-item {
-                width: 24px;
-                height: 24px;
-                border-radius: 50%;
-                display: flex;
-                align-items: center;
+            .van-tabs__nav {
                 justify-content: center;
-                background: #2199f8;
-                &.del-btn {
-                    margin-right: 5px;
-                    background: #ff953d;
-                }
             }
         }
     }
+    .farm-card-content {
+        width: 100%;
+        height: calc(100vh - 40px);
+        padding-top: 10px;
+        .tabs-list {
+            margin: 0 0 10px 10px;
+        }
+    }
+}
+.custom-bottom-fixed-btns{
+    justify-content: center;
+    .bottom-btn{
+        padding: 10px 40px;
+    }
 }
 </style>