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

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

lxf 1 день назад
Родитель
Сommit
c96cae29e9

+ 4 - 2
src/components/pageComponents/FarmInfoCard.vue

@@ -1,7 +1,8 @@
 <template>
 <template>
     <div class="farm-info-card" :class="{ 'has-footer': showFooter }" @click="handleClick">
     <div class="farm-info-card" :class="{ 'has-footer': showFooter }" @click="handleClick">
         <div class="item-content">
         <div class="item-content">
-            <div class="item-left" :style="{ width: data.maxWidth ? 'calc(100% - 80px)' : 'auto' }">
+            <div class="item-left">
+            <!-- <div class="item-left" :style="{ width: data.maxWidth ? 'calc(100% - 80px)' : 'auto' }"> -->
                 <img class="map-img" src="@/assets/img/home/farm.png" alt="地图" />
                 <img class="map-img" src="@/assets/img/home/farm.png" alt="地图" />
                 <div class="item-info">
                 <div class="item-info">
                     <div class="item-header">
                     <div class="item-header">
@@ -24,7 +25,7 @@
                         </div>
                         </div>
                     </div>
                     </div>
                     <div class="farm-address van-ellipsis">
                     <div class="farm-address van-ellipsis">
-                        {{ data.address || (data.dutyList && data.dutyList?.join("、")) || '--' }}
+                        {{ data.address || '手机号:'+ data.phone || '--' }}
                     </div>
                     </div>
                 </div>
                 </div>
             </div>
             </div>
@@ -169,6 +170,7 @@ const handleRemind = () => {
             .farm-address {
             .farm-address {
                 font-size: 12px;
                 font-size: 12px;
                 color: #86909c;
                 color: #86909c;
+                width: auto;
                 max-width: 190px;
                 max-width: 190px;
             }
             }
         }
         }

+ 0 - 1
src/components/pageComponents/FarmWorkPlanTimeline.vue

@@ -300,7 +300,6 @@ const getReproductiveItemHeight = (phenologyItem) => {
 };
 };
 
 
 const handleRowClick = (item) => {
 const handleRowClick = (item) => {
-    if (props.disableClick) return;
     emits("row-click", item);
     emits("row-click", item);
 };
 };
 
 

+ 2 - 0
src/main.js

@@ -4,6 +4,7 @@ import store from "@/store";
 import installElementPlus from "@/plugins/element";
 import installElementPlus from "@/plugins/element";
 import elementIcon from "@/plugins/svgicon";
 import elementIcon from "@/plugins/svgicon";
 import permission from "@/plugins/permission";
 import permission from "@/plugins/permission";
+import hasPermission from "@/plugins/hasPermission";
 import mock from "@/plugins/mock";
 import mock from "@/plugins/mock";
 // import directives from "@/directives";
 // import directives from "@/directives";
 import "nprogress/nprogress.css";
 import "nprogress/nprogress.css";
@@ -29,6 +30,7 @@ app.use(mock)
       })
       })
     .use(axios, { router, store, opt: "VE_API" })
     .use(axios, { router, store, opt: "VE_API" })
     .use(permission, { router, store })
     .use(permission, { router, store })
+    .use(hasPermission)
     // .use(directives, { router, store });
     // .use(directives, { router, store });
 
 
 
 

+ 52 - 0
src/plugins/hasPermission.js

@@ -0,0 +1,52 @@
+/**
+ * 权限控制指令
+ * 用法: v-has-permission="'增删成员'"
+ * 如果用户有该权限则显示元素,否则隐藏
+ */
+export default {
+    install(app) {
+        app.directive('has-permission', {
+            mounted(el, binding) {
+                checkPermission(el, binding);
+            },
+            updated(el, binding) {
+                checkPermission(el, binding);
+            }
+        });
+    }
+};
+
+function checkPermission(el, binding) {
+    // 获取权限值,支持字符串或数组
+    const permission = binding.value;
+    
+    // 从 localStorage 获取用户信息
+    const userInfoStr = localStorage.getItem('localUserInfo');
+    if (!userInfoStr) {
+        el.style.display = 'none';
+        return;
+    }
+
+    try {
+        const userInfo = JSON.parse(userInfoStr);
+        const permissions = userInfo.agriculturalPermissions || [];
+
+        // 如果权限是字符串,检查是否包含
+        if (typeof permission === 'string') {
+            const hasPermission = permissions.includes(permission);
+            el.style.display = hasPermission ? '' : 'none';
+        }
+        // 如果权限是数组,检查是否包含任一权限
+        else if (Array.isArray(permission)) {
+            const hasPermission = permission.some(p => permissions.includes(p));
+            el.style.display = hasPermission ? '' : 'none';
+        }
+        // 如果没有传入权限值,默认隐藏
+        else {
+            el.style.display = 'none';
+        }
+    } catch (error) {
+        console.error('解析用户信息失败:', error);
+        el.style.display = 'none';
+    }
+}

+ 12 - 10
src/views/old_mini/dev_login.vue

@@ -31,11 +31,12 @@ onMounted(async () => {
             savedRole = sessionRes.data.val;
             savedRole = sessionRes.data.val;
         }
         }
         // 优先使用保存的角色,如果保存的角色在 roles 中,则使用保存的角色,否则如果 roles 中包含 2,赋值 2,否则赋值 0
         // 优先使用保存的角色,如果保存的角色在 roles 中,则使用保存的角色,否则如果 roles 中包含 2,赋值 2,否则赋值 0
-        let curRole = 0;
-        if (savedRole !== null && Array.isArray(data.roles) && data.roles.includes(savedRole)) {
-            curRole = savedRole;
-        }
-        store.dispatch(`app/${SET_USER_CUR_ROLE}`, curRole);
+        // let curRole = 0;
+        // if (savedRole !== null && Array.isArray(data.roles) && data.roles.includes(savedRole)) {
+        //     curRole = savedRole;
+        // }
+        // store.dispatch(`app/${SET_USER_CUR_ROLE}`, curRole);
+        store.dispatch(`app/${SET_USER_CUR_ROLE}`, data.roles[data.roles.length - 1]);
         localStorage.setItem("localUserInfo", JSON.stringify(data));
         localStorage.setItem("localUserInfo", JSON.stringify(data));
     }
     }
     // 存userId
     // 存userId
@@ -52,11 +53,12 @@ onMounted(async () => {
             savedRole = sessionRes.data.val;
             savedRole = sessionRes.data.val;
         }
         }
         // 优先使用保存的角色,如果保存的角色在 roles 中,则使用保存的角色,否则如果 roles 中包含 2,赋值 2,否则赋值 0
         // 优先使用保存的角色,如果保存的角色在 roles 中,则使用保存的角色,否则如果 roles 中包含 2,赋值 2,否则赋值 0
-        let curRole = 0;
-        if (savedRole !== null && Array.isArray(roles) && roles.includes(savedRole)) {
-            curRole = savedRole;
-        }
-        store.dispatch(`app/${SET_USER_CUR_ROLE}`, curRole);
+        // let curRole = 0;
+        // if (savedRole !== null && Array.isArray(roles) && roles.includes(savedRole)) {
+        //     curRole = savedRole;
+        // }
+        // store.dispatch(`app/${SET_USER_CUR_ROLE}`, curRole);
+        store.dispatch(`app/${SET_USER_CUR_ROLE}`, roles[roles.length - 1]);
     }
     }
     await getFarmList();
     await getFarmList();
     localStorage.setItem("MINI_USER_LOCATION", route.query.point)
     localStorage.setItem("MINI_USER_LOCATION", route.query.point)

+ 48 - 43
src/views/old_mini/home/components/AgriculturalDynamics.vue

@@ -19,43 +19,49 @@
         <div class="title">农情互动</div>
         <div class="title">农情互动</div>
         <!-- 内容区域 -->
         <!-- 内容区域 -->
         <div class="agricultural-list">
         <div class="agricultural-list">
-        <div v-if="!unansweredList.length" class="empty-block">暂无数据</div>
-        <template v-else>
-            <div class="agricultural-item" v-for="(item, index) in unansweredList" :key="index">
-                <!-- 头部区域 -->
-                <div class="header-section">
-                    <div class="header-left">
-                        <div class="farm-name">{{ item.farmName }}</div>
-                        <div class="tag-group">
-                            <div class="tag tag-blue">{{ item.typeName }}</div>
-                            <div class="tag tag-orange">托管客户</div>
+            <div v-if="!unansweredList.length" class="empty-block">暂无数据</div>
+            <template v-else>
+                <div class="agricultural-item" v-for="(item, index) in unansweredList" :key="index">
+                    <!-- 头部区域 -->
+                    <div class="header-section">
+                        <div class="header-left">
+                            <div class="farm-name">{{ item.farmName }}</div>
+                            <div class="tag-group">
+                                <div class="tag tag-blue">{{ item.typeName }}</div>
+                                <div class="tag tag-orange">托管客户</div>
+                            </div>
                         </div>
                         </div>
+                        <div class="remind-btn" @click="handleRemind(item)">提醒客户</div>
                     </div>
                     </div>
-                    <div class="remind-btn" @click="handleRemind(item)">提醒客户</div>
-                </div>
-                <!-- 警告通知块 -->
-                <div class="warning-block" v-if="item.latestPhenologyProgressBroadcast" v-html="item.latestPhenologyProgressBroadcast?.content"></div>
-
-                <div class="timeline">
-                    <div class="timeline-item" v-for="timelineItem in item.timelineList" :key="timelineItem.id">
-                        <div class="timeline-left">
-                            <div class="dot"></div>
-                            <div class="line"></div>
-                        </div>
-                        <div class="timeline-right">
-                            <div class="date">
-                                预计{{ timelineItem.daysUntilTrigger }}天后触发<span class="work-name">{{ timelineItem.farmWorkName }}</span>
+                    <!-- 警告通知块 -->
+                    <div
+                        class="warning-block"
+                        v-if="item.latestPhenologyProgressBroadcast"
+                        v-html="item.latestPhenologyProgressBroadcast?.content"
+                    ></div>
+
+                    <div class="timeline">
+                        <div class="timeline-item" v-for="timelineItem in item.timelineList" :key="timelineItem.id">
+                            <div class="timeline-left">
+                                <div class="dot"></div>
+                                <div class="line"></div>
                             </div>
                             </div>
-                            <div class="text">
-                                预计报价<span class="price">{{ timelineItem.estimatedCost }}元</span>
-                                <span class="action-detail" @click="showPriceSheetPopup(timelineItem)">查看报价单</span>
+                            <div class="timeline-right">
+                                <div class="date">
+                                    <span class="work-name">{{ timelineItem.farmWorkName }}</span>
+                                </div>
+                                <div class="text">
+                                    预计报价<span class="price">{{ timelineItem.estimatedCost }}元</span>
+                                    <span class="action-detail" @click="showPriceSheetPopup(timelineItem)">查看报价单</span>
+                                </div>
+                            </div>
+                            <div class="timeline-action" @click="handleTimelineAction(timelineItem, item.farmId)">
+                                转入农事任务
                             </div>
                             </div>
                         </div>
                         </div>
-                        <div class="timeline-action" @click="handleTimelineAction(timelineItem, item.farmId)">转入农事任务</div>
                     </div>
                     </div>
                 </div>
                 </div>
-            </div>
-        </template>
+            </template>
         </div>
         </div>
     </div>
     </div>
     <offer-popup ref="offerPopupRef"></offer-popup>
     <offer-popup ref="offerPopupRef"></offer-popup>
@@ -117,14 +123,14 @@ const getManagerList = async () => {
     }
     }
 };
 };
 
 
-const executorList = ref([])
+const executorList = ref([]);
 const handleTimelineAction = (item, farmId) => {
 const handleTimelineAction = (item, farmId) => {
     // 处理转入农事任务逻辑
     // 处理转入农事任务逻辑
     eventBus.emit("activeUpload:show", {
     eventBus.emit("activeUpload:show", {
         gardenIdVal: farmId,
         gardenIdVal: farmId,
         needExecutorVal: true,
         needExecutorVal: true,
-        problemTitleVal: '请选择 ' + item.farmWorkName + ' 执行截止时间',
-        imgDescVal: '请上传凭证(转入农事任务凭证)',
+        problemTitleVal: "请选择 " + item.farmWorkName + " 执行截止时间",
+        imgDescVal: "请上传凭证(转入农事任务凭证)",
         arrangeIdVal: item.arrangeId,
         arrangeIdVal: item.arrangeId,
         executorListVal: executorList.value,
         executorListVal: executorList.value,
     });
     });
@@ -133,7 +139,6 @@ const handleTimelineAction = (item, farmId) => {
 
 
 const priceSheetPopupRef = ref(null);
 const priceSheetPopupRef = ref(null);
 const showPriceSheetPopup = (item) => {
 const showPriceSheetPopup = (item) => {
-    console.log('item', item)
     VE_API.z_farm_work_record.getDetail({ id: item.farmWorkId }).then(({ data }) => {
     VE_API.z_farm_work_record.getDetail({ id: item.farmWorkId }).then(({ data }) => {
         const res = data[0];
         const res = data[0];
         priceSheetPopupRef.value.handleShowPopup(res);
         priceSheetPopupRef.value.handleShowPopup(res);
@@ -150,7 +155,7 @@ const handleRemind = (item) => {
 
 
 onMounted(() => {
 onMounted(() => {
     getUnansweredFarms();
     getUnansweredFarms();
-    getManagerList()
+    getManagerList();
 });
 });
 
 
 //农情互动的农场列表接口(分页)
 //农情互动的农场列表接口(分页)
@@ -161,14 +166,14 @@ const getUnansweredFarms = async () => {
         limit: 3,
         limit: 3,
     };
     };
     const res = await VE_API.home.listUnansweredFarms(params);
     const res = await VE_API.home.listUnansweredFarms(params);
-    unansweredList.value = (res.data || []).map(item => ({
+    unansweredList.value = (res.data || []).map((item) => ({
         ...item,
         ...item,
-        timelineList: []
+        timelineList: [],
     }));
     }));
-    
+
     // 串行请求,一个完成后再请求下一个
     // 串行请求,一个完成后再请求下一个
-    if(unansweredList.value.length){
-        for(let i = 0; i < unansweredList.value.length; i++){
+    if (unansweredList.value.length) {
+        for (let i = 0; i < unansweredList.value.length; i++) {
             await getFutureFarmWorkWarning(unansweredList.value[i]);
             await getFutureFarmWorkWarning(unansweredList.value[i]);
         }
         }
     }
     }
@@ -176,7 +181,7 @@ const getUnansweredFarms = async () => {
 
 
 //查询未来农事预警
 //查询未来农事预警
 const getFutureFarmWorkWarning = async (item) => {
 const getFutureFarmWorkWarning = async (item) => {
-    const res = await VE_API.home.listFutureFarmWorkWarning({farmId: item.farmId});
+    const res = await VE_API.home.listFutureFarmWorkWarning({ farmId: item.farmId });
     item.timelineList = res.data || [];
     item.timelineList = res.data || [];
 };
 };
 </script>
 </script>
@@ -332,8 +337,8 @@ const getFutureFarmWorkWarning = async (item) => {
                 margin-top: 8px;
                 margin-top: 8px;
                 font-size: 12px;
                 font-size: 12px;
                 color: #252525;
                 color: #252525;
-                ::v-deep{
-                    p{
+                ::v-deep {
+                    p {
                         padding: 0;
                         padding: 0;
                         margin: 0;
                         margin: 0;
                     }
                     }

+ 1 - 1
src/views/old_mini/mine/pages/projectManager.vue

@@ -10,7 +10,7 @@
                         class="list-item"
                         class="list-item"
                         :data="{
                         :data="{
                             ...ele,
                             ...ele,
-                            maxWidth: 'auto',
+                            maxWidth: 'fit-content',
                             roleName: '项目管理员',
                             roleName: '项目管理员',
                         }"
                         }"
                     >
                     >

+ 37 - 14
src/views/old_mini/mine/pages/serviceRecords.vue

@@ -2,13 +2,8 @@
     <div class="service-records-page">
     <div class="service-records-page">
         <custom-header name="服务记录"></custom-header>
         <custom-header name="服务记录"></custom-header>
         <div class="record-list">
         <div class="record-list">
-            <div
-                v-for="(item, index) in recordList"
-                :key="index"
-                class="record-card"
-                @click="handleItemClick(item)"
-            >
-                <img class="thumb" :src="defaultThumb" alt="农场缩略图" />
+            <div v-for="(item, index) in recordList" :key="index" class="record-card" @click="handleItemClick(item)">
+                <img class="thumb" :src="item.farmIcon" alt="农场缩略图" />
                 <div class="card-body">
                 <div class="card-body">
                     <div class="card-header">
                     <div class="card-header">
                         <div class="title van-ellipsis">{{ item.farmWorkName }}</div>
                         <div class="title van-ellipsis">{{ item.farmWorkName }}</div>
@@ -20,7 +15,7 @@
                     </div>
                     </div>
                     <div class="line">
                     <div class="line">
                         <span class="label">药肥处方:</span>
                         <span class="label">药肥处方:</span>
-                        <span class="text van-ellipsis">{{ item.prescription }}</span>
+                        <span class="text van-ellipsis">{{ getPrescriptionText(item) }}</span>
                     </div>
                     </div>
                 </div>
                 </div>
             </div>
             </div>
@@ -31,7 +26,6 @@
 import { ref, onMounted } from "vue";
 import { ref, onMounted } from "vue";
 import customHeader from "@/components/customHeader.vue";
 import customHeader from "@/components/customHeader.vue";
 import { useRouter } from "vue-router";
 import { useRouter } from "vue-router";
-import defaultThumb from "@/assets/img/home/farm.png";
 const router = useRouter();
 const router = useRouter();
 
 
 // 服务记录列表数据
 // 服务记录列表数据
@@ -42,8 +36,12 @@ onMounted(() => {
 });
 });
 
 
 const getSimpleList = async () => {
 const getSimpleList = async () => {
-    const { data } = await VE_API.mine.getSimpleList({role: localStorage.getItem("SET_USER_CUR_ROLE")});
-    if(data.length > 0){
+    const { data } = await VE_API.mine.getSimpleList({
+        role: localStorage.getItem("SET_USER_CUR_ROLE"),
+        includePrescription: true,
+        isServiceRecord: true,
+    });
+    if (data.length > 0) {
         recordList.value = data;
         recordList.value = data;
     }
     }
 };
 };
@@ -52,6 +50,26 @@ const getSimpleList = async () => {
 const handleItemClick = (data) => {
 const handleItemClick = (data) => {
     router.push(`/review_work?miniJson={"id":${data.id},"goBack":true,"isBtn":true}`);
     router.push(`/review_work?miniJson={"id":${data.id},"goBack":true,"isBtn":true}`);
 };
 };
+
+// 获取药肥处方文本(从prescriptionList中提取pesticideFertilizerList的defaultName,用+连接)
+const getPrescriptionText = (item) => {
+    if (!item.prescriptionList || !Array.isArray(item.prescriptionList) || item.prescriptionList.length === 0) {
+        return item.prescription || "暂无";
+    }
+
+    const nameList = [];
+    item.prescriptionList.forEach((prescription) => {
+        if (prescription.pesticideFertilizerList && Array.isArray(prescription.pesticideFertilizerList)) {
+            prescription.pesticideFertilizerList.forEach((pesticide) => {
+                if (pesticide.defaultName) {
+                    nameList.push(pesticide.defaultName);
+                }
+            });
+        }
+    });
+
+    return nameList.length > 0 ? nameList.join(" + ") : item.prescription || "暂无";
+};
 </script>
 </script>
 <style lang="scss" scoped>
 <style lang="scss" scoped>
 .service-records-page {
 .service-records-page {
@@ -96,9 +114,14 @@ const handleItemClick = (data) => {
             .line {
             .line {
                 font-size: 12px;
                 font-size: 12px;
                 color: #666;
                 color: #666;
-            }
-            .label {
-                color: #bbb;
+                display: flex;
+                align-items: center;
+                .label {
+                    color: #bbb;
+                }
+                .text {
+                    flex: 1;
+                }
             }
             }
         }
         }
     }
     }

+ 30 - 25
src/views/old_mini/mine/pages/teamManage.vue

@@ -14,11 +14,11 @@
                             ...ele,
                             ...ele,
                             roleName: ele.role == 1 ? '超级管理员' : ele.role == 2 ? '项目管理员' : '普通成员',
                             roleName: ele.role == 1 ? '超级管理员' : ele.role == 2 ? '项目管理员' : '普通成员',
                             className: ele.role == 1 ? 'tag-role' : ele.role == 2 ? '' : 'tag-gray',
                             className: ele.role == 1 ? 'tag-role' : ele.role == 2 ? '' : 'tag-gray',
-                            maxWidth: 'auto',
+                            maxWidth: 'fit-content',
                             address: ele.role == 1 ? ele.store?.address || '--' : null,
                             address: ele.role == 1 ? ele.store?.address || '--' : null,
                         }"
                         }"
                     >
                     >
-                        <template #right v-if="ele.role === 2 && !route.query.add && currentUserRole !== 3">
+                        <template #right v-if="ele.role === 2 && !route.query.add && currentUserRole === 1">
                             <div @click.stop="handlePermission(ele)">权限设置</div>
                             <div @click.stop="handlePermission(ele)">权限设置</div>
                         </template>
                         </template>
                     </farm-info-card>
                     </farm-info-card>
@@ -28,11 +28,11 @@
                 <div class="empty-text">暂无数据</div>
                 <div class="empty-text">暂无数据</div>
             </div>
             </div>
         </div>
         </div>
-        <div class="custom-bottom-fixed-btns" :class="{ center: currentUserRole == 2 }" v-if="currentUserRole !== 3">
-            <div class="bottom-btn secondary-btn" @click="handleSetAdmin">
+        <div class="custom-bottom-fixed-btns" :class="{ center: currentUserRole == 2 }" v-if="(currentUserRole === 1 || (currentUserRole !== 3 && currentEditPermissions))">
+            <div class="bottom-btn secondary-btn" @click="handleSetAdmin" v-if="currentUserRole === 1">
                 {{ route.query.add ? "取消选择" : "设置管理员" }}
                 {{ route.query.add ? "取消选择" : "设置管理员" }}
             </div>
             </div>
-            <div class="bottom-btn primary-btn" @click="handleAddTeamMember" v-if="currentUserRole === 1">
+            <div class="bottom-btn primary-btn" @click="handleAddTeamMember">
                 {{ route.query.add ? "确认设为" : "新增团队成员" }}
                 {{ route.query.add ? "确认设为" : "新增团队成员" }}
             </div>
             </div>
         </div>
         </div>
@@ -73,7 +73,7 @@
                             :readonly="detailType === 'detail'"
                             :readonly="detailType === 'detail'"
                         />
                         />
                     </el-form-item>
                     </el-form-item>
-                    <el-form-item label="角色类型" prop="duties">
+                    <!-- <el-form-item label="角色类型" prop="duties">
                         <div class="role-type-grid">
                         <div class="role-type-grid">
                             <div
                             <div
                                 v-for="role in dutyList"
                                 v-for="role in dutyList"
@@ -88,15 +88,17 @@
                                 {{ role.name }}
                                 {{ role.name }}
                             </div>
                             </div>
                         </div>
                         </div>
-                    </el-form-item>
+                    </el-form-item> -->
                 </el-form>
                 </el-form>
                 <div class="popup-footer" v-show="detailType === 'add'">
                 <div class="popup-footer" v-show="detailType === 'add'">
                     <div class="footer-btn share-btn" @click="handleShare">分享微信好友</div>
                     <div class="footer-btn share-btn" @click="handleShare">分享微信好友</div>
                     <div class="footer-btn confirm-btn" @click="handleConfirm">确认添加</div>
                     <div class="footer-btn confirm-btn" @click="handleConfirm">确认添加</div>
                 </div>
                 </div>
-                <div class="popup-footer" v-show="detailType === 'detail' && currentUserRole !== 3">
-                    <div class="footer-btn cancel-admin-btn" @click="handleDelete">删除成员</div>
-                </div>
+                <template v-if="currentPermissionItem.role !== 1">
+                    <div class="popup-footer" v-show="detailType === 'detail' && (currentUserRole === 1 || (currentUserRole !== 3 && currentEditPermissions))">
+                        <div class="footer-btn cancel-admin-btn" @click="handleDelete">删除成员</div>
+                    </div>
+                </template>
             </div>
             </div>
         </Popup>
         </Popup>
 
 
@@ -164,13 +166,14 @@ const getDutyList = async () => {
     if (data && data.length > 0) {
     if (data && data.length > 0) {
         dutyList.value = data;
         dutyList.value = data;
         // 默认选中第一个角色
         // 默认选中第一个角色
-        formData.duties = [data[0].code];
+        // formData.duties = [data[0].code];
     }
     }
 };
 };
 
 
 const teamList = ref([]);
 const teamList = ref([]);
 // 当前登录用户在团队中的角色(1 超管、2 项目管理员、3 普通成员)
 // 当前登录用户在团队中的角色(1 超管、2 项目管理员、3 普通成员)
 const currentUserRole = ref(null);
 const currentUserRole = ref(null);
+const currentEditPermissions = ref(false);
 //查询当前农资店的成员列表
 //查询当前农资店的成员列表
 const getManagerList = async () => {
 const getManagerList = async () => {
     const { data } = await VE_API.mine.listManagerList();
     const { data } = await VE_API.mine.listManagerList();
@@ -191,11 +194,13 @@ const getManagerList = async () => {
             );
             );
             if (selfItem) {
             if (selfItem) {
                 currentUserRole.value = selfItem.role;
                 currentUserRole.value = selfItem.role;
+                currentEditPermissions.value = selfItem.permissionList.includes("增删成员");
             }
             }
         }
         }
     } else {
     } else {
         teamList.value = [];
         teamList.value = [];
         currentUserRole.value = null;
         currentUserRole.value = null;
+        currentEditPermissions.value = false;
     }
     }
 };
 };
 
 
@@ -240,6 +245,10 @@ const togglePermission = (value) => {
 
 
 // 确认权限设置
 // 确认权限设置
 const handleConfirmPermission = async () => {
 const handleConfirmPermission = async () => {
+    if (selectedPermission.value.length === 0) {
+        ElMessage.warning("请至少选择一个权限");
+        return;
+    }
     if (route.query.add) {
     if (route.query.add) {
         const params = {
         const params = {
             ids: filterList.value.map((item) => item.id),
             ids: filterList.value.map((item) => item.id),
@@ -340,7 +349,7 @@ const handleCancelAdmin = async () => {
     ElMessageBox.confirm("确定取消管理员权限吗?", "提示", {
     ElMessageBox.confirm("确定取消管理员权限吗?", "提示", {
         confirmButtonText: "确定",
         confirmButtonText: "确定",
         cancelButtonText: "取消",
         cancelButtonText: "取消",
-        zIndex: 3000,
+        zIndex: 3500,
         type: "warning",
         type: "warning",
     })
     })
         .then(async () => {
         .then(async () => {
@@ -364,11 +373,11 @@ const handlePopupClosed = () => {
         formRef.value.resetFields();
         formRef.value.resetFields();
     }
     }
     // 重置为默认值(选中第一个角色)
     // 重置为默认值(选中第一个角色)
-    if (dutyList.value && dutyList.value.length > 0) {
-        formData.duties = [dutyList.value[0].code];
-    } else {
-        formData.duties = [];
-    }
+    // if (dutyList.value && dutyList.value.length > 0) {
+    //     formData.duties = [dutyList.value[0].code];
+    // } else {
+    //     formData.duties = [];
+    // }
 };
 };
 
 
 // 分享微信好友
 // 分享微信好友
@@ -447,10 +456,6 @@ const handleItem = (item) => {
     if (route.query.add) {
     if (route.query.add) {
         item.checked = !item.checked;
         item.checked = !item.checked;
     } else {
     } else {
-        if (item.role == 1) {
-            ElMessage.warning("超级管理员无法删除");
-            return;
-        }
         detailType.value = "detail";
         detailType.value = "detail";
         showAddMemberPopup.value = true;
         showAddMemberPopup.value = true;
         // 记录当前选中的成员,后续删除时使用
         // 记录当前选中的成员,后续删除时使用
@@ -458,9 +463,9 @@ const handleItem = (item) => {
         // 回填已知字段
         // 回填已知字段
         formData.name = item.name || "";
         formData.name = item.name || "";
         formData.phone = item.phone || "";
         formData.phone = item.phone || "";
-        if (Array.isArray(item.dutyEnumList)) {
-            formData.duties = [...item.dutyEnumList];
-        }
+        // if (Array.isArray(item.dutyEnumList)) {
+        //     formData.duties = [...item.dutyEnumList];
+        // }
     }
     }
 };
 };
 
 
@@ -468,7 +473,7 @@ const handleDelete = () => {
     ElMessageBox.confirm("确定删除该成员吗?", "提示", {
     ElMessageBox.confirm("确定删除该成员吗?", "提示", {
         confirmButtonText: "确定",
         confirmButtonText: "确定",
         cancelButtonText: "取消",
         cancelButtonText: "取消",
-        zIndex: 3000,
+        zIndex: 3500,
         type: "warning",
         type: "warning",
     })
     })
         .then(async () => {
         .then(async () => {

+ 3 - 3
src/views/old_mini/mine/pages/userInfo.vue

@@ -21,7 +21,7 @@
                         maxlength="11"
                         maxlength="11"
                     />
                     />
                 </el-form-item>
                 </el-form-item>
-                <el-form-item label="角色类型" prop="duties">
+                <!-- <el-form-item label="角色类型" prop="duties">
                     <div class="role-type-grid">
                     <div class="role-type-grid">
                         <div
                         <div
                             v-for="role in roleList"
                             v-for="role in roleList"
@@ -33,7 +33,7 @@
                             {{ role.name }}
                             {{ role.name }}
                         </div>
                         </div>
                     </div>
                     </div>
-                </el-form-item>
+                </el-form-item> -->
             </el-form>
             </el-form>
         </div>
         </div>
         <div class="custom-bottom-fixed-btns">
         <div class="custom-bottom-fixed-btns">
@@ -67,7 +67,7 @@ const getRoleList = async () => {
     if (data && data.length > 0) {
     if (data && data.length > 0) {
         roleList.value = data;
         roleList.value = data;
         // 默认选中第一个角色
         // 默认选中第一个角色
-        formData.duties = [data[0].code];
+        // formData.duties = [data[0].code];
     }
     }
 };
 };
 
 

+ 19 - 2
src/views/old_mini/modify_work/modify.vue

@@ -1,7 +1,7 @@
 <template>
 <template>
     <div class="new-farming-page" ref="pageRef">
     <div class="new-farming-page" ref="pageRef">
         <custom-header :name="isEdit ? '编辑方案' : '查看详情'"></custom-header>
         <custom-header :name="isEdit ? '编辑方案' : '查看详情'"></custom-header>
-        <div class="new-farming-content">
+        <div class="new-farming-content" :class="{ 'no-permission': !hasPlanPermission }">
             <el-form
             <el-form
                 ref="formRef"
                 ref="formRef"
                 style="max-width: 600px"
                 style="max-width: 600px"
@@ -488,7 +488,7 @@
                         </div>
                         </div>
                     </div>
                     </div>
                     
                     
-                    <div class="submit-btn center-btn">
+                    <div class="submit-btn center-btn" v-has-permission="'农事规划'">
                         <div class="btn" @click.prevent="toEditPrescription">编辑处方</div>
                         <div class="btn" @click.prevent="toEditPrescription">编辑处方</div>
                     </div>
                     </div>
                 </template>
                 </template>
@@ -524,6 +524,20 @@ const pageRef = ref(null);
 const router = useRouter();
 const router = useRouter();
 const route = useRoute();
 const route = useRoute();
 
 
+// 检查是否有"农事规划"权限
+const hasPlanPermission = computed(() => {
+    try {
+        const userInfoStr = localStorage.getItem("localUserInfo");
+        if (!userInfoStr) return false;
+        const userInfo = JSON.parse(userInfoStr);
+        const permissions = userInfo.agriculturalPermissions || [];
+        return permissions.includes("农事规划");
+    } catch (error) {
+        console.error("解析用户信息失败:", error);
+        return false;
+    }
+});
+
 const tagList = ref([
 const tagList = ref([
     { label: "托管农事", value: 2 },
     { label: "托管农事", value: 2 },
     { label: "特别关注", value: 1 },
     { label: "特别关注", value: 1 },
@@ -1179,6 +1193,9 @@ const handleDeleteInteract = () => {
         padding: 4px 12px 8px 12px;
         padding: 4px 12px 8px 12px;
         width: 100%;
         width: 100%;
         box-sizing: border-box;
         box-sizing: border-box;
+        &.no-permission{
+            margin-bottom: 12px;
+        }
 
 
         // ::v-deep {
         // ::v-deep {
         //     .el-select__input {
         //     .el-select__input {

+ 60 - 538
src/views/old_mini/monitor/subPages/plan.vue

@@ -8,88 +8,20 @@
                 </el-select>
                 </el-select>
                 <tab-list type="light" v-model="active" :tabs="tabs" @change="handleTabChange" />
                 <tab-list type="light" v-model="active" :tabs="tabs" @change="handleTabChange" />
             </div>
             </div>
-            <div
+            <farm-work-plan-timeline
                 class="timeline-container"
                 class="timeline-container"
-                ref="timelineContainerRef"
-                :class="{ 'timeline-container-plant': pageType === 'plant' }"
-            >
-                <div class="timeline-list" :style="getListStyle">
-                    <div class="timeline-middle-line"></div>
-                    <!-- 物候期覆盖条(progress 为起点,progress2 为终点,单位 %) -->
-                    <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">
-                                    <div
-                                        v-for="(fw, aIdx) in Array.isArray(r.farmWorkArrangeList)
-                                            ? r.farmWorkArrangeList
-                                            : []"
-                                        :key="fw.id ?? aIdx"
-                                        class="arrange-card"
-                                        :class="getArrangeStatusClass(fw)"
-                                        @click="handleRowClick(fw)"
-                                    >
-                                        <div class="card-header">
-                                            <div class="header-left">
-                                                <span class="farm-work-name">{{ fw.farmWorkName || "农事名称" }}</span>
-                                                <span class="tag-standard">标准农事</span>
-                                            </div>
-                                            <div class="header-right">托管农事</div>
-                                        </div>
-                                        <div class="card-content">
-                                            <span>{{ fw.warmReminder || "暂无提示" }}</span>
-                                            <span class="edit-link" @click.stop="handleEdit(fw)">点击编辑</span>
-                                        </div>
-                                        <div
-                                            v-if="
-                                                getArrangeStatusClass(fw) === 'status-complete' ||
-                                                getArrangeStatusClass(fw) === 'status-warning'
-                                            "
-                                            class="status-icon"
-                                            :class="getArrangeStatusClass(fw)"
-                                        >
-                                            <el-icon
-                                                v-if="getArrangeStatusClass(fw) === 'status-complete'"
-                                                size="16"
-                                                color="#1CA900"
-                                            >
-                                                <SuccessFilled />
-                                            </el-icon>
-                                            <el-icon v-else size="18" color="#FF953D">
-                                                <WarnTriangleFilled />
-                                            </el-icon>
-                                        </div>
-                                    </div>
-                                </div>
-                            </div>
-                        </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>
+                :class="{
+                    'timeline-container-plant': pageType == 'plant',
+                    'timeline-container-no-permission': !hasPlanPermission
+                }"
+                :pageType="pageType"
+                :farmId="route.query.farmId"
+                @row-click="handleRowClick"
+                @edit="handleEdit"
+                :disableClick="!hasPlanPermission"
+            />
         </div>
         </div>
-        <div class="custom-bottom-fixed-btns">
+        <div class="custom-bottom-fixed-btns" v-has-permission="'农事规划'">
             <div class="bottom-btn-group">
             <div class="bottom-btn-group">
                 <div class="bottom-btn secondary-btn" @click="handlePhenologySetting">物候期设置</div>
                 <div class="bottom-btn secondary-btn" @click="handlePhenologySetting">物候期设置</div>
                 <div class="bottom-btn secondary-btn" v-if="pageType === 'plant'" @click="openCopyPlanPopup">
                 <div class="bottom-btn secondary-btn" v-if="pageType === 'plant'" @click="openCopyPlanPopup">
@@ -104,7 +36,7 @@
     <!-- 互动设置弹窗 -->
     <!-- 互动设置弹窗 -->
     <interact-popup
     <interact-popup
         ref="interactPopupRef"
         ref="interactPopupRef"
-        @handleSaveSuccess="getFarmWorkPlan"
+        @handleSaveSuccess="getFarmWorkPlanForPhenology"
         @handleDeleteInteract="handleDeleteInteract"
         @handleDeleteInteract="handleDeleteInteract"
     ></interact-popup>
     ></interact-popup>
     <!-- 复制方案弹窗 -->
     <!-- 复制方案弹窗 -->
@@ -156,10 +88,11 @@
 </template>
 </template>
 
 
 <script setup>
 <script setup>
-import { ref, onMounted, computed, nextTick } from "vue";
+import { ref, onMounted, computed } from "vue";
 import { Popup } from "vant";
 import { Popup } from "vant";
 import customHeader from "@/components/customHeader.vue";
 import customHeader from "@/components/customHeader.vue";
 import tabList from "@/components/pageComponents/TabList.vue";
 import tabList from "@/components/pageComponents/TabList.vue";
+import FarmWorkPlanTimeline from "@/components/pageComponents/FarmWorkPlanTimeline.vue";
 import { useRouter, useRoute } from "vue-router";
 import { useRouter, useRoute } from "vue-router";
 import detailDialog from "@/components/detailDialog.vue";
 import detailDialog from "@/components/detailDialog.vue";
 import eventBus from "@/api/eventBus";
 import eventBus from "@/api/eventBus";
@@ -168,22 +101,19 @@ import { ElMessage } from "element-plus";
 const router = useRouter();
 const router = useRouter();
 const route = useRoute();
 const route = useRoute();
 
 
-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月
+// 检查是否有"农事规划"权限
+const hasPlanPermission = computed(() => {
+    try {
+        const userInfoStr = localStorage.getItem("localUserInfo");
+        if (!userInfoStr) return false;
+        const userInfo = JSON.parse(userInfoStr);
+        const permissions = userInfo.agriculturalPermissions || [];
+        return permissions.includes("农事规划");
+    } catch (error) {
+        console.error("解析用户信息失败:", error);
+        return false;
     }
     }
-};
+});
 
 
 const active = ref(1);
 const active = ref(1);
 const tabs = ref([
 const tabs = ref([
@@ -220,17 +150,38 @@ const options = ref([
 const pageType = ref("");
 const pageType = ref("");
 onMounted(() => {
 onMounted(() => {
     pageType.value = route.query.pageType || "";
     pageType.value = route.query.pageType || "";
-    getFarmWorkPlan();
+    getFarmWorkPlanForPhenology();
 });
 });
 
 
-const mergedReproductiveList = ref([])
-const getPhenologyList = async (containerSpaceTimeId) => {
-    const res = await VE_API.monitor.listPhenology({ containerSpaceTimeId ,farmId: route.query.farmId });
+const mergedReproductiveList = ref([]);
+const containerSpaceTimeId = ref(null);
+
+const getPhenologyList = async (containerSpaceTimeIdVal) => {
+    if (!containerSpaceTimeIdVal) return;
+    const res = await VE_API.monitor.listPhenology({
+        containerSpaceTimeId: containerSpaceTimeIdVal,
+        farmId: route.query.farmId,
+    });
     if (res.code === 0) {
     if (res.code === 0) {
         mergedReproductiveList.value = res.data || [];
         mergedReproductiveList.value = res.data || [];
     }
     }
 };
 };
 
 
+// 获取农事规划数据以获取 containerSpaceTimeId
+const getFarmWorkPlanForPhenology = async () => {
+    try {
+        const { data, code } = await VE_API.monitor.farmWorkPlan({
+            farmId: route.query.farmId,
+        });
+        if (code === 0 && data?.phenologyList?.[0]?.containerSpaceTimeId) {
+            containerSpaceTimeId.value = data.phenologyList[0].containerSpaceTimeId;
+            await getPhenologyList(containerSpaceTimeId.value);
+        }
+    } catch (error) {
+        console.error("获取农事规划数据失败:", error);
+    }
+};
+
 // 复制方案弹窗相关
 // 复制方案弹窗相关
 const showCopyPlan = ref(false);
 const showCopyPlan = ref(false);
 const showPhenologySetting = ref(false);
 const showPhenologySetting = ref(false);
@@ -252,7 +203,7 @@ const handleConfirmPhenologySetting = async () => {
     console.log(mergedReproductiveList.value);
     console.log(mergedReproductiveList.value);
     const params = {
     const params = {
         farmId: route.query.farmId,
         farmId: route.query.farmId,
-        items: mergedReproductiveList.value.map(item => ({
+        items: mergedReproductiveList.value.map((item) => ({
             phenologyId: item.id,
             phenologyId: item.id,
             startDate: item.startDate,
             startDate: item.startDate,
         })),
         })),
@@ -261,7 +212,7 @@ const handleConfirmPhenologySetting = async () => {
     if (res.code === 0) {
     if (res.code === 0) {
         ElMessage.success("设置成功");
         ElMessage.success("设置成功");
         showPhenologySetting.value = false;
         showPhenologySetting.value = false;
-        getFarmWorkPlan();
+        getFarmWorkPlanForPhenology();
     }
     }
 };
 };
 // 取消复制方案
 // 取消复制方案
@@ -280,83 +231,6 @@ const handleConfirmCopyPlan = () => {
     showCopyPlan.value = false;
     showCopyPlan.value = false;
 };
 };
 
 
-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 : [];
-                getPhenologyList(data.phenologyList[0]?.containerSpaceTimeId)
-                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) => {
-                          const reproductiveList = Array.isArray(it.reproductiveList)
-                              ? it.reproductiveList.map((r) => {
-                                    const farmWorkArrangeList = Array.isArray(r.farmWorkArrangeList)
-                                        ? r.farmWorkArrangeList.map((fw) => ({
-                                              ...fw,
-                                              containerSpaceTimeId: it.containerSpaceTimeId,
-                                          }))
-                                        : [];
-                                    return {
-                                        ...r,
-                                        farmWorkArrangeList,
-                                    };
-                                })
-                              : [];
-
-                          return {
-                              id: it.id ?? it.phenologyId ?? it.name ?? `${it.progress}-${it.progress2}`,
-                              progress: Number(it.progress) || 0, // 起点 %
-                              progress2: Number(it.progress2) || 0, // 终点 %
-                              // 兼容多种可能的开始时间字段
-                              startTimeMs: safeParseDate(
-                                  it.startDate || it.beginDate || it.startTime || it.start || it.start_at
-                              ),
-                              reproductiveList,
-                          };
-                      })
-                    : [];
-
-                // 等待 DOM 更新后处理滚动
-                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 addNewTask = () => {
 const addNewTask = () => {
@@ -391,190 +265,7 @@ const handleEdit = (item) => {
 };
 };
 
 
 const handleDeleteInteract = (params) => {
 const handleDeleteInteract = (params) => {
-    getFarmWorkPlan();
-};
-
-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;
-};
-
-// 计算最小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 minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP); // 避免除0
-    const total = (solarTerms.value?.length || 0) * 1200;
-    const minH = total; // 无上下留白
-    return { minHeight: `${minH}px` };
-});
-
-const getTermStyle = (t) => {
-    const p = Math.max(0, Math.min(100, Number(t?.progress) || 0));
-    const minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP); // 避免除0
-    const total = (solarTerms.value?.length || 0) * 1200;
-    // 将progress映射到0开始的位置,最小progress对应top: 0
-    const normalizedP = range > 0 ? ((p - minP) / range) * 100 : 0;
-    const top = (normalizedP / 100) * total;
-    return {
-        position: "absolute",
-        top: `${top}px`,
-        left: 0,
-        width: "30px",
-        height: "20px",
-        display: "flex",
-        alignItems: "flex-start",
-    };
-};
-
-// 点击季节 → 滚动到对应节气(立春/立夏/立秋/立冬)
-const handleSeasonClick = (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 minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP);
-    const total = (solarTerms.value?.length || 0) * 1200;
-    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;
-    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 minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP);
-    const total = (solarTerms.value?.length || 0) * 1200; // 有效绘制区高度(px)
-    // 将progress映射到0开始的位置
-    const normalizedStart = range > 0 ? ((start - minP) / range) * 100 : 0;
-    const normalizedEnd = range > 0 ? ((end - minP) / range) * 100 : 0;
-    let topPx = (normalizedStart / 100) * total;
-    let heightPx = Math.max(2, ((normalizedEnd - normalizedStart) / 100) * total);
-
-    // 计算第一个节气的位置(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近似
-    // 反转:大于当前时间用灰色,小于等于当前时间用蓝色
-    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 minP = minProgress.value;
-    const maxP = maxProgress.value;
-    const range = Math.max(1, maxP - minP);
-    const total = (solarTerms.value?.length || 0) * 1200;
-    // 将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;
-};
-
-// 计算 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;
+    getFarmWorkPlanForPhenology();
 };
 };
 </script>
 </script>
 
 
@@ -605,185 +296,16 @@ const getReproductiveItemHeight = (phenologyItem) => {
 
 
         .timeline-container {
         .timeline-container {
             height: calc(100vh - 40px - 73px);
             height: calc(100vh - 40px - 73px);
-            overflow: auto;
-            position: relative;
-            box-sizing: border-box;
             padding: 0 12px;
             padding: 0 12px;
             &.timeline-container-plant {
             &.timeline-container-plant {
                 height: calc(100vh - 40px - 73px - 38px);
                 height: calc(100vh - 40px - 73px - 38px);
             }
             }
-            .timeline-list {
-                position: relative;
-            }
-            .timeline-middle-line {
-                position: absolute;
-                left: 15px; /* 位于节气文字列中间(列宽约30px) */
-                top: 0;
-                bottom: 0;
-                width: 2px;
-                background: #e8e8e8;
-                z-index: 1;
-            }
-            .phenology-bar {
-                display: flex;
-                align-items: stretch;
-                justify-content: center;
-                box-sizing: border-box;
-                .reproductive-list {
-                    display: grid;
-                    grid-auto-rows: 1fr; /* 子项等高,整体等分父高度 */
-                    align-items: stretch;
-                    justify-items: center; /* 子项居中 */
-                    width: 100%;
-                    height: 100%;
-                    box-sizing: border-box;
-                }
-                .reproductive-item {
-                    font-size: 12px;
-                    text-align: center;
-                    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: 40px; /* 列与中线右侧一段距离 */
-                        top: 0;
-                        z-index: 3;
-                        display: flex;
-                        max-width: calc(100vw - 100px);
-                        gap: 12px;
-                        letter-spacing: 0px;
-                        .arrange-card {
-                            width: 97%;
-                            border: 0.5px solid #2199f8;
-                            border-radius: 8px;
-                            background: #fff;
-                            box-sizing: border-box;
-                            position: relative;
-                            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: 12px;
-                                        color: #000;
-                                    }
-                                }
-                                .header-right {
-                                    font-size: 12px;
-                                    color: #808080;
-                                    padding: 0 8px;
-                                    border-radius: 25px;
-                                }
-                            }
-                            .card-content {
-                                color: #909090;
-                                text-align: left;
-                                line-height: 1.55;
-                                margin: 4px 0 2px 0;
-                                .edit-link {
-                                    color: #2199f8;
-                                    margin-left: 5px;
-                                }
-                            }
-                            .status-icon {
-                                position: absolute;
-                                right: -8px;
-                                bottom: -8px;
-                                z-index: 3;
-                            }
-                            &::before {
-                                content: "";
-                                position: absolute;
-                                left: -6px;
-                                top: 50%;
-                                transform: translateY(-50%);
-                                width: 0;
-                                height: 0;
-                                border-top: 5px solid transparent;
-                                border-bottom: 5px solid transparent;
-                                border-right: 5px solid #2199f8;
-                            }
-                        }
-                        .arrange-card.status-warning {
-                            border-color: #ff953d;
-                            &::before {
-                                border-right-color: #ff953d;
-                            }
-                        }
-                        .arrange-card.status-complete {
-                            border-color: #1ca900;
-                            &::before {
-                                border-right-color: #1ca900;
-                            }
-                        }
-                        .arrange-card.status-normal {
-                            border-color: #2199f8;
-                            &::before {
-                                border-right-color: #2199f8;
-                            }
-                        }
-                    }
-                }
-            }
-            .reproductive-item + .reproductive-item {
-                border-top: 2px solid #fff;
-            }
-            .phenology-bar + .phenology-bar {
-                border-top: 2px solid #fff;
+            &.timeline-container-plant {
+                height: calc(100vh - 40px - 38px);
             }
             }
-            .timeline-term {
-                position: absolute;
-                width: 30px;
-                padding-right: 16px;
-                display: flex;
-                align-items: flex-start;
-                z-index: 2; /* 置于中线之上 */
-                .term-name {
-                    display: inline-block;
-                    width: 100%;
-                    height: 46px;
-                    line-height: 30px;
-                    background: #f5f7fb;
-                    font-size: 13px;
-                    word-break: break-all;
-                    writing-mode: vertical-rl;
-                    text-orientation: upright;
-                    color: rgba(174, 174, 174, 0.6);
-                    text-align: center;
-                }
+            // 没有权限时,底部按钮不显示,高度增加 73px
+            &.timeline-container-no-permission {
+                height: calc(100vh - 40px - 58px);
             }
             }
         }
         }
     }
     }