Ver Fonte

feat:修改农情互动和添加弹窗逻辑

wangsisi há 1 semana atrás
pai
commit
eb621d61f7

+ 52 - 39
src/components/popup/farmInfoPopup.vue

@@ -9,7 +9,7 @@
         <!-- 第一步:农场信息填写 -->
         <template v-if="currentStep === 1">
             <!-- 标题 -->
-            <div class="popup-title">为了更方便分析农场问题,请先完善您的农场信息</div>
+            <div class="popup-title">完善以下信息,生成智能种植方案</div>
 
             <!-- 表单区域 -->
             <el-form ref="formRef" :model="formData" :rules="rules" class="farm-form">
@@ -20,35 +20,37 @@
 
                 <!-- 农场品种 -->
                 <el-form-item label="农场品种" prop="speciesId">
-                    <div class="variety-select-wrap">
-                        <el-select
-                            v-model="formData.speciesId"
-                            placeholder="请选择"
-                            class="variety-select"
-                            @change="handleSpecieChange"
-                        >
-                            <el-option
-                                v-for="item in specieList"
-                                :key="item.id"
-                                :label="item.name"
-                                :value="item.id"
-                            />
-                        </el-select>
-                        <el-select
-                            v-model="formData.typeId"
-                            placeholder="请选择"
-                            class="variety-select"
-                            :disabled="!formData.speciesId"
-                            @change="handleFruitsChange"
-                        >
-                            <el-option
-                                v-for="item in fruitsList"
-                                :key="item.id"
-                                :label="item.name"
-                                :value="item.id"
-                            />
-                        </el-select>
-                    </div>
+                    <el-select
+                        v-model="formData.speciesId"
+                        placeholder="请选择"
+                        @change="handleSpecieChange"
+                    >
+                        <el-option
+                            v-for="item in specieList"
+                            :key="item.id"
+                            :label="item.name"
+                            :value="item.id"
+                        />
+                    </el-select>
+                </el-form-item>
+
+                <!-- 农场品类 -->
+                <el-form-item label="农场品类" prop="typeId">
+                    <el-select
+                        v-model="formData.typeId"
+                        multiple
+                        collapse-tags
+                        placeholder="请选择"
+                        :disabled="!formData.speciesId"
+                        @change="handleFruitsChange"
+                    >
+                        <el-option
+                            v-for="item in fruitsList"
+                            :key="item.id"
+                            :label="item.name"
+                            :value="item.id"
+                        />
+                    </el-select>
                 </el-form-item>
 
                 <!-- 农场亩数 -->
@@ -75,7 +77,7 @@
         <!-- 第二步:物候期起始时间填写 -->
         <template v-else>
             <!-- 提示文字 -->
-            <div class="popup-title">请填写当下物候期起始时间,农事预测更精准</div>
+            <div class="popup-title">为了精准匹配种植方案,完善物候信息</div>
 
             <!-- 物候期表单 -->
             <el-form ref="phenologyFormRef" :model="phenologyData" :rules="phenologyRules" class="farm-form">
@@ -206,7 +208,7 @@ const isNameEdited = ref(false);
 const formData = ref({
     address: "",
     speciesId: "",
-    typeId: "",
+    typeId: [],
     mu: "",
     name: "",
 });
@@ -220,9 +222,20 @@ const phenologyData = ref({});
 const specieList = ref([]);
 const fruitsList = ref([]);
 
+// 规范化多选品种 typeId(支持字符串/数组)
+const normalizeTypeId = (value) => {
+    if (Array.isArray(value)) return value;
+    if (value === null || value === undefined || value === "") return [];
+    return [value];
+};
+
 // 自定义验证器:验证农场品种
 const validateVariety = (rule, value, callback) => {
-    if (!formData.value.speciesId || !formData.value.typeId) {
+    const hasType =
+        Array.isArray(formData.value.typeId)
+            ? formData.value.typeId.length > 0
+            : !!formData.value.typeId;
+    if (!formData.value.speciesId || !hasType) {
         callback(new Error("请选择农场品种"));
     } else {
         callback();
@@ -233,6 +246,7 @@ const validateVariety = (rule, value, callback) => {
 const rules = {
     address: [{ required: true, message: "请输入农场位置", trigger: "blur" }],
     speciesId: [{ required: true, validator: validateVariety, trigger: "change" }],
+    typeId: [{ required: true, validator: validateVariety, trigger: "change" }],
     mu: [
         { required: true, message: "请输入农场亩数", trigger: "blur" },
         { pattern: /^\d+(\.\d+)?$/, message: "请输入有效的数字", trigger: "blur" },
@@ -252,6 +266,7 @@ watch(
     (newData) => {
         if (newData && Object.keys(newData).length > 0) {
             Object.assign(formData.value, newData);
+            formData.value.typeId = normalizeTypeId(formData.value.typeId);
         }
     },
     { immediate: true, deep: true }
@@ -272,11 +287,12 @@ watch(
             // 弹窗打开时,如果有初始数据则使用,否则使用默认值
             if (props.initialData && Object.keys(props.initialData).length > 0) {
                 Object.assign(formData.value, props.initialData);
+                formData.value.typeId = normalizeTypeId(formData.value.typeId);
             } else {
                 formData.value = {
                     address: "",
                     speciesId: "",
-                    typeId: "",
+                    typeId: [],
                     mu: "",
                     name: "",
                 };
@@ -292,7 +308,7 @@ watch(
 
 // 品种1变化时,重置品种2并触发验证
 const handleSpecieChange = (val) => {
-    formData.value.typeId = "";
+    formData.value.typeId = [];
     const specie = specieList.value.find(item => item.id === val);
     // 只有在用户没有手动修改名称时,才自动带出默认名称
     if (specie && !isNameEdited.value) {
@@ -310,6 +326,7 @@ const handleFruitsChange = () => {
     nextTick(() => {
         // 校验农场品种(包含大类和品种)
         formRef.value?.validateField("speciesId");
+        formRef.value?.validateField("typeId");
     });
 };
 
@@ -417,10 +434,6 @@ const getCurrentAndNextPhenology = async () => {
             display: flex;
             gap: 10px;
             width: 100%;
-
-            .variety-select {
-                flex: 1;
-            }
         }
 
         .unit {

+ 1 - 45
src/components/upload.vue

@@ -11,13 +11,6 @@
     </div>
     <div class="upload-content">
       <img v-if="exampleImg" @click="showExample" class="example" src="@/assets/img/home/example-4.png" alt="" />
-      <div class="example-list-wrapper" v-if="exampleList.length > 0">
-        <div class="example-list">
-          <div class="image-item-wrapper" v-for="example in exampleList" :key="example" @click="showExample(example)">
-            <img class="image-item" :src="example" alt="" />
-          </div>
-        </div>
-      </div>
       <uploader class="uploader" :class="{ 'uploader-list': exampleImg }" v-model="fileList"
         :multiple="props.maxCount > 1" :max-count="props.maxCount" :after-read="afterRead" @delete="deleteImg">
         <template v-if="exampleImg">
@@ -74,10 +67,6 @@ const props = defineProps({
     type: Number,
     default: 3
   },
-  exampleList: {
-    type: Array,
-    default: () => []
-  }
 })
 
 
@@ -164,40 +153,6 @@ onMounted(() => {
 
   .upload-content {
     display: flex;
-
-    .example-list-wrapper {
-      display: inline-block;
-    }
-
-    .example-list {
-      display: flex;
-      align-items: center;
-
-      .image-item-wrapper {
-        position: relative;
-        margin-right: 6px;
-
-        &::after {
-          content: '示例';
-          position: absolute;
-          top: 0;
-          left: 0;
-          background: rgba(0, 0, 0, 0.7);
-          color: #F2F4F5;
-          padding: 2px 6px;
-          border-radius: 8px 0 2px 0;
-          font-size: 10px;
-          z-index: 1;
-        }
-      }
-
-      .image-item {
-        width: calc((100vw - 68px) / 4);
-        height: calc((100vw - 68px) / 4);
-        border-radius: 8px;
-        object-fit: cover;
-      }
-    }
   }
 
   .tips {
@@ -249,6 +204,7 @@ onMounted(() => {
   }
 
   .uploader {
+
     .plus,
     .example {
       width: calc((100vw - 68px) / 4);

+ 252 - 30
src/views/old_mini/interactionList/index.vue

@@ -5,7 +5,10 @@
             :class="{ 'uploaded-item': item.isConfirmed != null }">
             <!-- 标题区域 -->
             <div class="item-header-wrapper" :class="{ 'has-status': item.isConfirmed != null }">
-                <div class="item-header">{{ item.interactionTypeName }}</div>
+                <div class="item-header">
+                    <div class="title">{{ item.interactionTypeName }}</div>
+                    <div class="status">紧急</div>
+                </div>
                 <div class="upload-status" v-show="item.isConfirmed != null">
                     <el-icon class="status-icon">
                         <SuccessFilled />
@@ -21,41 +24,58 @@
 
                 <!-- 图片展示 -->
                 <div class="uploaded-images" v-show="item.imagePaths.length > 0">
-                    <img class="uploaded-img" v-for="image in item.imagePaths" :key="image" :src="base_img_url2 + image" alt="" />
+                    <img class="uploaded-img" v-for="image in item.imagePaths" :key="image" :src="base_img_url2 + image"
+                        alt="" />
                 </div>
             </div>
 
             <!-- 未上传状态内容 -->
-            <div v-show="item.isConfirmed == null">
-                <!-- 说明文字 -->
-                <div class="item-desc">{{ item.question }}</div>
-
-                <upload :maxCount="8" @handleUpload="handleUploadSuccess" :exampleList="item.exampleImagesJson">
-                </upload>
-
-                <div class="question-wrapper">
+            <div class="uploaded-content" v-show="item.isConfirmed == null">
+                <div class="content-wrapper">
+                    <div class="item-desc">{{ item.question }}</div>
+                    <div class="example-wrapper">
+                        <div class="example-header">
+                            <div>示例照片</div>
+                            <div class="more">查看更多</div>
+                        </div>
+                        <div class="example-list" v-if="item.exampleImagesJson.length > 0">
+                            <div class="image-item-wrapper" v-for="example in item.exampleImagesJson" :key="example"
+                                @click="showExample(example)">
+                                <img class="image-item" :src="example" alt="" />
+                            </div>
+                        </div>
+                    </div>
+                </div>
+                <div class="upload-btn">
+                    <el-icon>
+                        <Plus />
+                    </el-icon>
+                    <span>点击上传照片</span>
+                </div>
+                <!-- <div class="question-wrapper">
                     <div class="question-text">
                         <span class="text-title">{{ item.phenologyName }}占比</span>
                         <el-input v-model="item.replyText" type="number" style="width: 80px" />
                         <span class="text-unit">%</span>
                     </div>
                     <div class="draw-region-btn" @click="handleDrawRegion(item)">勾画发生区域</div>
-                </div>
+                </div> -->
                 <!-- 输入框 -->
                 <div class="input-wrapper">
-                    <el-input v-model="item.remark" placeholder="添加备注:" clearable />
+                    <el-input v-model="item.remark" placeholder="请输入您咨询的问题" clearable />
                 </div>
 
                 <!-- 按钮区域 -->
                 <div class="button-group">
                     <div class="btn-not-reached" @click="handleNotReached(item)">{{ item.cancelButtonName }}</div>
-                    <div class="btn-confirm" @click="handleConfirm(item)">确认上传</div>
+                    <div class="btn-confirm" @click="handleConfirm(item)">确认提交</div>
                 </div>
             </div>
 
             <!-- 比例信息(已上传状态显示) -->
             <div class="proportion-info" v-show="item.isConfirmed != null">
-                <span class="proportion-text" v-if="item.replyText">当前果园{{ item.phenologyName }}占比: {{ item.replyText }}%</span>
+                <span class="proportion-text" v-if="item.replyText">当前果园{{ item.phenologyName }}占比: {{ item.replyText
+                }}%</span>
                 <span class="proportion-text" v-else>暂无数据</span>
                 <div class="toggle-btn" @click="toggleExpand(item)">
                     <span>{{ item.expanded ? "收起" : "展开" }}</span>
@@ -71,10 +91,38 @@
     <!-- 农场信息完善弹窗 -->
     <farm-info-popup :expertMiniUserId="query.expertMiniUserId" v-model:show="showFarmInfoPopup"
         @confirm="handleFarmInfoConfirm" />
+
+    <!-- 示例照片 -->
+    <popup v-model:show="showExamplePopup" class="example-popup" :overlay-style="{ backdropFilter: 'blur(4px)' }">
+        <div class="example-content">
+            <img class="example-img" :src="exampleImgData" alt="" />
+            <div class="example-tips">
+                拍摄要求:请采集代表农场作物物候期的照片,请采集代表农场作物物候期的照片。
+            </div>
+        </div>
+    </popup>
+    <!-- 照片上传进度 -->
+    <popup v-model:show="showUploadProgressPopup" round class="upload-progress-popup">
+        <div class="upload-progress-title">
+            <span>照片上传进度</span>
+            <el-progress class="upload-progress" :percentage="50" stroke-width="10" :format="format"/>
+        </div>
+        <div class="upload-wrap">
+            <upload :maxCount="10" @handleUpload="handleUploadSuccess">
+            </upload>
+        </div>
+        <el-input v-model="uploadProgressText" placeholder="请输入您咨询的问题" clearable />
+        <div class="region-tips">勾画新发生区域,精准匹配专属农事方案</div>
+        <div class="region-map">
+            <div class="region-map-text" @click="handleDrawRegion">点击勾画新发生区域</div>
+        </div>
+        <div class="confirm-btn">确认上传</div>
+    </popup>
 </template>
 <script setup>
 import { ref, onMounted } from "vue";
 import { ElMessage } from "element-plus";
+import { Popup } from "vant";
 import customHeader from "@/components/customHeader.vue";
 import upload from "@/components/upload.vue";
 import FarmInfoPopup from "@/components/popup/farmInfoPopup.vue";
@@ -86,6 +134,19 @@ const router = useRouter();
 const listData = ref([]);
 const query = ref(useRoute().query);
 
+//照片上传进度
+const showUploadProgressPopup = ref(false);
+const uploadProgressText = ref('');
+const format = (percentage) => (percentage === 100 ? '上传完成' : `5/10`)
+
+//示例照片
+const showExamplePopup = ref(false);
+const exampleImgData = ref(null);
+const showExample = (example) => {
+    exampleImgData.value = example;
+    showExamplePopup.value = true;
+};
+
 // 加载数据
 const loadData = async () => {
     loading.value = true;
@@ -236,19 +297,31 @@ const getFarmList = async () => {
         border-radius: 6px;
         padding: 10px;
         border: 1px solid #ffffff;
-        &.uploaded-item{
+
+        &.uploaded-item {
             border: 1px solid #2199f8;
         }
 
         .item-header-wrapper {
             .item-header {
-                font-size: 16px;
-                color: #6f6f6f;
-                font-family: "PangMenZhengDao";
-                width: fit-content;
-                background: rgba(143, 143, 143, 0.1);
-                padding: 5px 10px;
-                border-radius: 2px;
+                display: flex;
+                align-items: center;
+                justify-content: space-between;
+
+                div {
+                    font-size: 16px;
+                    color: #6f6f6f;
+                    font-family: "PangMenZhengDao";
+                    width: fit-content;
+                    background: rgba(143, 143, 143, 0.1);
+                    padding: 5px 10px;
+                    border-radius: 2px;
+                }
+
+                .status {
+                    color: #FF953D;
+                    background: rgba(255, 149, 61, 0.1);
+                }
             }
 
             .upload-status {
@@ -302,9 +375,74 @@ const getFarmList = async () => {
             }
         }
 
-        .item-desc {
-            padding: 12px 0;
-            color: #969696;
+        .uploaded-content {
+            .content-wrapper {
+                border: 0.5px solid rgba(0, 0, 0, 0.1);
+                border-radius: 4px;
+                padding: 10px;
+                margin-top: 12px;
+
+                .item-desc {
+                    color: #3C3C3C;
+                    margin-bottom: 10px;
+                }
+
+                .example-wrapper {
+                    .example-header {
+                        display: flex;
+                        align-items: center;
+                        justify-content: space-between;
+                        margin-bottom: 8px;
+                        color: #BEB9B9;
+
+                        .more {
+                            font-size: 12px;
+                        }
+                    }
+
+                    .example-list {
+                        display: flex;
+                        align-items: center;
+
+                        .image-item-wrapper {
+                            position: relative;
+                            margin-right: 6px;
+
+                            &::after {
+                                content: '示例';
+                                position: absolute;
+                                top: 0;
+                                left: 0;
+                                background: rgba(0, 0, 0, 0.7);
+                                color: #F2F4F5;
+                                padding: 2px 6px;
+                                border-radius: 8px 0 2px 0;
+                                font-size: 10px;
+                                z-index: 1;
+                            }
+                        }
+
+                        .image-item {
+                            width: calc((100vw - 68px) / 4);
+                            height: calc((100vw - 68px) / 4);
+                            border-radius: 8px;
+                            object-fit: cover;
+                        }
+                    }
+                }
+            }
+
+            .upload-btn {
+                display: flex;
+                align-items: center;
+                justify-content: center;
+                gap: 4px;
+                color: #0B84E4;
+                padding: 6px;
+                border: 0.5px solid rgba(33, 153, 248, 0.5);
+                border-radius: 4px;
+                margin-top: 12px;
+            }
         }
 
         .input-wrapper {
@@ -326,15 +464,21 @@ const getFarmList = async () => {
             .btn-not-reached {
                 max-width: fit-content;
                 background: #fff;
-                color: #2199f8;
-                border: 1px solid rgba(33, 153, 248, 0.2);
+                color: #585858;
+                border: 1px solid rgba(88, 88, 88, 0.2);
             }
 
             .btn-confirm {
-                background: #2199f8;
-                color: #ffffff;
-                border: 1px solid #2199f8;
+                background: #F1F1F1;
+                color: #999999;
+                border: 1px solid #F1F1F1;
             }
+
+            // .btn-confirm {
+            //     background: #2199f8;
+            //     color: #ffffff;
+            //     border: 1px solid #2199f8;
+            // }
         }
 
         .proportion-info {
@@ -409,4 +553,82 @@ const getFarmList = async () => {
         }
     }
 }
+
+.example-popup {
+    width: 100%;
+    border-radius: 0;
+    background: none;
+    max-width: 100%;
+
+    .example-content {
+        text-align: center;
+
+        .example-img {
+            width: 100%;
+        }
+    }
+
+    .example-tips {
+        margin: 16px 12px 6px 12px;
+        background: #3d3d3d;
+        padding: 8px 10px;
+        border-radius: 4px;
+        backdrop-filter: blur(4px);
+        color: #fff;
+        font-size: 14px;
+        line-height: 21px;
+        text-align: left;
+    }
+}
+
+.upload-progress-popup {
+    width: 100%;
+    padding: 20px 16px;
+    .upload-progress-title{
+        font-size: 16px;
+        color: #121212;
+        margin-bottom: 12px;
+        display: flex;
+        align-items: center;
+        justify-content: space-between;
+        .upload-progress{
+            width: 55%;
+        }
+    }
+    .upload-wrap{
+        margin-bottom: 12px;
+    }
+    .region-tips{
+        color: #2199F8;
+        padding: 5px;
+        background: rgba(33, 153, 248, 0.1);
+        border-radius: 5px;
+        margin: 16px 0 12px 0;
+        text-align: center;
+    }
+    .region-map{
+        width: 100%;
+        height: 168px;
+        background: red;
+        border-radius: 5px;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        .region-map-text{
+            color: #FFFFFF;
+            padding: 5px 20px;
+            border-radius: 5px;
+            background: rgba(0, 0, 0, 0.6);
+        }
+    }
+    .confirm-btn{
+        background: #2199f8;
+        color: #ffffff;
+        border-radius: 4px;
+        padding: 8px;
+        text-align: center;
+        font-size: 16px;
+        margin-top: 16px;
+    }
+}
 </style>