Ver código fonte

feat:修改bug

wangsisi 1 dia atrás
pai
commit
800415e8ef

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

@@ -63,4 +63,14 @@ module.exports = {
         url: config.base_dev_url + "container_farm_work_scheme/copyMyScheme",
         type: "get",
     },
+    //删除方案(逻辑删除)
+    deleteScheme: {
+        url: config.base_dev_url + "container_farm_work_scheme/deleteScheme",
+        type: "get",
+    },
+    //修改方案名称
+    renameScheme: {
+        url: config.base_dev_url + "container_farm_work_scheme/renameScheme?schemeId={schemeId}&name={name}",
+        type: "post",
+    },
 }

+ 121 - 0
src/components/pageComponents/AgriculturalInteractionCard.vue

@@ -0,0 +1,121 @@
+<template>
+    <!-- 只封装时间轴区域 -->
+    <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">
+                    <span class="work-name">{{ timelineItem.farmWorkName }}</span>
+                    <span v-show="timelineItem.expectedRisk">({{ timelineItem.expectedRisk }})</span>
+                    <span class="ignore-btn" @click="$emit('ignore', { timelineItem, farmId: item.farmId })">
+                        忽略
+                    </span>
+                </div>
+                <div class="text">
+                    预计报价<span class="price">{{ timelineItem.estimatedCost }}元</span>
+                    <span class="action-detail" @click="$emit('show-price-sheet', timelineItem)">查看报价单</span>
+                </div>
+            </div>
+            <div
+                v-has-permission="'转入农事'"
+                class="timeline-action"
+                @click="$emit('timeline-action', { timelineItem, farmId: item.farmId })"
+            >
+                转入农事任务
+            </div>
+        </div>
+    </div>
+</template>
+
+<script setup>
+const props = defineProps({
+    item: {
+        type: Object,
+        required: true,
+        default: () => ({}),
+    },
+});
+
+defineEmits(["timeline-action", "show-price-sheet", "ignore"]);
+</script>
+
+<style scoped lang="scss">
+.timeline {
+    margin-left: -5px;
+    margin-top: 8px;
+    .timeline-item {
+        display: flex;
+        align-items: flex-start;
+        font-size: 14px;
+        color: #ffffff;
+        line-height: 22px;
+        & + .timeline-item {
+            margin-top: 10px;
+        }
+        .timeline-left {
+            width: 22px;
+            display: flex;
+            flex-direction: column;
+            align-items: center;
+            .dot {
+                width: 6px;
+                height: 6px;
+                border-radius: 50%;
+                background: #a2d5fd;
+                margin-top: 6px;
+            }
+            .line {
+                border-left: 1px dashed #a2d5fd;
+                margin-top: 4px;
+                height: 28px;
+            }
+        }
+        .timeline-right {
+            padding-left: 5px;
+            flex: 1;
+            .date {
+                color: #1d2129;
+                font-weight: 500;
+                font-size: 14px;
+                line-height: 22px;
+            }
+            .text {
+                font-size: 12px;
+                color: #d7d7d7;
+                .price {
+                    padding-left: 4px;
+                }
+                .action-detail {
+                    margin-left: 6px;
+                    color: #2199f8;
+                    border-bottom: 1px solid;
+                }
+            }
+            .work-name {
+                padding-left: 4px;
+            }
+            .ignore-btn {
+                margin-left: 6px;
+                color: rgba(29, 33, 41, 0.4);
+                font-size: 13px;
+            }
+        }
+        .timeline-action {
+            align-self: center;
+            height: 28px;
+            line-height: 28px;
+            flex: none;
+            background: rgba(33, 153, 248, 0.1);
+            color: #2199f8;
+            font-size: 12px;
+            padding: 0px 11px;
+            border-radius: 20px;
+        }
+    }
+}
+</style>
+
+

+ 65 - 17
src/components/pageComponents/TabList.vue

@@ -1,5 +1,9 @@
 <template>
-    <div class="tabs-container" :class="[`tabs-type-${type}`, { 'tabs-small': tabs.length <= 2 }]">
+    <div
+        class="tabs-container"
+        :class="[`tabs-type-${type}`, { 'tabs-small': tabs.length <= 2 }]"
+        ref="tabsContainerRef"
+    >
         <div 
             v-for="(tab, index) in tabs" 
             :key="index"
@@ -13,34 +17,47 @@
 </template>
 
 <script setup>
+import { ref, watch, nextTick } from "vue";
+
 const props = defineProps({
     tabs: {
         type: Array,
         required: true,
-        default: () => []
+        default: () => [],
     },
     modelValue: {
         type: [Number, String],
-        default: 0
+        default: 0,
     },
     type: {
         type: String,
-        default: 'default', // 'default' | 'light'
-        validator: (value) => ['default', 'light'].includes(value)
-    }
+        default: "default", // 'default' | 'light'
+        validator: (value) => ["default", "light"].includes(value),
+    },
+    // 控制滚动方向:'left' | 'right' | 'auto' | ''
+    scrollType: {
+        type: String,
+        default: "",
+    },
 });
 
-const emit = defineEmits(['update:modelValue', 'change']);
+const emit = defineEmits(["update:modelValue", "change"]);
+
+const tabsContainerRef = ref(null);
 
 // 判断是否为对象格式的 tab
 const isObjectTab = (tab) => {
-    return typeof tab === 'object' && tab !== null && (tab.title !== undefined || tab.label !== undefined || tab.name !== undefined);
+    return (
+        typeof tab === "object" &&
+        tab !== null &&
+        (tab.title !== undefined || tab.label !== undefined || tab.name !== undefined)
+    );
 };
 
 // 获取 tab 的标题
 const getTabTitle = (tab) => {
     if (isObjectTab(tab)) {
-        return tab.title || tab.label || tab.name || '';
+        return tab.title || tab.label || tab.name || "";
     }
     return tab;
 };
@@ -48,10 +65,10 @@ const getTabTitle = (tab) => {
 // 判断当前 tab 是否激活
 const isActive = (index, tab) => {
     if (isObjectTab(tab)) {
-        // 对象格式:比较 value
-        if(tab.value){
+        // 对象格式:比较 value 或 id
+        if (tab.value) {
             return props.modelValue === tab.value;
-        }else{
+        } else {
             return props.modelValue === tab.id;
         }
     }
@@ -62,14 +79,45 @@ const isActive = (index, tab) => {
 const handleTabClick = (index, tab) => {
     if (isObjectTab(tab)) {
         // 对象格式:传递 value
-        emit('update:modelValue', tab.value);
-        emit('change', tab.value || tab.id, tab, index);
+        emit("update:modelValue", tab.value);
+        emit("change", tab.value || tab.id, tab, index);
     } else {
         // 字符串/简单格式:传递索引
-        emit('update:modelValue', index);
-        emit('change', index, tab);
+        emit("update:modelValue", index);
+        emit("change", index, tab);
     }
 };
+
+// 根据 scrollType 控制滚动:left -> 最左边;right -> 最右边;auto/空 -> 不动
+const scrollByType = (type) => {
+    const wrap = tabsContainerRef.value;
+    if (!wrap) return;
+
+    if (type === "left") {
+        wrap.scrollTo({
+            left: 0,
+            behavior: "smooth",
+        });
+        return;
+    }
+
+    if (type === "right") {
+        wrap.scrollTo({
+            left: wrap.scrollWidth,
+            behavior: "smooth",
+        });
+    }
+};
+
+watch(
+    () => props.scrollType,
+    (val) => {
+        if (!val || val === "auto") return;
+        nextTick(() => {
+            scrollByType(val);
+        });
+    }
+);
 </script>
 
 <style scoped lang="scss">
@@ -99,7 +147,7 @@ const handleTabClick = (index, tab) => {
         gap: 8px;
         
         .tab-item {
-            padding: 4px 12px;
+            padding: 4px 8px;
         }
     }
     

+ 9 - 96
src/views/old_mini/home/components/AgriculturalDynamics.vue

@@ -40,28 +40,13 @@
                         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">
-                                    <span class="work-name">{{ timelineItem.farmWorkName }}</span>
-                                    <span v-show="timelineItem.expectedRisk">({{timelineItem.expectedRisk}})</span>
-                                    <span class="ignore-btn" @click="handleIgnore(timelineItem)">忽略</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>
+                    <!-- 时间轴组件,只负责时间轴区域 -->
+                    <AgriculturalInteractionCard
+                        :item="item"
+                        @timeline-action="({ timelineItem, farmId }) => handleTimelineAction(timelineItem, farmId)"
+                        @show-price-sheet="showPriceSheetPopup"
+                        @ignore="handleIgnore"
+                    />
                 </div>
             </template>
         </div>
@@ -81,6 +66,7 @@ import offerPopup from "@/components/popup/offerPopup.vue";
 import eventBus from "@/api/eventBus";
 import activeUploadPopup from "@/components/popup/activeUploadPopup.vue";
 import priceSheetPopup from "@/components/popup/priceSheetPopup.vue";
+import AgriculturalInteractionCard from "@/components/pageComponents/AgriculturalInteractionCard.vue";
 // 任务列表数据
 const taskList = ref([
     { id: 1, title: "梢期杀虫", executionTime: "2025.05.06", status: "pending" },
@@ -166,6 +152,7 @@ const getUnansweredFarms = async () => {
     const params = {
         page: 0,
         limit: 3,
+        flowStatus: 5,
     };
     const res = await VE_API.home.listUnansweredFarms(params);
     unansweredList.value = (res.data || []).map((item) => ({
@@ -346,80 +333,6 @@ const getFutureFarmWorkWarning = async (item) => {
                     }
                 }
             }
-
-            .timeline {
-                margin-left: -5px;
-                margin-top: 8px;
-                .timeline-item {
-                    display: flex;
-                    align-items: flex-start;
-                    font-size: 14px;
-                    color: #ffffff;
-                    line-height: 22px;
-                    & + .timeline-item {
-                        margin-top: 10px;
-                    }
-                    .timeline-left {
-                        width: 22px;
-                        display: flex;
-                        flex-direction: column;
-                        align-items: center;
-                        .dot {
-                            width: 6px;
-                            height: 6px;
-                            border-radius: 50%;
-                            background: #a2d5fd;
-                            margin-top: 6px;
-                        }
-                        .line {
-                            border-left: 1px dashed #a2d5fd;
-                            margin-top: 4px;
-                            height: 28px;
-                        }
-                    }
-                    .timeline-right {
-                        padding-left: 5px;
-                        flex: 1;
-                        .date {
-                            color: #1d2129;
-                            font-weight: 500;
-                            font-size: 14px;
-                            line-height: 22px;
-                        }
-                        .text {
-                            font-size: 12px;
-                            color: #d7d7d7;
-                            .price {
-                                padding-left: 4px;
-                            }
-                            .action-detail {
-                                margin-left: 6px;
-                                color: #2199f8;
-                                border-bottom: 1px solid;
-                            }
-                        }
-                        .work-name {
-                            padding-left: 4px;
-                        }
-                        .ignore-btn{
-                            margin-left: 6px;
-                            color: rgba(29, 33, 41, 0.4);
-                            font-size: 13px;
-                        }
-                    }
-                    .timeline-action {
-                        align-self: center;
-                        height: 28px;
-                        line-height: 28px;
-                        flex: none;
-                        background: rgba(33, 153, 248, 0.1);
-                        color: #2199f8;
-                        font-size: 12px;
-                        padding: 0px 11px;
-                        border-radius: 20px;
-                    }
-                }
-            }
         }
     }
 }

+ 3 - 2
src/views/old_mini/mine/pages/serviceRecords.vue

@@ -3,7 +3,7 @@
         <custom-header name="服务记录"></custom-header>
         <div class="record-list">
             <div v-for="(item, index) in recordList" :key="index" class="record-card" @click="handleItemClick(item)">
-                <img class="thumb" :src="item.farmIcon" alt="农场缩略图" />
+                <img class="thumb" :src=" base_img_url2 + item.executeEvidenceList[0] + resize" alt="照片" />
                 <div class="card-body">
                     <div class="card-header">
                         <div class="title van-ellipsis">{{ item.farmWorkName }}</div>
@@ -26,8 +26,9 @@
 import { ref, onMounted } from "vue";
 import customHeader from "@/components/customHeader.vue";
 import { useRouter } from "vue-router";
+import { base_img_url2 } from "@/api/config";
 const router = useRouter();
-
+const resize = "?x-oss-process=image/resize,w_300";
 // 服务记录列表数据
 const recordList = ref([]);
 

+ 93 - 28
src/views/old_mini/monitor/subPages/plan.vue

@@ -3,7 +3,7 @@
         <custom-header :name="pageType === 'plant' ? '种植方案' : '农事规划'"></custom-header>
         <div class="plan-content">
             <div class="plan-content-header" v-if="pageType === 'plant'">
-                <el-select class="select-item" v-model="specieValue" placeholder="选择品类" @change="getListMySchemes">
+                <el-select class="select-item" v-model="specieValue" placeholder="选择品类" @change="() => getListMySchemes('left')">
                     <el-option
                         v-for="item in options"
                         :key="item.id"
@@ -11,7 +11,14 @@
                         :value="item.defaultContainerId"
                     />
                 </el-select>
-                <tab-list type="light" v-model="active" :tabs="tabs" @change="handleTabChange" />
+                <tab-list
+                    class="tabs-list"
+                    type="light"
+                    v-model="active"
+                    :tabs="tabs"
+                    :scrollType="scrollType"
+                    @change="handleTabChange"
+                />
             </div>
             <farm-work-plan-timeline
                 class="timeline-container"
@@ -20,7 +27,8 @@
                     'timeline-container-no-permission': !hasPlanPermission,
                 }"
                 :pageType="pageType"
-                :containerId="specieValue"
+                :farmId="route.query.farmId"
+                :containerId="containerIdData"
                 @row-click="handleRowClick"
                 @edit="handleEdit"
                 :disableClick="!hasPlanPermission"
@@ -30,7 +38,7 @@
             <div class="bottom-btn-group">
                 <div class="bottom-btn secondary-btn" @click="handlePhenologySetting">物候期设置</div>
                 <div class="bottom-btn secondary-btn" v-if="pageType === 'plant'" @click="openCopyPlanPopup">
-                    {{ defaultContainerId === specieValue ? "复制方案" : "方案设置" }}
+                    {{ active === tabs[0]?.id ? "复制方案" : "方案设置" }}
                 </div>
             </div>
             <div class="bottom-btn primary-btn" @click="addNewTask">新增农事</div>
@@ -48,13 +56,15 @@
     <!-- 复制方案弹窗 -->
     <Popup v-model:show="showCopyPlan" class="copy-plan-popup" round closeable :close-on-click-overlay="false">
         <div class="copy-plan-content">
-            <div class="label">{{ defaultContainerId === specieValue ? "复制为" : "方案名称" }}</div>
+            <div class="label">{{ active === tabs[0]?.id ? "复制为" : "方案名称" }}</div>
             <el-input v-model="copyPlanName" size="large" placeholder="请输入方案名称" class="copy-plan-input" />
         </div>
         <div class="copy-plan-footer">
-            <div class="btn btn-cancel" @click="handleCancelCopyPlan">{{ defaultContainerId === specieValue ? "取消复制" : "删除方案" }}</div>
+            <div class="btn btn-cancel" @click="handleCancelCopyPlan">
+                {{ active === tabs[0]?.id ? "取消复制" : "删除方案" }}
+            </div>
             <div class="btn btn-confirm" @click="handleConfirmCopyPlan">
-                {{ active === 1 ? "确定复制" : "确定设置" }}
+                {{ active === tabs[0]?.id ? "确定复制" : "确定设置" }}
             </div>
         </div>
     </Popup>
@@ -100,7 +110,7 @@ import { useRouter, useRoute } from "vue-router";
 import detailDialog from "@/components/detailDialog.vue";
 import eventBus from "@/api/eventBus";
 import interactPopup from "@/components/popup/interactPopup.vue";
-import { ElMessage } from "element-plus";
+import { ElMessage,ElMessageBox } from "element-plus";
 const router = useRouter();
 const route = useRoute();
 
@@ -122,49 +132,61 @@ const pageType = ref("");
 
 onMounted(() => {
     pageType.value = route.query.pageType || "";
-    if (pageType.value === "plant") {
-        getSpecieList();
-    }
+    getSpecieList();
 });
 
 // 获取品类列表
 const specieValue = ref(null);
-const defaultContainerId = ref(null);
 const options = ref([]);
 const userInfo = JSON.parse(localStorage.getItem("localUserInfo"));
 const getSpecieList = () => {
     VE_API.farm.fetchSpecieList({ agriculturalId: userInfo.agriculturalId }).then(({ data }) => {
         options.value = data || [];
         specieValue.value = data[0].defaultContainerId;
-        defaultContainerId.value = data[0].defaultContainerId;
-        getListMySchemes();
+        getListMySchemes('left');
     });
 };
 
 // 获取方案列表
 const active = ref(null);
 const tabs = ref([]);
-const getListMySchemes = () => {
+// 控制标签滚动方向:'left' | 'right' | 'auto' | ''
+const scrollType = ref("auto");
+const containerIdData = ref(null);
+const getListMySchemes = (type = "auto") => {
     VE_API.home.listMySchemes({ containerId: specieValue.value }).then(({ data }) => {
         tabs.value = data || [];
-        active.value = data[0].id;
-        defaultContainerId.value = data[0].containerId;
+        containerIdData.value = data[0]?.containerId;
+        if (type === "right") {
+            active.value = data[data.length - 1].id;
+        } else if (type === "left") {
+            active.value = data[0].id;
+        } else {
+            currentTab.value = data.filter((item) => item.id === currentTab.value.id)[0];
+            copyPlanName.value = currentTab.value.name;
+        }
+        scrollType.value = type;
         getFarmWorkPlanForPhenology();
     });
 };
 
+const currentTab = ref(null);
 const handleTabChange = (id, item) => {
     active.value = id;
-    console.log(id, item);
+    currentTab.value = item;
+    getFarmWorkPlanForPhenology();
 };
 
 const mergedReproductiveList = ref([]);
 const containerSpaceTimeId = ref(null);
 
 const getPhenologyList = async () => {
-    const res = await VE_API.monitor.listPhenology({
+    const params = {
         containerSpaceTimeId: containerSpaceTimeId.value,
-    });
+        agriculturalId: userInfo.agriculturalId,
+        farmId: route.query.farmId,
+    }
+    const res = await VE_API.monitor.listPhenology(params);
     if (res.code === 0) {
         mergedReproductiveList.value = res.data || [];
     }
@@ -192,6 +214,9 @@ const copyPlanName = ref("");
 const openCopyPlanPopup = () => {
     copyPlanName.value = "";
     showCopyPlan.value = true;
+    if (active.value !== tabs.value[0]?.id) {
+        copyPlanName.value = currentTab.value.name;
+    }
 };
 
 // 物候期设置弹窗
@@ -205,6 +230,7 @@ const handlePhenologySetting = () => {
 const handleConfirmPhenologySetting = async () => {
     const params = {
         farmId: route.query.farmId,
+        agriculturalId: userInfo.agriculturalId,
         items: mergedReproductiveList.value.map((item) => ({
             phenologyId: item.id,
             startDate: item.startDate,
@@ -219,7 +245,27 @@ const handleConfirmPhenologySetting = async () => {
 };
 // 取消复制方案
 const handleCancelCopyPlan = () => {
-    showCopyPlan.value = false;
+    if (active.value === tabs.value[0]?.id) {
+        showCopyPlan.value = false;
+    } else {
+        ElMessageBox.confirm("确定要删除该方案吗?", "提示", {
+            confirmButtonText: "确定",
+            cancelButtonText: "取消",
+            type: "warning",
+        }).then(() => {
+            VE_API.monitor
+                .deleteScheme({
+                    schemeId: active.value,
+                })
+                .then(({ code }) => {
+                    if (code === 0) {
+                        showCopyPlan.value = false;
+                        ElMessage.success("删除成功");
+                        getListMySchemes('left');
+                    }
+                });
+        });
+    }
 };
 
 // 确定复制方案
@@ -228,15 +274,30 @@ const handleConfirmCopyPlan = () => {
         ElMessage.warning("请输入方案名称");
         return;
     }
-    if (defaultContainerId.value === specieValue.value) {
-        VE_API.monitor.copyScheme({
-            containerId: specieValue.value,
-            sourceSchemeId: active.value,
+    if (active.value === tabs.value[0]?.id) {
+        VE_API.monitor
+            .copyScheme({
+                containerId: specieValue.value,
+                sourceSchemeId: active.value,
+                schemeName: copyPlanName.value,
+            })
+            .then(({ code,data }) => {
+                if (code === 0) {
+                    showCopyPlan.value = false;
+                    ElMessage.success("复制成功");
+                    getListMySchemes('right');
+                    currentTab.value = data
+                }
+            });
+    }else{
+        VE_API.monitor.renameScheme({
+            schemeId: active.value,
+            name: copyPlanName.value,
         }).then(({ code }) => {
             if (code === 0) {
                 showCopyPlan.value = false;
-                ElMessage.success("复制成功");
-                getListMySchemes();
+                ElMessage.success("修改成功");
+                getListMySchemes('auto');
             }
         });
     }
@@ -274,7 +335,6 @@ const handleRowClick = (item) => {
 const interactPopupRef = ref(null);
 
 const handleEdit = (item) => {
-    console.log(item);
     if (interactPopupRef.value) {
         interactPopupRef.value.showPopup(item);
     }
@@ -308,6 +368,10 @@ const handleDeleteInteract = (params) => {
                     }
                 }
             }
+            .tabs-list {
+                width: calc(100% - 94px);
+                margin-right: 8px;
+            }
         }
 
         .timeline-container {
@@ -340,6 +404,7 @@ const handleDeleteInteract = (params) => {
         left: 0;
         width: 100%;
         height: 136px;
+        pointer-events: none;
         background: url("@/assets/img/monitor/popup-header-bg.png") no-repeat center center / 100% 100%;
     }
     .copy-plan-content {