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

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

wangsisi 23 часов назад
Родитель
Сommit
8799e1073a

+ 3 - 3
src/views/old_mini/dev_login.vue

@@ -62,9 +62,9 @@ onMounted(async () => {
     }
     // 进入首页时请求接口,确定是否为托管农户
     await fetchUserType();
-    await getFarmList(() => {
-        targetUrl = '/create_farm?type=farmer&expertMiniUserId=81881&isReload=true';
-    });
+    // await getFarmList(() => {
+    //     targetUrl = '/create_farm?type=farmer&expertMiniUserId=81881&isReload=true';
+    // });
     
     localStorage.setItem("MINI_USER_LOCATION", route.query.point)
     localStorage.setItem("MINI_USER_LOCATION_POINT", `POINT(${pointXy[0]} ${pointXy[1]})`)

+ 1 - 0
src/views/old_mini/home/index.vue

@@ -209,6 +209,7 @@ const rows = ref([
 const handleBlockClick = (block) => {
     // 预留点击跳转/弹窗逻辑
     console.log("点击卡片:", block);
+    router.push(`/work_detail?id=${block.id}`);
 };
 
 const handleMoreClick = () => {

+ 1 - 1
src/views/old_mini/interaction/index.vue

@@ -47,7 +47,7 @@
                     <div class="field-row">
                         <div class="field-label">第一批花穗时间</div>
                         <div class="field-value">
-                            <el-date-picker style="width: 100%" v-model="variety.firstFlowerDate" class="date-picker" type="date"
+                            <el-date-picker :editable="false" style="width: 100%" v-model="variety.firstFlowerDate" class="date-picker" type="date"
                                 placeholder="选择时间" format="YYYY-MM-DD" value-format="YYYY-MM-DD" />
                         </div>
                     </div>

+ 263 - 0
src/views/old_mini/work_detail/components/executePopup.vue

@@ -0,0 +1,263 @@
+<template>
+    <popup v-model:show="showValue" class="execute-popup" closeable>
+        <div class="popup-content">
+            <div class="time-wrap">
+                <div class="name">
+                    请选择实际执行时间
+                </div>
+                <div class="time-input">
+                    <el-date-picker format="YYYY-MM-DD" value-format="YYYY-MM-DD" v-model="executeTime"
+                        :disabled-date="disabledDate" size="large" style="width: 100%" type="date" placeholder="请选择日期"
+                        :editable="false" />
+                </div>
+            </div>
+            <div class="upload-wrap" :class="{ 'upload-cont': fileList.length }">
+                <div class="name">执行照片</div>
+                <uploader class="uploader" v-model="fileList" multiple :max-count="5" :after-read="afterRead"
+                    @click="handleClick('rg')">
+                    <img class="img" v-show="!fileList.length" src="@/assets/img/home/example-4.png" alt="" />
+                    <img class="plus" src="@/assets/img/home/plus.png" alt="" />
+                </uploader>
+            </div>
+            <div class="button-wrap">
+                <div @click="closeTask" class="button primary">确认上传</div>
+            </div>
+        </div>
+    </popup>
+
+    <popup v-model:show="showRemindValue" class="execute-popup" closeable>
+        <div class="popup-content">
+            <div class="name">
+                <span class="required">*</span>请选择下次提醒时间
+            </div>
+            <div class="time-number-input">
+                <el-input-number v-model="remindTime" :min="1" :max="30" :step="1" size="large" style="width: 100%" />
+                <span class="time-unit">天后</span>
+            </div>
+            <div class="button-wrap">
+                <div @click="handleRemind" class="button primary">确认</div>
+            </div>
+        </div>
+    </popup>
+</template>
+
+<script setup>
+import { Popup, Uploader } from "vant";
+import { ref } from "vue";
+import { ElMessage } from "element-plus";
+import { getFileExt } from "@/utils/util";
+import UploadFile from "@/utils/upliadFile";
+import { useStore } from "vuex";
+
+const store = useStore();
+const miniUserId = store.state.home.miniUserId;
+
+const props = defineProps({
+    executionData: {
+        type: Object,
+        default: () => ({}),
+    },
+});
+
+const showValue = ref(false);
+
+const fileList = ref([]);
+const fileArr = ref([]);
+const executeTime = ref("");
+const uploadFileObj = new UploadFile();
+
+const afterRead = (file) => {
+    // 处理多张照片的情况:file 可能是数组
+    const files = Array.isArray(file) ? file : [file];
+
+    files.forEach((item) => {
+        // 将文件上传至服务器
+        let fileVal = item.file;
+        if (!fileVal) return; // 如果没有 file 属性,跳过
+
+        item.status = "uploading";
+        item.message = "上传中...";
+        let ext = getFileExt(fileVal.name);
+        let key = `birdseye-look-mini/${miniUserId}/${new Date().getTime()}.${ext}`;
+        uploadFileObj.put(key, fileVal).then((resFilename) => {
+            item.status = "done";
+            item.message = "";
+            fileArr.value.push(resFilename);
+        }).catch(() => {
+            item.status = 'failed';
+            item.message = '上传失败';
+            ElMessage.error('图片上传失败,请稍后再试!')
+        });
+    });
+};
+
+function closeTask() {
+    // stepIndex.value = 2
+    if (!fileArr.value.length) return ElMessage.warning('请上传至少两张图片')
+    if (!executeTime.value) return ElMessage.warning('请选择实际执行时间')
+    console.log(fileArr.value, executeTime.value)
+    const params = {
+        // recordId: executionData.value.id,
+        executeDate: executeTime.value,
+        executeEvidence: fileArr.value,
+    }
+    // VE_API.z_farm_work_record.addExecuteImgAndComplete(params).then((res) => {
+    //     if (res.code === 0) {
+    //         ElMessage.success('上传成功')
+    //         showValue.value = false
+    //     }
+    // })
+}
+
+const handleClick = () => {
+    // 预留点击上传区域的埋点或其它逻辑
+};
+
+const disabledDate = (time) => {
+    // 示例:不允许选择未来日期
+    return time.getTime() > Date.now();
+};
+
+const showRemindValue = ref(false);
+const remindTime = ref("");
+
+function showRemindPopup() {
+    showRemindValue.value = true;
+}
+
+function handleRemind() {
+    if (!remindTime.value) return ElMessage.warning('请选择下次提醒时间')
+    console.log(remindTime.value)
+    showRemindValue.value = false;
+}
+
+function openPopup() {
+    showValue.value = true;
+}
+
+defineExpose({
+    openPopup,
+    showRemindPopup,
+});
+</script>
+
+<style lang="scss" scoped>
+.execute-popup {
+    width: 90%;
+    border-radius: 8px;
+
+    .popup-content {
+        padding: 24px 16px 20px 16px;
+        background: linear-gradient(360deg, #FFFFFF 74.2%, #D1EBFF 100%);
+        border-radius: 8px;
+    }
+}
+
+.name {
+    color: #000000;
+    font-size: 16px;
+    font-weight: 500;
+    padding-bottom: 12px;
+
+    .required {
+        color: #ff4d4f;
+        margin-right: 2px;
+    }
+}
+
+.upload-wrap {
+
+    &.upload-cont {
+        ::v-deep {
+            .van-uploader__wrapper {
+                flex-wrap: nowrap;
+            }
+        }
+    }
+
+    .img {
+        width: 80px;
+        height: 80px;
+        margin-right: 12px;
+    }
+
+    .plus {
+        margin-right: 12px;
+        width: 80px;
+        height: 80px;
+    }
+}
+
+.upload-wrap {
+    ::v-deep {
+        .van-image__img {
+            border-radius: 8px;
+        }
+
+        .avatar-uploader .el-upload {
+            width: 100%;
+            border: 1px dashed #dddddd;
+            border-radius: 6px;
+            cursor: pointer;
+            position: relative;
+            overflow: hidden;
+        }
+
+        .van-uploader,
+        .van-uploader__wrapper,
+        .van-uploader__input-wrapper {
+            width: 100%;
+        }
+
+        .el-icon.avatar-uploader-icon {
+            font-size: 28px;
+            color: #8c939d;
+            width: 100%;
+            height: 128px;
+            text-align: center;
+            background: #f6f6f6;
+        }
+    }
+}
+
+.button-wrap {
+    display: flex;
+    justify-content: center;
+    margin: 24px 4px 0 4px;
+
+    .button {
+        border-radius: 20px;
+        color: #fff;
+        padding: 7px 0;
+        text-align: center;
+        font-size: 16px;
+
+        &.primary {
+            flex: 1;
+            background: #2199f8;
+            color: #fff;
+        }
+    }
+}
+
+.time-wrap {
+    margin-bottom: 12px;
+
+    .time-input {
+        width: 100%;
+    }
+}
+
+.time-number-input {
+    display: flex;
+    align-items: center;
+    width: 100%;
+
+    .time-unit {
+        font-size: 14px;
+        color: rgba(0, 0, 0, 0.4);
+        flex: none;
+        margin-left: 8px;
+    }
+}
+</style>

+ 149 - 40
src/views/old_mini/work_detail/index.vue

@@ -5,7 +5,7 @@
         <div class="work-detail-content">
             <!-- 顶部状态 -->
             <div class="content-status" :class="'status-' + detail?.flowStatus">
-                <div class="status-l">
+                <div class="status-l" v-if="userInfo?.appType === 2">
                     <div class="status-title">{{ statusText }}</div>
                     <div class="status-sub" v-if="triggerDateText && detail?.flowStatus === 0">
                         预计触发时间 {{ triggerDateText }}
@@ -14,6 +14,15 @@
                         任务进度:1/2
                     </div>
                 </div>
+                <div class="status-l" v-else>
+                    <div class="status-title">{{ statusText }}</div>
+                    <div class="status-sub" v-if="triggerDateText && detail?.flowStatus === 0">
+                        预计触发时间 {{ triggerDateText }}
+                    </div>
+                    <div class="status-sub" v-if="detail?.flowStatus === 1">
+                        距离执行时间还差 {{ daysDiff }} 天
+                    </div>
+                </div>
             </div>
 
             <div class="work-wrap">
@@ -26,10 +35,12 @@
                 </div> -->
 
                 <!-- 每一段农事 -->
-                <div v-for="(prescription, index) in stageList" :key="index" class="box-wrap stage-card" :class="'status-' + prescription.tagType">
+                <div v-for="(prescription, index) in stageList" :key="index" class="box-wrap stage-card"
+                    :class="'status-' + prescription.tagType">
                     <div class="stage-header">
-                        <div class="stage-title">{{ getStageTitle(index, prescription) }}</div>
-                        <div class="title-tag" :class="'tag-' + prescription.tagType">{{ prescription.tagType === 0 ? "待完成" : "已完成" }}</div>
+                        <div class="stage-title">{{ detail.farmWorkName }}</div>
+                        <div class="title-tag" :class="'tag-' + prescription.tagType">{{ prescription.tagType === 0 ?
+                            "待完成" : "已完成" }}</div>
                     </div>
 
                     <div class="stage-info">
@@ -64,13 +75,9 @@
 
                     <!-- 执行方式 -->
                     <div class="stage-tabs">
-                        <div
-                            v-for="tab in executionTabs"
-                            :key="tab.value"
-                            class="tab-pill"
+                        <div v-for="tab in executionTabs" :key="tab.value" class="tab-pill"
                             :class="{ active: getStageExecutionMethod(index) === tab.value }"
-                            @click="changeExecutionMethod(index, tab.value)"
-                        >
+                            @click="changeExecutionMethod(index, tab.value)">
                             {{ tab.label }}
                         </div>
                     </div>
@@ -85,11 +92,7 @@
                                 <div class="col col-ratio">药肥配比</div>
                             </div>
 
-                            <div
-                                v-for="(item, i) in prescription.pesticideList"
-                                :key="i"
-                                class="table-row"
-                            >
+                            <div v-for="(item, i) in prescription.pesticideList" :key="i" class="table-row">
                                 <div class="col col-type">
                                     {{ item.typeName || "--" }}
                                 </div>
@@ -103,10 +106,7 @@
                         </div>
 
                         <div v-if="hasRemark(prescription, index)" class="prescription-remark">
-                            <span
-                                v-for="(item, idx) in [prescription.pesticideList[0]]"
-                                :key="idx"
-                            >
+                            <span v-for="(item, idx) in [prescription.pesticideList[0]]" :key="idx">
                                 <template v-if="getParamRemark(item, index)">
                                     {{ getParamRemark(item, index) }}
                                     <br />
@@ -114,10 +114,27 @@
                             </span>
                         </div>
                     </div>
+
+
+                    <!-- 执行照片 -->
+                    <div class="photo-box" v-if="prescription.cropAlbum && prescription.cropAlbum.length">
+                        <div class="photo-title">执行照片</div>
+                        <div class="photo-img-wrap">
+                            <photo-provider :photo-closable="true">
+                                <photo-consumer v-for="(src, index) in prescription.cropAlbum" intro="执行照片" :key="index"
+                                    :src="base_img_url2 + src.filename">
+                                    <div class="photo-img">
+                                        <img :src="base_img_url2 + src.filename" />
+                                    </div>
+                                </photo-consumer>
+                            </photo-provider>
+                        </div>
+                    </div>
+
                     <!-- 执行操作 -->
                     <div class="execute-action" v-if="info?.appType === 1 && prescription.tagType === 0">
-                        <div class="action-item second">稍后执行</div>
-                        <div class="action-item primary">我已完成</div>
+                        <div class="action-item second" @click="handleRemind">稍后执行</div>
+                        <div class="action-item primary" @click="handleExecute">我已完成</div>
                     </div>
                 </div>
 
@@ -129,19 +146,24 @@
                 </div>
             </div>
         </div>
+        <ExecutePopup ref="executePopupRef" />
     </div>
 </template>
 
 <script setup>
+import wx from "weixin-js-sdk";
 import customHeader from "@/components/customHeader.vue";
 import { ref, computed, onActivated } from "vue";
 import { useRoute, useRouter } from "vue-router";
 import { formatDate } from "@/common/commonFun";
+import ExecutePopup from "./components/executePopup.vue";
+import { base_img_url2, resize_300 } from "@/api/config";
 
 const route = useRoute();
 const router = useRouter();
-const info = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
-console.log('info', info);
+// const info = JSON.parse(localStorage.getItem("localUserInfo") || "{}");
+const info = { appType: 1 };
+
 const detail = ref({
     "consequenceText": "",
     "id": null,
@@ -165,8 +187,8 @@ const detail = ref({
     "agriculturalIcon": "",
     "farmWorkId": "793434539249635329",
     "farmWorkLibId": "793434539249635329",
-    "farmWorkLibName": "蒂蛀虫防治",
-    "farmWorkName": "蒂蛀虫防治",
+    "farmWorkLibName": "第一段蒂蛀虫防治",
+    "farmWorkName": "第一段蒂蛀虫防治",
     "expertName": "",
     "expertUserName": "",
     "expertIcon": "",
@@ -174,7 +196,7 @@ const detail = ref({
     "icon": null,
     "indexName": "",
     "indexChart": [],
-    "executeDate": "2025-08-15",
+    "executeDate": "2026-03-15",
     "intervelTime": 10,
     "executeDeadlineDate": null,
     "expectedExecuteDate": null,
@@ -226,6 +248,14 @@ const detail = ref({
         {
             "name": "蒂蛀虫防治",
             "tagType": 1,
+            "cropAlbum": [
+                {
+                    "filename": "6e707c1f-7095-409e-8b3a-2633e7b36bd5/1e2dc0e7-6f8b-4952-a574-8e8e0713529c/DJI_202601251415_001_1e2dc0e7-6f8b-4952-a574-8e8e0713529c/DJI_20260125141713_0003_V_code-ws0gefwtn5n4.jpeg"
+                },
+                {
+                    "filename": "dfb387fa-e7d3-4f49-a40c-fcbb9be2baf9/c727d4e9-f5bb-4e40-be2d-efdec3b91702/DJI_202511270800_001_c727d4e9-f5bb-4e40-be2d-efdec3b91702/DJI_20251127080720_0056_V_code-ws0gefyb37kp.jpeg"
+                }
+            ],
             "pesticideList": [
                 {
                     "code": "1009",
@@ -397,6 +427,27 @@ const detail = ref({
     "postId": "806490699515039744"
 });
 
+
+// 计算距离执行时间的天数差
+const daysDiff = computed(() => {
+    if (!detail.value?.executeDate) {
+        return 0;
+    }
+
+    const executeDate = new Date(detail.value.executeDate);
+    const today = new Date();
+
+    // 将时间设置为 00:00:00,只比较日期
+    executeDate.setHours(0, 0, 0, 0);
+    today.setHours(0, 0, 0, 0);
+
+    // 计算天数差(毫秒转天数)
+    const diffTime = executeDate.getTime() - today.getTime();
+    const diffDays = Math.floor(diffTime / (1000 * 60 * 60 * 24));
+
+    return diffDays;
+});
+
 // 执行方式 Tab 配置
 const executionTabs = [
     { label: "植保机", value: 1 },
@@ -405,7 +456,7 @@ const executionTabs = [
 ];
 // 每一段农事的当前执行方式(索引 -> 执行方式),默认 1:植保机
 const stageExecutionMethods = ref({});
-
+const executePopupRef = ref(null);
 const statusText = computed(() => {
     // 简单根据 flowStatus 判断,默认“待触发”
     if (detail.value.flowStatus === 1) return "待完成";
@@ -419,19 +470,8 @@ const triggerDateText = computed(() => {
     return d.replace(/-/g, ".");
 });
 
-// const displayExecuteDate = computed(() => {
-//     if (!detail.value.executeDate) return "";
-//     return formatDate(detail.value.executeDate);
-// });
-
 const stageList = computed(() => detail.value.groupList || []);
 
-const getStageTitle = (index, prescription) => {
-    const numMap = ["一", "二", "三", "四", "五", "六", "七"];
-    const prefix = numMap[index] ? `第${numMap[index]}段` : `第${index + 1}段`;
-    const name = detail.value.farmWorkName || prescription?.name || "";
-    return name ? `${prefix}${name}` : prefix;
-};
 
 const hasRemark = (prescription, stageIndex) => {
     if (!prescription?.pesticideList || !Array.isArray(prescription.pesticideList)) return false;
@@ -443,12 +483,29 @@ const hasRemark = (prescription, stageIndex) => {
     });
 };
 
+const handleExecute = () => {
+    executePopupRef.value.openPopup();
+};
+
+const handleRemind = () => {
+    executePopupRef.value.showRemindPopup();
+};
+
 const handleBack = () => {
     router.back();
 };
 
 const handleConvert = () => {
-    // 预留“转成农事”逻辑,后续接入实际接口
+    const query = {
+        askInfo: { title: "农情互动", content: "是否分享该互动给好友" },
+        shareText: '邀请您农情互动,精准匹配种植方案',
+        targetUrl: `interaction`,
+        paramsPage: JSON.stringify({ id: detail.value.id }),
+        imageUrl: 'https://birdseye-img.sysuimars.com/birdseye-look-mini/work_cover.png',
+    };
+    wx.miniProgram.navigateTo({
+        url: `/pages/subPages/share_page/index?pageParams=${JSON.stringify(query)}&type=sharePage`,
+    });
 };
 
 // 获取当前段的执行方式
@@ -511,6 +568,7 @@ const changeExecutionMethod = (stageIndex, value) => {
         // background: #FF953D;
         width: 100%;
     }
+
     &.status-1 {
         &::after {
             background: #FF953D;
@@ -542,11 +600,24 @@ const changeExecutionMethod = (stageIndex, value) => {
     padding: 14px 10px 10px 10px;
     box-shadow: 0 2px 8px rgba(15, 35, 52, 0.06);
     border: 1px solid transparent;
+
     &.status-0 {
         border: 1px solid #FF953D;
     }
 }
 
+.photo-box {
+    margin-top: 10px;
+    border: 0.5px dashed #2199F8;
+    border-radius: 5px;
+    padding: 11px 10px;
+    .photo-title {
+        font-size: 14px;
+        color: #000;
+        padding-bottom: 8px;
+    }
+}
+
 .group-info {
     margin-bottom: 10px;
 
@@ -579,9 +650,11 @@ const changeExecutionMethod = (stageIndex, value) => {
         line-height: 1.5;
     }
 }
-.stage-card + .stage-card {
+
+.stage-card+.stage-card {
     margin-top: 10px;
 }
+
 .stage-card {
 
     .stage-header {
@@ -594,6 +667,7 @@ const changeExecutionMethod = (stageIndex, value) => {
             font-size: 16px;
             color: #000;
         }
+
         .title-tag {
             width: fit-content;
             font-size: 12px;
@@ -604,6 +678,7 @@ const changeExecutionMethod = (stageIndex, value) => {
             border-radius: 20px;
             padding: 0 8px;
         }
+
         .tag-0 {
             color: #FF953D;
             background: rgba(255, 149, 61, 0.1);
@@ -728,12 +803,44 @@ const changeExecutionMethod = (stageIndex, value) => {
     line-height: 21px;
 }
 
+
+.photo-img-wrap {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 10px;
+
+    ::v-deep {
+        .PhotoConsumer {
+            width: 31%;
+            height: 92px;
+        }
+    }
+
+    .photo-img {
+        width: 100%;
+        height: 100%;
+        position: relative;
+        box-sizing: border-box;
+        border: 2px solid transparent;
+        border-radius: 8px;
+        overflow: hidden;
+
+        img {
+            width: 100%;
+            height: 100%;
+            border-radius: 8px;
+            object-fit: cover;
+        }
+    }
+}
+
 .execute-action {
     margin-top: 8px;
     display: flex;
     align-items: center;
     justify-content: center;
     gap: 16px;
+
     .action-item {
         padding: 0 16px;
         height: 32px;
@@ -741,11 +848,13 @@ const changeExecutionMethod = (stageIndex, value) => {
         border-radius: 16px;
         box-sizing: border-box;
         font-size: 14px;
+
         &.second {
             background: #ffffff;
             border: 0.5px solid rgba(153, 153, 153, 0.5);
             color: #999999;
         }
+
         &.primary {
             background: linear-gradient(180deg, #8ACBFF 0%, #2199F8 100%);
             color: #ffffff;