瀏覽代碼

Merge branch 'feature_20241120_图像识别' into release

# Conflicts:
#	src/components/uploadImgVStore.vue
#	src/components/uploadVNormal.vue
#	src/store/getters.js
#	src/store/index.js
#	src/utils/request.js
#	src/views/deviceWithin/addStoreVisit.vue
#	src/views/storeManagement/storeAdd.vue
#	src/views/week/index.vue
zhujindu 9 月之前
父節點
當前提交
0f33195e2d

+ 2 - 2
package.json

@@ -36,8 +36,8 @@
     "eslint": "^6.7.2",
     "eslint-plugin-prettier": "^3.1.1",
     "eslint-plugin-vue": "^6.2.2",
-    "sass": "^1.23.7",
-    "sass-loader": "^8.0.0",
+    "sass": "^1.32.13",
+    "sass-loader": "^10.4.1",
     "vue-template-compiler": "^2.6.11"
   },
   "eslintConfig": {

+ 28 - 0
src/api/AIImage.js

@@ -0,0 +1,28 @@
+import request from '@/utils/request';
+
+// 获取拜访照片AI识别异常,待审批列表。 失败异常照片需要部主管审批
+export function getPhotoApproveList(query) {
+  return request({
+    url: 'mobile/visit/getPhotoApproveList',
+    method: 'get',
+    params: query,
+  });
+}
+
+// 获取拜访照片AI识别异常详情
+export function getPhotoApproveDetail(query) {
+  return request({
+    url: 'mobile/visit/getPhotoApproveDetail',
+    method: 'get',
+    params: query,
+  });
+}
+
+//部主管反馈图片识别异常
+export function savePhotoApprove(data) {
+  return request({
+    url: 'mobile/visit/savePhotoApprove',
+    method: 'post',
+    data,
+  });
+}

+ 14 - 3
src/api/index.js

@@ -678,11 +678,12 @@ export function getChainsByDeptCode(query) {
 }
 // 照片上传
 //门店
-export function uploadImagev(data) {
+export function uploadImagev(data, signal) {
   return request({
     url: '/mobile/store/uploadImage',
     method: 'post',
     data: data,
+    signal: signal,
   });
 }
 //任务
@@ -694,11 +695,12 @@ export function addPhotov(data) {
   });
 }
 // 拜访
-export function addstorePhoto(data) {
+export function addstorePhoto(data, signal) {
   return request({
     url: '/mobile/storeGroup/addPhoto',
     method: 'post',
     data: data,
+    signal: signal,
   });
 }
 export function buryingPoint(data) {
@@ -905,7 +907,7 @@ export function selectVisitsRealTime(query) {
   });
 }
 
-// 审批历史
+// 审批历史-审批历史
 export function storeApprovaHistory(query) {
   return request({
     url: '/mobile/store/storeApprovaHistory',
@@ -959,3 +961,12 @@ export function getDictOption(query, dictTypr) {
     params: query,
   });
 }
+
+// 仍要上传图片入库
+export function addPhotoToDB(data) {
+  return request({
+    url: 'mobile/storeGroup/addPhotoToDB',
+    method: 'post',
+    data: data,
+  });
+}

二進制
src/assets/imageEmpty.png


+ 8 - 0
src/assets/styles/variable.scss

@@ -0,0 +1,8 @@
+
+
+@use "sass:math";
+$vw_base: 750; //设计稿宽度
+@function vw($px) {
+	// @return ($px / $vw_base) * 100vw;
+	@return (math.div( $px , $vw_base ))*100vw;
+}

+ 46 - 31
src/components/deleteUploadImg1.vue

@@ -1,27 +1,37 @@
 <template>
-  <div >
-  <van-row gutter="10" >
-    <van-col span="6" v-for="(urls, index) in imgs" :key="index">
-      <div class="imgview">
-        <van-icon name="close" size="16" v-on:click="deleteImg(index,urls.id)"/>
-        <img v-if="urls.type=='2'" :src="urls.fileUrl" width="100px" height="100px" @click="previewsImg(index)"/>
-        <img v-else :src="urls.fileUrl" width="100px" height="100px" @click="previewsImg(index)"/>
-      </div>
-    </van-col>
-  </van-row>
-<!--  <div style="padding: 10px 16px 0;">-->
-<!--    <div class="img-box" v-for="(urls, index) in imgs" :key="index">-->
-<!--      <van-icon name="clear" v-on:click="deleteImg(index,urls.id)"/>-->
-<!--      <img v-if="urls.type=='2'" :src="urls.fileUrl" width="100px" height="100px" @click="previewsImg(index)"/>-->
-<!--      <img v-else :src="urls.fileUrl" width="100px" height="100px" @click="previewsImg(index)"/>-->
-<!--    </div>-->
-<!--  </div>-->
+  <div>
+    <van-row gutter="10">
+      <van-col span="6" v-for="(urls, index) in imgs" :key="index">
+        <div class="imgview">
+          <van-icon name="close" size="16" v-on:click="deleteImg(index, urls.id)" />
+          <img
+            v-if="urls.type == '2'"
+            :src="urls.fileUrl"
+            width="100px"
+            height="100px"
+            @click="previewsImg(index)" />
+          <img
+            v-else
+            :src="urls.fileUrl"
+            width="100px"
+            height="100px"
+            @click="previewsImg(index)" />
+        </div>
+      </van-col>
+    </van-row>
+    <!--  <div style="padding: 10px 16px 0;">-->
+    <!--    <div class="img-box" v-for="(urls, index) in imgs" :key="index">-->
+    <!--      <van-icon name="clear" v-on:click="deleteImg(index,urls.id)"/>-->
+    <!--      <img v-if="urls.type=='2'" :src="urls.fileUrl" width="100px" height="100px" @click="previewsImg(index)"/>-->
+    <!--      <img v-else :src="urls.fileUrl" width="100px" height="100px" @click="previewsImg(index)"/>-->
+    <!--    </div>-->
+    <!--  </div>-->
   </div>
 </template>
 
 <script>
-import {ImagePreview} from "vant";
-import {removePhoto} from "@/api/index";
+import { ImagePreview } from 'vant';
+import { removePhoto } from '@/api/index';
 
 export default {
   name: 'deleteUploadImg',
@@ -29,41 +39,46 @@ export default {
     imgs: {
       type: Array,
       default() {
-        return []
-      }
+        return [];
+      },
+    },
+    photoIdentifyType: {
+      // 图匠识别目的(1:店招内容识别,2:门店代码识别,3:调色机识别,4:更换店招)
+      type: String,
+      default: '',
     },
   },
   data() {
     return {
-      url: process.env.VUE_APP_Target1 + process.env.VUE_APP_BASE_API
-    }
+      url: process.env.VUE_APP_Target1 + process.env.VUE_APP_BASE_API,
+    };
   },
   methods: {
     deleteImg(index, collectionItemId) {
-      removePhoto({fileId: collectionItemId}).then(res => {
+      removePhoto({ fileId: collectionItemId }).then((res) => {
         if (res.code == 200) {
-          this.$toast("删除成功!")
+          this.$toast('删除成功!');
           this.imgs.splice(index, 1);
         } else {
-          this.$toast("删除失败!")
+          this.$toast('删除失败!');
         }
-      })
+      });
     },
     previewsImg(index) {
-      var arrimg = []
+      var arrimg = [];
       for (var imgi = 0; imgi < this.imgs.length; imgi++) {
-        arrimg.push(this.imgs[imgi].fileUrl)
+        arrimg.push(this.imgs[imgi].fileUrl);
       }
       ImagePreview({
         images: arrimg,
         startPosition: index,
         onClose() {
           // do something
-        }
+        },
       });
     },
   },
-}
+};
 </script>
 
 <style lang="scss" scoped>

+ 439 - 0
src/components/imageAIVerifyErr copy.vue

@@ -0,0 +1,439 @@
+<template>
+  <div class="imageAIVerifyErr">
+    <el-dialog
+      title="图像识别结果"
+      :visible.sync="vanPopup"
+      width="80%"
+      :append-to-body="true"
+      :close-on-click-modal="false"
+      @close="close"
+      custom-class="AIVerifyErrdialog">
+      <div class="AIVerifyErrMask" v-if="npkpiData">
+        <!-- shopSignChange 与历史照片是否一致(是否要更换照片) 0一致(要更换),1不一致(不要更换) -->
+        <template v-if="shopSignChange == 0">
+          <div class="errorImg">
+            <img v-if="url" :src="url" height="300px" @click="previewsImg(url)" />
+            <img v-else :src="imageEmpty" width="100%" height="300px" />
+          </div>
+          <div class="AIVerify">
+            <span>{{ contentMessage }}</span>
+          </div>
+          <template v-if="shotsNum >= maxNum">
+            <div class="feedbackMessage">
+              <div class="label">若图像识别不正确,可在此反馈:</div>
+              <div class="value">
+                <van-field
+                  v-model="feedbackMessage"
+                  rows="1"
+                  autosize
+                  type="textarea"
+                  placeholder="请输入反馈意见" />
+              </div>
+            </div>
+          </template>
+          <div class="uploadBtnAIVerify">
+            <div class="confirmUploadAIVerify" @click="uploadImg(false)">重新拍照</div>
+            <div v-if="shotsNum >= maxNum" class="stillUploadAIVerify" @click="confirmUpload">
+              仍要上传
+            </div>
+          </div>
+          <template v-if="shotsNum >= maxNum">
+            <div class="tipsAIVerify" v-if="npkpiData.recognizeType == 1">
+              <van-icon name="question-o" />上传后作为本店标准店招,未来每次拜访时校验。
+            </div>
+          </template>
+        </template>
+        <template v-if="shopSignChange == 1">
+          <div class="historyImageAIVerify">
+            <!-- 有门店身份证时 只显示门店身份证和本地拜访照 -->
+            <template v-if="storeIDCardUrl">
+              <div class="storeIDCardUrl imageItemAIVerify">
+                <img
+                  :src="storeIDCardUrl"
+                  width="100px"
+                  height="100px"
+                  @click="previewsImg(storeIDCardUrl)" />
+                <span>门店标准店招</span>
+              </div>
+            </template>
+            <template v-else>
+              <div class="initImage imageItemAIVerify">
+                <img
+                  v-if="createStoreUrl"
+                  :src="createStoreUrl"
+                  width="100px"
+                  height="100px"
+                  @click="previewsImg(createStoreUrl)" />
+                <img v-else :src="imageEmpty" width="100px" height="100px" />
+                <span>建店时门店照</span>
+              </div>
+              <div class="newestImage imageItemAIVerify">
+                <img
+                  v-if="lastVisitUrl"
+                  :src="lastVisitUrl"
+                  width="100px"
+                  height="100px"
+                  @click="previewsImg(lastVisitUrl)" />
+                <img v-else :src="imageEmpty" width="100px" height="100px" />
+                <span>上次拜访店招</span>
+              </div>
+            </template>
+            <div class="presentImage imageItemAIVerify">
+              <img v-if="url" :src="url" width="100px" height="100px" @click="previewsImg(url)" />
+              <img v-else :src="imageEmpty" width="100px" height="100px" />
+              <span>本次拜访店招</span>
+            </div>
+          </div>
+          <div class="AIVerify">
+            <span>{{ contentMessage }}</span>
+          </div>
+          <div class="feedbackMessage">
+            <div class="label">若图像识别不正确,可在此反馈:</div>
+            <div class="value">
+              <van-field
+                v-model="feedbackMessage"
+                rows="2"
+                autosize
+                type="textarea"
+                placeholder="请输入反馈意见" />
+            </div>
+          </div>
+          <div class="uploadBtnAIVerify">
+            <div class="confirmUploadAIVerify" @click="uploadImg(true)">重新拍照</div>
+            <div class="changeImageAIVerify" @click="confirmUpDataImage()">更新门店照</div>
+          </div>
+          <div class="tipsRemarkAIVerify">
+            <div>若历史照片拍摄不规范,请选择<span style="color: #81b337">更新门店照</span></div>
+            <div>本次拜访店招会作为本店标准店招,未来每次拜访时校验</div>
+          </div>
+        </template>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { ImagePreview } from 'vant';
+import store from '@/store';
+import { mapState } from 'vuex';
+import imageEmpty from '@/assets/imageEmpty.png';
+export default {
+  props: {
+    imageAIVerifyFlag: {
+      type: Boolean,
+      default: false,
+    },
+    imageAIVerifyData: {
+      type: [Array, Object],
+    },
+    source: {
+      // 新建店还是门店拜访 visit/newCreated
+      type: String,
+    },
+  },
+  computed: {
+    ...mapState({
+      shotsNum: (state) => state.otheStore.shotsNum,
+    }),
+  },
+  watch: {
+    imageAIVerifyFlag: {
+      handler(val) {
+        console.log('imageAIVerifyFlag=' + val);
+        if (val) this.initData();
+      },
+      immediate: true,
+    },
+  },
+  data() {
+    return {
+      imageEmpty: imageEmpty,
+      contentMessage: '', //提示内容
+      vanPopup: true,
+      shopSignChange: 0,
+      npkpiData: null,
+      feedbackMessage: '', //反馈图像识别不正确原因
+      url: '', //	本次图片路径
+      createStoreUrl: '', //	建店时店招
+      lastVisitUrl: '', //	上次拜访时店招
+      storeIDCardUrl: '', //	门店身份证
+      maxNum: 2,
+    };
+  },
+  methods: {
+    initData() {
+      // 图匠识别目的(1:店招内容识别,2:门店代码识别,3:调色机识别,4:更换店招)
+      // shopSignChange  是否更换店招(0:未更换,1:更换) 1不一致,0一致
+      // checkInfo 图片检查结果
+      // cheatState 是否作弊(0:未作弊,1:作弊)
+      // cheatType	作弊类型
+      // qualifiedState 是否合格(0:不合格,1:合格)
+      // unqualifiedReason	不合格原因
+      this.feedbackMessage = '';
+      this.shopSignChange = 0;
+      this.npkpiData =
+        this.source == 'visit'
+          ? this.imageAIVerifyData.npkpiData
+          : this.imageAIVerifyData[0].npkpiData;
+      let imageAIVerifyData =
+        this.source == 'visit' ? this.imageAIVerifyData : this.imageAIVerifyData[0];
+      this.shopSignMatchList = this.npkpiData.shopSignMatchList;
+      this.url = imageAIVerifyData.url || ''; //	图片路径
+      this.createStoreUrl = imageAIVerifyData.createStoreUrl || ''; //	建店时店招
+      this.lastVisitUrl = imageAIVerifyData.lastVisitUrl || ''; //	上次拜访时店招
+      this.storeIDCardUrl = imageAIVerifyData.storeIDCardUrl || ''; //	门店身份证
+      // 先判断照片作弊情况,然后是否合格,然后是否和历史照片一致
+      // 作弊和不合格记录识别次数,超过两次弹框提醒
+      if (this.npkpiData.checkInfo) {
+        // 作弊
+        if (this.npkpiData.checkInfo.cheatState == 1) {
+          // 增加识别次数
+          store.dispatch('setShotsNum', this.shotsNum + 1);
+          // 作弊原因
+          this.contentMessage = this.contentMessage + this.npkpiData.checkInfo.cheatType;
+          return;
+        }
+        // 不合格
+        if (this.npkpiData.checkInfo.qualifiedState == 0) {
+          // 增加识别次数
+          store.dispatch('setShotsNum', this.shotsNum + 1);
+          // 不合格原因
+          this.contentMessage = this.contentMessage + this.npkpiData.checkInfo.unqualifiedReason;
+          return;
+        }
+        // recognizeType 识别目的(1:店招内容识别,2:门店代码识别,3:调色机识别)
+        if (this.npkpiData.recognizeType == 1) {
+          this.comparisonImage();
+        } else {
+          this.confirmUpload();
+        }
+      }
+    },
+    // 照片和历史照片是否一致
+    comparisonImage() {
+      this.shopSignChange = this.npkpiData.shopSignChange;
+      if (this.npkpiData.shopSignChange == 1) {
+        this.contentMessage = '与历史照片不一致,请确认店招是否更换?';
+        return false;
+      } else {
+        this.confirmUpDataImage();
+      }
+    },
+    // 重新拍照
+    uploadImg(flag) {
+      // flag: true,识别与历史照片不一致状态下,点击重新拍照,照片识别次数需要加1
+      if (flag) {
+        // 增加识别次数
+        store.dispatch('setShotsNum', this.shotsNum + 1);
+      }
+      this.$emit('close');
+      this.$emit('uploadImgFun');
+    },
+    // 照片是否入库,1.照片识别三次不通过仍要上传,2.照片识别通过
+    // isUpdate:是否更新店招照片,只有门店店招需要更新
+    confirmUpload() {
+      // 拜访店招 不合格或作弊三次先提示是否仍要上传,确认后在判断是否与历史照片一致
+      if (this.npkpiData.recognizeType == 1 && this.shotsNum >= this.maxNum) {
+        this.comparisonImage();
+      } else {
+        this.$emit('close');
+        this.$emit('confirmUpload', {
+          data: this.imageAIVerifyData,
+          feedbackMessage: this.feedbackMessage,
+        });
+      }
+    },
+    confirmUpDataImage() {
+      this.$emit('close');
+      this.$emit('confirmUpload', {
+        data: this.imageAIVerifyData,
+        isUpdate: 'true',
+        feedbackMessage: this.feedbackMessage,
+      });
+    },
+    close() {
+      this.$emit('close');
+    },
+    openTips() {
+      this.$dialog
+        .confirm({
+          title: '提示',
+          message: '不规范的照片上传后会更换本店标准店招,未来每次拜访时校验。',
+          showCancelButton: false,
+          className: 'openTips',
+          overlayClass: 'openTipsMask',
+        })
+        .then(() => {});
+    },
+    previewsImg(url) {
+      ImagePreview({
+        images: [url],
+        className: 'AIImageItem',
+        getContainer: 'el-dialog__wrapper',
+      });
+    },
+  },
+};
+</script>
+<style lang="scss">
+.el-dialog__wrapper {
+  z-index: 3333 !important;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: rgba(0, 0, 0, 0.5) !important;
+  .el-dialog__body {
+    padding: 6px !important;
+  }
+  .el-dialog__header {
+    text-align: center;
+  }
+  .AIVerifyErrdialog {
+    width: 95% !important;
+    margin-top: 1vh !important;
+    border-radius: 8px !important;
+    font-size: 32px !important;
+  }
+  .AIVerifyErrMask {
+    width: 100%;
+    padding: 8px;
+    overflow: hidden;
+    /* min-height: 180px; */
+    .errorImg {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      margin-top: 45px;
+    }
+    .AIVerify {
+      font-size: 30px;
+      text-align: center;
+      width: 40%;
+      margin-top: 45px;
+      span {
+        color: #9e0202;
+      }
+    }
+  }
+  .van-popup {
+    width: 90%;
+    padding: 8px;
+    border-radius: 8px;
+    overflow: hidden;
+  }
+  .van-f-red-AIVerify {
+    color: red;
+    width: 8px;
+    display: inline-block;
+    line-height: 26px;
+  }
+  .photoAIVerify {
+    /*margin-top: 9px;*/
+    float: right;
+  }
+  .title {
+    font-size: 16px;
+    font-weight: 600;
+    text-align: center;
+    padding: 5px;
+  }
+  .contentAIVerify {
+    .uploadImgAIVerify {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 8px 0;
+      border-top: 1px solid #cfcfcf;
+      .labelAIVerify {
+        font-size: 14px;
+      }
+    }
+  }
+  .tipsAIVerify {
+    padding: 5px 0;
+    font-size: 14px;
+    color: red;
+  }
+  .uploadBtnAIVerify {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    padding: 8px 0;
+    margin-top: 45px;
+    div {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: 14px;
+      color: #fff;
+      border-radius: 10px;
+      margin: 0 6px;
+      /* flex: 1; */
+    }
+    .confirmUploadAIVerify {
+      /* background-color: #0057ba;
+      padding: 8px 0; */
+      width: 298px;
+      height: 68px;
+      background-image: linear-gradient(180deg, #ffa1a1 0%, #f6695f 35%, #ed301d 100%),
+        linear-gradient(#aed0f9, #aed0f9);
+      background-blend-mode: normal, normal;
+      box-shadow: 0px 3px 6px 0px rgba(183, 30, 14, 0.35);
+      /* border-radius: 10px; */
+    }
+    .changeImageAIVerify {
+      background-color: #81b337;
+      padding: 8px 0;
+    }
+    .stillUploadAIVerify {
+      border: 1px solid #0057ba;
+      color: #0057ba;
+      padding: 8px 0;
+    }
+  }
+  .historyImageAIVerify {
+    display: flex;
+    justify-content: space-around;
+    padding: 5px 0;
+    .imageItemAIVerify {
+      width: 30%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      span {
+        font-size: 12px;
+        padding-top: 3px;
+      }
+    }
+  }
+  .tipsRemarkAIVerify {
+    border-top: 1px solid #cfcfcf;
+    padding: 3px 0;
+    div {
+      font-size: 12px;
+      padding: 3px 0;
+    }
+  }
+  .feedbackMessage {
+    border-top: 1px solid #cfcfcf;
+    padding: 5px 0;
+    /* width: 100%; */
+    .van-field__body {
+      border: 1px solid #ccc;
+      padding-left: 10px;
+    }
+  }
+}
+.openTipsMask,
+.openTips {
+  z-index: 3334 !important;
+}
+.van-overlay {
+  /* z-index: 3334 !important; */
+}
+.van-image-preview {
+  z-index: 3335 !important;
+  background: rgba(0, 0, 0, 0.8) !important;
+}
+</style>

+ 470 - 0
src/components/imageAIVerifyErr.vue

@@ -0,0 +1,470 @@
+<template>
+  <div class="imageAIVerifyErr">
+    <el-dialog
+      title="图像识别结果"
+      :visible.sync="vanPopup"
+      width="80%"
+      :append-to-body="true"
+      :close-on-click-modal="false"
+      @close="close"
+      custom-class="AIVerifyErrdialog">
+      <div class="AIVerifyErrMask" v-if="npkpiData">
+        <!-- shopSignChange 与历史照片是否一致(是否要更换照片) 0一致(要更换),1不一致(不要更换) -->
+        <template v-if="shopSignChange == 0">
+          <div class="errorImg">
+            <img v-if="url" :src="url" fit="contain" @click="previewsImg(url)" />
+            <img v-else :src="imageEmpty" width="100%" height="300px" />
+          </div>
+          <div class="AIVerify">
+            <span>{{ contentMessage }}</span>
+          </div>
+          <template v-if="shotsNum >= maxNum">
+            <div class="feedbackMessage">
+              <div class="label">若图像识别不正确,可在此反馈:</div>
+              <div class="value">
+                <van-field
+                  v-model="feedbackMessage"
+                  rows="3"
+                  type="textarea"
+                  placeholder="请输原因" />
+              </div>
+            </div>
+          </template>
+          <div
+            class="uploadBtnAIVerify"
+            :style="{ 'justify-content': shotsNum >= maxNum ? 'space-between' : 'center' }">
+            <div class="confirmUploadAIVerify" @click="uploadImg(false)">重新拍照</div>
+            <div v-if="shotsNum >= maxNum" class="stillUploadAIVerify" @click="confirmUpload">
+              仍要上传
+            </div>
+          </div>
+          <template v-if="shotsNum >= maxNum">
+            <div class="tipsAIVerify" v-if="npkpiData.recognizeType == 1">
+              <van-icon name="warning-o" />上传后作为本店标准店招,未来每次拜访时校验。
+            </div>
+          </template>
+          <template v-else>
+            <!-- 补位 -->
+            <div class="coveringPosition"></div>
+          </template>
+        </template>
+        <template v-if="shopSignChange == 1">
+          <div class="historyImageAIVerify">
+            <!-- 有门店身份证时 只显示门店身份证和本地拜访照 -->
+            <template v-if="storeIDCardUrl">
+              <div class="storeIDCardUrl imageItemAIVerify">
+                <img :src="storeIDCardUrl" @click="previewsImg(storeIDCardUrl)" />
+                <span>门店标准店招</span>
+              </div>
+            </template>
+            <template v-else>
+              <div class="initImage imageItemAIVerify">
+                <img
+                  v-if="createStoreUrl"
+                  :src="createStoreUrl"
+                  @click="previewsImg(createStoreUrl)" />
+                <img v-else :src="imageEmpty" />
+                <span>建店时门店照</span>
+              </div>
+              <div class="newestImage imageItemAIVerify">
+                <img v-if="lastVisitUrl" :src="lastVisitUrl" @click="previewsImg(lastVisitUrl)" />
+                <img v-else :src="imageEmpty" />
+                <span>上次拜访店招</span>
+              </div>
+            </template>
+            <div class="presentImage imageItemAIVerify">
+              <img v-if="url" :src="url" @click="previewsImg(url)" />
+              <img v-else :src="imageEmpty" />
+              <span>本次拜访店招</span>
+            </div>
+          </div>
+          <div class="AIVerify">
+            <span>{{ contentMessage }}</span>
+          </div>
+          <div class="feedbackMessage">
+            <div class="label">若图像识别不正确,可在此反馈:</div>
+            <div class="value">
+              <van-field
+                v-model="feedbackMessage"
+                rows="2"
+                autosize
+                type="textarea"
+                placeholder="请输入反馈意见" />
+            </div>
+          </div>
+          <div class="uploadBtnAIVerify" style="justify-content: center">
+            <div class="confirmUploadAIVerify" @click="uploadImg(true)">重新拍照</div>
+            <div class="changeImageAIVerify" @click="confirmUpDataImage()">更新门店照</div>
+          </div>
+          <div class="tipsRemarkAIVerify">
+            <!-- <div>若历史照片拍摄不规范,请选择<span style="color: #81b337">更新门店照</span></div> -->
+            <div><van-icon name="warning-o" />若历史照片拍摄不规范,请选择更新门店照</div>
+            <div>本次拜访店招会作为本店标准店招,未来每次拜访时校验</div>
+          </div>
+        </template>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import { ImagePreview } from 'vant';
+import store from '@/store';
+import { mapState } from 'vuex';
+import imageEmpty from '@/assets/imageEmpty.png';
+export default {
+  props: {
+    imageAIVerifyFlag: {
+      type: Boolean,
+      default: false,
+    },
+    imageAIVerifyData: {
+      type: [Array, Object],
+    },
+    source: {
+      // 新建店还是门店拜访 visit/newCreated
+      type: String,
+    },
+  },
+  computed: {
+    ...mapState({
+      shotsNum: (state) => state.otheStore.shotsNum,
+    }),
+  },
+  watch: {
+    imageAIVerifyFlag: {
+      handler(val) {
+        console.log('imageAIVerifyFlag=' + val);
+        if (val) this.initData();
+      },
+      immediate: true,
+    },
+  },
+  data() {
+    return {
+      imageEmpty: imageEmpty,
+      contentMessage: '', //提示内容
+      vanPopup: true,
+      shopSignChange: 0,
+      npkpiData: null,
+      feedbackMessage: '', //反馈图像识别不正确原因
+      url: '', //	本次图片路径
+      createStoreUrl: '', //	建店时店招
+      lastVisitUrl: '', //	上次拜访时店招
+      storeIDCardUrl: '', //	门店身份证
+      maxNum: 2,
+    };
+  },
+  methods: {
+    initData() {
+      // 图匠识别目的(1:店招内容识别,2:门店代码识别,3:调色机识别,4:更换店招)
+      // shopSignChange  是否更换店招(0:未更换,1:更换) 1不一致,0一致
+      // checkInfo 图片检查结果
+      // cheatState 是否作弊(0:未作弊,1:作弊)
+      // cheatType	作弊类型
+      // qualifiedState 是否合格(0:不合格,1:合格)
+      // unqualifiedReason	不合格原因
+      this.feedbackMessage = '';
+      this.shopSignChange = 0;
+      this.npkpiData =
+        this.source == 'visit'
+          ? this.imageAIVerifyData.npkpiData
+          : this.imageAIVerifyData[0].npkpiData;
+      let imageAIVerifyData =
+        this.source == 'visit' ? this.imageAIVerifyData : this.imageAIVerifyData[0];
+      this.shopSignMatchList = this.npkpiData.shopSignMatchList;
+      this.url = imageAIVerifyData.url || ''; //	图片路径
+      this.createStoreUrl = imageAIVerifyData.createStoreUrl || ''; //	建店时店招
+      this.lastVisitUrl = imageAIVerifyData.lastVisitUrl || ''; //	上次拜访时店招
+      this.storeIDCardUrl = imageAIVerifyData.storeIDCardUrl || ''; //	门店身份证
+      // 先判断照片作弊情况,然后是否合格,然后是否和历史照片一致
+      // 作弊和不合格记录识别次数,超过两次弹框提醒
+      if (this.npkpiData.checkInfo) {
+        // 作弊
+        if (this.npkpiData.checkInfo.cheatState == 1) {
+          // 增加识别次数
+          store.dispatch('setShotsNum', this.shotsNum + 1);
+          // 作弊原因
+          this.contentMessage = this.contentMessage + this.npkpiData.checkInfo.cheatType;
+          return;
+        }
+        // 不合格
+        if (this.npkpiData.checkInfo.qualifiedState == 0) {
+          // 增加识别次数
+          store.dispatch('setShotsNum', this.shotsNum + 1);
+          // 不合格原因
+          this.contentMessage = this.contentMessage + this.npkpiData.checkInfo.unqualifiedReason;
+          return;
+        }
+        // recognizeType 识别目的(1:店招内容识别,2:门店代码识别,3:调色机识别)
+        if (this.npkpiData.recognizeType == 1) {
+          this.comparisonImage();
+        } else {
+          this.confirmUpload();
+        }
+      }
+    },
+    // 照片和历史照片是否一致
+    comparisonImage() {
+      this.shopSignChange = this.npkpiData.shopSignChange;
+      if (this.npkpiData.shopSignChange == 1) {
+        this.contentMessage = '与历史照片不一致,请确认店招是否更换?';
+        return false;
+      } else {
+        this.confirmUpDataImage();
+      }
+    },
+    // 重新拍照
+    uploadImg(flag) {
+      // flag: true,识别与历史照片不一致状态下,点击重新拍照,照片识别次数需要加1
+      if (flag) {
+        // 增加识别次数
+        store.dispatch('setShotsNum', this.shotsNum + 1);
+      }
+      this.$emit('close');
+      this.$emit('uploadImgFun');
+    },
+    // 照片是否入库,1.照片识别三次不通过仍要上传,2.照片识别通过
+    // isUpdate:是否更新店招照片,只有门店店招需要更新
+    confirmUpload() {
+      // 拜访店招 不合格或作弊三次先提示是否仍要上传,确认后在判断是否与历史照片一致
+      if (this.npkpiData.recognizeType == 1 && this.shotsNum >= this.maxNum) {
+        this.comparisonImage();
+      } else {
+        this.$emit('close');
+        this.$emit('confirmUpload', {
+          data: this.imageAIVerifyData,
+          feedbackMessage: this.feedbackMessage,
+        });
+      }
+    },
+    confirmUpDataImage() {
+      this.$emit('close');
+      this.$emit('confirmUpload', {
+        data: this.imageAIVerifyData,
+        isUpdate: 'true',
+        feedbackMessage: this.feedbackMessage,
+      });
+    },
+    close() {
+      this.$emit('close');
+    },
+    openTips() {
+      this.$dialog
+        .confirm({
+          title: '提示',
+          message: '不规范的照片上传后会更换本店标准店招,未来每次拜访时校验。',
+          showCancelButton: false,
+          className: 'openTips',
+          overlayClass: 'openTipsMask',
+        })
+        .then(() => {});
+    },
+    previewsImg(url) {
+      ImagePreview({
+        images: [url],
+        className: 'AIImageItem',
+        getContainer: 'el-dialog__wrapper',
+      });
+    },
+  },
+};
+</script>
+<style lang="scss">
+.el-dialog__wrapper {
+  z-index: 3333 !important;
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  background: rgba(0, 0, 0, 0.5) !important;
+  .el-dialog__body {
+    padding: 0px !important;
+  }
+  .el-dialog__header {
+    text-align: center;
+  }
+  .AIVerifyErrdialog {
+    width: vw(690) !important;
+    margin-top: 1vh !important;
+    border-radius: 8px !important;
+    font-size: vw(32) !important;
+    .el-dialog__headerbtn {
+      width: vw(44);
+      height: vw(44);
+      background-color: #e1e1e1;
+      border-radius: 50%;
+      margin-top: -3px;
+    }
+  }
+  .AIVerifyErrMask {
+    width: 100%;
+    padding: vw(30) vw(30) 0 vw(30);
+    overflow: hidden;
+    /* min-height: 180px; */
+    .errorImg {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      min-height: vw(303);
+      img {
+        width: vw(235);
+        height: vw(303);
+        border-radius: vw(10);
+      }
+    }
+    .AIVerify {
+      font-size: vw(32);
+      margin-top: vw(45);
+      display: flex;
+      justify-content: center;
+      span {
+        color: #9e0202;
+        display: block;
+        max-width: 70%;
+      }
+    }
+  }
+  .van-popup {
+    width: 90%;
+    padding: 8px;
+    border-radius: 8px;
+    overflow: hidden;
+  }
+  .van-f-red-AIVerify {
+    color: red;
+    width: 8px;
+    display: inline-block;
+    line-height: 26px;
+  }
+  .photoAIVerify {
+    /*margin-top: 9px;*/
+    float: right;
+  }
+  .title {
+    font-size: 16px;
+    font-weight: 600;
+    text-align: center;
+    padding: 5px;
+  }
+  .contentAIVerify {
+    .uploadImgAIVerify {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      padding: 8px 0;
+      border-top: 1px solid #cfcfcf;
+      .labelAIVerify {
+        font-size: 14px;
+      }
+    }
+  }
+  .tipsAIVerify {
+    padding: vw(30) 0 vw(40) 0;
+    font-size: vw(22);
+    color: #999999;
+  }
+  .coveringPosition {
+    height: vw(60);
+  }
+  .uploadBtnAIVerify {
+    display: flex;
+    align-items: center;
+    margin-top: vw(30);
+    div {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      font-size: vw(28);
+      color: #fff;
+      border-radius: 10px;
+      /* margin: 0 6px; */
+      width: vw(298);
+      height: vw(68);
+      background-blend-mode: normal, normal;
+    }
+    .confirmUploadAIVerify {
+      background-image: linear-gradient(180deg, #ffa1a1 0%, #f6695f 35%, #ed301d 100%),
+        linear-gradient(#aed0f9, #aed0f9);
+      box-shadow: 0px 3px 6px 0px rgba(183, 30, 14, 0.35);
+      margin-right: 6px;
+    }
+    .changeImageAIVerify {
+      background-image: linear-gradient(180deg, #3b89e6 0%, #1e6acb 35%, #014baf 100%),
+        linear-gradient(#0356b9, #0356b9);
+      box-shadow: 0px 3px 6px 0px rgba(6, 60, 131, 0.35);
+      margin-left: 6px;
+    }
+    .stillUploadAIVerify {
+      background-image: linear-gradient(180deg, #3b89e6 0%, #1e6acb 35%, #014baf 100%),
+        linear-gradient(#0356b9, #0356b9);
+      box-shadow: 0px 3px 6px 0px rgba(6, 60, 131, 0.35);
+      margin-left: 6px;
+    }
+  }
+  .historyImageAIVerify {
+    display: flex;
+    justify-content: space-around;
+    padding: 5px 0;
+    .imageItemAIVerify {
+      width: 30%;
+      display: flex;
+      flex-direction: column;
+      align-items: center;
+      justify-content: center;
+      span {
+        font-size: 12px;
+        padding-top: 3px;
+      }
+      img {
+        width: vw(196);
+        height: vw(228);
+        border-radius: vw(10);
+      }
+    }
+  }
+  .tipsRemarkAIVerify {
+    padding: vw(28) 0;
+    div {
+      font-size: vw(22);
+      color: #999999;
+      position: relative;
+      padding-left: 17px;
+    }
+    .van-icon {
+      position: absolute;
+      left: 0;
+    }
+  }
+  .feedbackMessage {
+    margin-top: vw(45);
+    .label {
+      font-size: vw(26);
+      line-height: vw(50);
+    }
+    .value {
+      .van-cell {
+        padding: 0;
+      }
+    }
+    .van-field__body {
+      border-radius: vw(10);
+      border: solid 1px #aaaaaa;
+      padding-left: vw(10);
+      background-color: #e7e7e7;
+      .van-field__control {
+      }
+    }
+  }
+}
+.openTipsMask,
+.openTips {
+  z-index: 3334 !important;
+}
+.van-overlay {
+  /* z-index: 3334 !important; */
+}
+.van-image-preview {
+  z-index: 3335 !important;
+  background: rgba(0, 0, 0, 0.8) !important;
+}
+</style>

+ 165 - 18
src/components/uploadImgVStore.vue

@@ -17,6 +17,24 @@
       </p>
     </div>
     <p style="text-align: center">{{ imgText }}</p>
+    <div class="mask" v-if="progressFlag">
+      <el-progress
+        type="circle"
+        :percentage="percentage"
+        :show-text="true"
+        :format="format"></el-progress>
+      <div class="progressClose" @click="progressClose">取消</div>
+    </div>
+    <!-- 图像识别弹框 -->
+    <imageAIVerifyErr
+      v-if="imageAIVerifyFlag"
+      :imageAIVerifyFlag="imageAIVerifyFlag"
+      :imageAIVerifyData="imageAIVerifyData"
+      @confirmUpload="confirmUpload"
+      @uploadImgFun="uploadImg"
+      :source="'newCreated'"
+      @normalFlow="normalFlow"
+      @close="close"></imageAIVerifyErr>
   </div>
 </template>
 
@@ -24,9 +42,10 @@
 import { ImagePreview } from 'vant';
 import axios from 'axios';
 import { uploadImagev } from '@/api/index';
-
+import imageAIVerifyErr from './imageAIVerifyErr';
 export default {
   name: 'uploadImg',
+  components: { imageAIVerifyErr },
   props: {
     uploadid: {
       type: String,
@@ -65,6 +84,11 @@ export default {
       type: Boolean,
       default: false,
     },
+    photoIdentifyType: {
+      // 图匠识别目的(1:店招内容识别,2:门店代码识别,3:调色机识别,4:更换店招)
+      type: String,
+      default: '',
+    },
   },
   data() {
     return {
@@ -74,6 +98,12 @@ export default {
       num: 0,
       serverIdArr: [],
       imgUrlArr: [],
+      progressFlag: false,
+      percentage: 0,
+      timeFlag: null,
+      imageAIVerifyFlag: false,
+      imageAIVerifyData: null, //图匠校验返回的数据
+      controller: null, //取消请求状态
     };
   },
   watch: {
@@ -94,7 +124,10 @@ export default {
         this.$toast('请输入名称!');
         return;
       }
+      console.log(this.wx);
+      console.log(this.wx.ready);
       this.wx.ready(() => {
+        console.log(this.wx.chooseImage);
         this.wx.chooseImage({
           count: this.count,
           sizeType: ['compressed'], // 可以指定是原图还是压缩图,默认二者都有
@@ -135,6 +168,9 @@ export default {
       });
     },
     uploadImagev(meidaId) {
+      // 初始化重置 图匠校验
+      this.resetProgress();
+      this.close();
       var that = this;
       var form = {
         mediaId: meidaId,
@@ -142,26 +178,103 @@ export default {
         locationRemark: localStorage.getItem('locationRemark'),
         deptName: localStorage.getItem('deptName'),
       };
-      var loind1 = that.$toast.loading({
-        duration: 0,
-        message: '上传中...',
-        forbidClick: true,
+      this.controller = null;
+      // 需要图匠校验的添加参数和loading
+      if (this.photoIdentifyType) {
+        form.photoIdentifyType = this.photoIdentifyType;
+        this.progress();
+        this.controller = new AbortController(); //取消请求
+      } else {
+        this.toastLoading(0, '上传中...', true);
+      }
+      uploadImagev(form, this.controller ? this.controller.signal : null)
+        .then((res) => {
+          this.toastLoading().clear();
+          if (res.code == -1) {
+            // 图匠图片校验接口超时
+            this.requestTimeOut(res);
+          } else if (res.code == 200) {
+            // 图匠校验结果返回
+            if (this.photoIdentifyType) {
+              // 重置loaidng状态
+              this.resetProgress();
+              this.imageAIVerifyFlag = true;
+              console.log('res.data=' + JSON.stringify(res.data));
+              this.imageAIVerifyData = res.data;
+            } else {
+              // 正常流程
+              this.normalFlow(res);
+            }
+          } else {
+            this.resetProgress();
+            that.$toast('上传失败!');
+          }
+        })
+        .catch((error) => {
+          if (error.message === 'canceled') {
+            this.$toast('取消上传');
+            console.log('请求被取消:', error.message);
+          }
+          this.resetProgress();
+        });
+    },
+    progress() {
+      // 后端接口20000ms后失效,每1000m progress加10,到90停止;
+      this.progressFlag = true;
+      this.percentage = 10;
+      this.timeFlag = setInterval(() => {
+        this.percentage = this.percentage + 10;
+        if (this.percentage == 90) clearInterval(this.timeFlag);
+      }, 1000);
+    },
+    format(percentage) {
+      return `${percentage} %\n图像识别中`;
+    },
+    requestTimeOut(res) {
+      this.resetProgress();
+      this.close();
+      this.$dialog
+        .confirm({
+          title: '系统提示',
+          message: res.msg,
+          showCancelButton: false,
+        })
+        .then(() => {
+          this.normalFlow(res);
+        });
+    },
+    // 正常流程
+    normalFlow(res) {
+      let imgArr = [];
+      let businessId = [];
+      res.data.forEach((item) => {
+        imgArr.push(item.url);
+        if (item.businessId) businessId.push(item.businessId);
       });
-      uploadImagev(form).then((res) => {
-        if (res.code == 200) {
-          // that.imgArr = res.data.url;
-          let imgArr = [];
-          res.data.forEach((item) => {
-            imgArr.push(item.url);
-          });
-          loind1.clear();
-          that.$toast('上传成功!');
-          that.$emit('newimgarr', { fileUrl: imgArr.join(','), type: that.type });
-        } else {
-          that.$toast('上传失败!');
-        }
+      this.$toast('上传成功!');
+      this.$emit('newimgarr', {
+        fileUrl: imgArr.join(','),
+        type: this.type,
+        businessId: businessId.join(','),
       });
     },
+    // 重置loaidng状态
+    resetProgress() {
+      this.percentage = 100;
+      clearInterval(this.timeFlag);
+      this.progressFlag = false;
+      this.percentage = 0;
+    },
+    // 取消图片上传
+    progressClose() {
+      this.controller.abort();
+    },
+    confirmUpload(res) {
+      this.normalFlow(res);
+    },
+    close() {
+      this.imageAIVerifyFlag = false;
+    },
   },
 };
 </script>
@@ -219,4 +332,38 @@ export default {
 .coverImg .ico {
   top: 42%;
 }
+.mask {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(255, 255, 255, 0.8);
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  z-index: 99999999;
+  display: flex;
+  flex-direction: column;
+  .progressClose {
+    width: 70px;
+    text-align: center;
+    background: #67c23a;
+    color: #fff;
+    height: 30px;
+    line-height: 30px;
+    border-radius: 5px;
+    margin-top: 5px;
+    font-size: 12px;
+  }
+}
+</style>
+<style lang="scss">
+.mask {
+  .el-progress__text {
+    white-space: pre-wrap;
+  }
+}
 </style>

+ 193 - 15
src/components/uploadVNormal.vue

@@ -4,15 +4,35 @@
       <van-icon class="photo photos" name="photograph" size="22px" color="#969696" />
     </div>
     <div id="allmap"></div>
+    <div class="mask" v-if="progressFlag">
+      <el-progress
+        type="circle"
+        :percentage="percentage"
+        :show-text="true"
+        :width="110"
+        :format="format"></el-progress>
+      <div class="progressClose" @click="progressClose">取消</div>
+    </div>
+    <imageAIVerifyErr
+      v-if="imageAIVerifyFlag"
+      :imageAIVerifyFlag="imageAIVerifyFlag"
+      :imageAIVerifyData="imageAIVerifyData"
+      @confirmUpload="confirmUpload"
+      @uploadImgFun="uploadImg"
+      :source="'visit'"
+      @normalFlow="normalFlow"
+      @close="close"></imageAIVerifyErr>
   </div>
 </template>
 
 <script>
-import { addstorePhoto, addVisitsPosition } from '@/api/index';
+import { addstorePhoto, addVisitsPosition, addPhotoToDB } from '@/api/index';
+import imageAIVerifyErr from './imageAIVerifyErr';
 import axios from 'axios';
 
 export default {
   name: 'uploadImg',
+  components: { imageAIVerifyErr },
   props: {
     uploadid: {
       type: String,
@@ -27,7 +47,7 @@ export default {
       default: '',
     },
     secondCollectionId: {
-      type: String,
+      type: [String, Number],
       default: '',
     },
     firstCollectionId: {
@@ -85,11 +105,24 @@ export default {
       type: String,
       default: '0',
     },
+    photoIdentifyType: {
+      // 图匠识别目的(1:店招内容识别,2:门店代码识别,3:调色机识别,4:更换店招)
+      type: String,
+      default: '',
+    },
   },
   data() {
     return {
       shows: false,
       url: '',
+      progressFlag: false,
+      percentage: 0,
+      timeFlag: null,
+      imageAIVerifyFlag: false,
+      imageAIVerifyData: null, //图匠校验返回的数据
+      meidaId: '', //当前上传图片id
+      addressesRemark: '', //当前位置信息
+      controller: null, //取消请求状态
     };
   },
   methods: {
@@ -170,7 +203,7 @@ export default {
                 sizeType: ['original'], // 可以指定是原图还是压缩图,默认二者都有
                 sourceType: sourceType, // 可以指定来源是相册还是相机,默认二者都有
                 defaultCameraMode: 'normal', //表示进入拍照界面的默认模式,目前有normal与batch两种选择,normal表示普通单拍模式,batch表示连拍模式,不传该参数则为normal模式。从3.0.26版本开始支持front和batch_front两种值,其中front表示默认为前置摄像头单拍模式,batch_front表示默认为前置摄像头连拍模式。(注:用户进入拍照界面仍然可自由切换两种模式)
-                isSaveToAlbum: 0,
+                isSaveToAlbum: 0, //整型值,0表示拍照时不保存到系统相册,1表示自动保存,默认值是1
                 success: function (res) {
                   var localIds = '';
                   if (res.localIds != undefined) {
@@ -192,6 +225,9 @@ export default {
         });
     },
     uploadImagev(meidaId, addressesRemark) {
+      // 初始化重置 图匠校验
+      this.resetProgress();
+      this.close();
       var that = this;
       var parentCollectionId = null;
       if (that.parentCollectionId != null && that.parentCollectionId != 'null') {
@@ -213,6 +249,8 @@ export default {
       if (that.thirdCollectionId != null && that.thirdCollectionId != 'null') {
         thirdCollectionId = that.thirdCollectionId;
       }
+      this.meidaId = meidaId;
+      this.addressesRemark = addressesRemark;
       var form = {
         mediaId: meidaId,
         collectionItemId: that.collectionId,
@@ -231,20 +269,126 @@ export default {
         deviceCode: that.deviceCode, //设备编号
         putInCode: that.putInCode, //投放编号
       };
-      var loind1 = that.$toast.loading({
-        duration: 0,
-        message: '上传中...',
-        forbidClick: true,
+      this.controller = null;
+      // 需要图匠校验的添加参数和loading
+      if (this.photoIdentifyType) {
+        form.photoIdentifyType = this.photoIdentifyType;
+        this.progress();
+        this.controller = new AbortController(); //取消请求
+      } else {
+        this.toastLoading(0, '上传中...', true);
+      }
+      addstorePhoto(form, this.controller ? this.controller.signal : null)
+        .then((res) => {
+          this.toastLoading().clear();
+          if (res.code == -1) {
+            // 图匠图片校验接口超时
+            this.requestTimeOut(res);
+          } else if (res.code == 200) {
+            // 图匠校验结果返回
+            if (this.photoIdentifyType) {
+              // 重置loaidng状态
+              this.resetProgress();
+              this.imageAIVerifyFlag = true;
+              this.imageAIVerifyData = res.data;
+            } else {
+              // 正常流程
+              this.normalFlow(res);
+            }
+          } else {
+            this.resetProgress();
+            that.$toast('上传失败!');
+          }
+        })
+        .catch((error) => {
+          if (error.message === 'canceled') {
+            this.$toast('取消上传');
+            console.log('请求被取消:', error.message);
+          }
+          this.resetProgress();
+        });
+    },
+    // 正常流程
+    normalFlow(res) {
+      this.$toast('上传成功!');
+      this.$emit('newimgarr', {
+        fileUrl: res.data.url,
+        id: res.data.fileId,
+        type: 2,
+        photoIdentifyType: this.photoIdentifyType,
       });
-      addstorePhoto(form).then((res) => {
-        if (res.code == 200) {
-          loind1.clear();
-          that.$toast('上传成功!');
-          that.$emit('newimgarr', { fileUrl: res.data.url, id: res.data.fileId, type: 2 });
-        } else {
-          that.$toast('上传失败!');
+    },
+    progress() {
+      // 后端接口20000ms后失效,每1000m progress加10,到90停止;
+      this.progressFlag = true;
+      this.percentage = 10;
+      this.timeFlag = setInterval(() => {
+        this.percentage = this.percentage + 10;
+        if (this.percentage == 90) clearInterval(this.timeFlag);
+      }, 1000);
+    },
+    format(percentage) {
+      return `${percentage} %\n图像识别中`;
+    },
+    // 重置loaidng状态
+    resetProgress() {
+      this.percentage = 100;
+      clearInterval(this.timeFlag);
+      this.progressFlag = false;
+      this.percentage = 0;
+    },
+    // 照片是否入库,1.照片识别三次不通过仍要上传,2.照片识别通过
+    // isUpdate:是否更新店招照片,只有门店店招需要更新
+    confirmUpload(res) {
+      if (this.photoIdentifyType) {
+        var form = {
+          mediaId: this.meidaId, //	string	图片素材id
+          visitSource: '1', //	Long	拜访模式
+          storeGroupId: this.storeGroupId, //	string	门店任务组,多个用逗号隔开
+          visitsId: localStorage.getItem('visitId'), //	string	拜访id
+          taskId: this.taskId, //	string	任务id
+          objectType: this.objectType, //	string	照片类型,取任务上的照片类型,如果没有则取手动选择的照片类型
+          locationRemark: this.addressesRemark, //	String	当前地址信息
+          firstCollectionId: this.firstCollectionId, //	Long	第一级采集项id,取当前采集项的字段就行
+          secondCollectionId: this.secondCollectionId, //	Long	第二级采集项id,取当前采集项的字段就行
+          putInCode: this.putInCode, //	String	当前任务对应的投放编号
+          deviceCode: this.deviceCode, //	String	当前任务对应的设备编号
+          collectionItemId: this.collectionId,
+          url: res.data.url, //	String	当前拍摄图片的url
+          businessId: res.data.businessId, // 当前拍摄图片id
+        };
+        if (res.isUpdate) {
+          form.isUpdate = 'true';
+          form.feedbackMessage = res.feedbackMessage;
         }
-      });
+        addPhotoToDB(form).then((resData) => {
+          if (resData.code == 200) {
+            console.log(resData);
+            res.data.fileId = resData.data.fileId;
+            this.normalFlow(res);
+          }
+        });
+      }
+    },
+    close() {
+      this.imageAIVerifyFlag = false;
+    },
+    requestTimeOut(res) {
+      this.resetProgress();
+      this.close();
+      this.$dialog
+        .confirm({
+          title: '系统提示',
+          message: res.msg,
+          showCancelButton: false,
+        })
+        .then(() => {
+          this.confirmUpload(res);
+        });
+    },
+    // 取消图片上传
+    progressClose() {
+      this.controller.abort();
     },
   },
 };
@@ -277,6 +421,33 @@ export default {
       z-index: 89;
     }
   }
+  .mask {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    width: 100%;
+    height: 100%;
+    background: rgba(255, 255, 255, 1);
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    z-index: 99999999;
+    display: flex;
+    flex-direction: column;
+    .progressClose {
+      width: 70px;
+      text-align: center;
+      background: #67c23a;
+      color: #fff;
+      height: 30px;
+      line-height: 30px;
+      border-radius: 5px;
+      margin-top: 5px;
+      font-size: 12px;
+    }
+  }
 }
 #allmap {
   width: 10px;
@@ -285,3 +456,10 @@ export default {
   position: relative;
 }
 </style>
+<style lang="scss">
+.mask {
+  .el-progress__text {
+    white-space: pre-wrap;
+  }
+}
+</style>

+ 6 - 0
src/main.js

@@ -33,12 +33,18 @@ import ElPopover from 'element-ui/lib/popover';
 import 'element-ui/lib/theme-chalk/table-column.css';
 import 'element-ui/lib/theme-chalk/popover.css';
 import Vconsole from 'vconsole';
+import ElProgress from 'element-ui/lib/progress';
+import 'element-ui/lib/theme-chalk/progress.css';
+import ElDialog from 'element-ui/lib/dialog';
+import 'element-ui/lib/theme-chalk/dialog.css';
 // import wx from 'weixin-js-sdk';
 // jenkins 测试打包前先合并
 
 Vue.use(ElTable);
 Vue.use(ElTableColumn);
 Vue.use(ElPopover);
+Vue.use(ElProgress);
+Vue.use(ElDialog);
 Vue.config.productionTip = false;
 Vue.prototype.parseTime = parseTime;
 Vue.prototype.selectDictLabel = selectDictLabel;

+ 17 - 0
src/router/index.js

@@ -456,6 +456,23 @@ const router = new VueRouter({
         },
       ],
     },
+    {
+      path: '/AIImage',
+      component: layout,
+      redirect: '/AIImage/list',
+      children: [
+        {
+          path: '/AIImage/list',
+          name: 'AIImage',
+          component: () => import('@/views/AIImage/list.vue'),
+        },
+        {
+          path: '/AIImageDetail',
+          name: 'AIImageDetail',
+          component: () => import('@/views/AIImage/AIImageDetail.vue'),
+        },
+      ],
+    },
   ],
 });
 export default router;

+ 1 - 0
src/store/getters.js

@@ -3,5 +3,6 @@ const getters = {
   refreshClewPage: (state) => state.isRefreshPage.refreshClewPage,
   storeType: (state) => state.user.storeType,
   activaCreateTypeStore: (state) => state.user.activaCreateTypeStore,
+  shotsNum: (state) => state.otheStore.shotsNum,
 };
 export default getters;

+ 2 - 0
src/store/index.js

@@ -2,6 +2,7 @@ import Vue from 'vue';
 import Vuex from 'vuex';
 import user from './modules/user';
 import isRefreshPage from './modules/isRefreshPage';
+import otheStore from './modules/otheStore';
 import getters from './getters';
 
 Vue.use(Vuex);
@@ -10,6 +11,7 @@ const store = new Vuex.Store({
   modules: {
     user,
     isRefreshPage,
+    otheStore,
   },
   getters,
 });

+ 19 - 0
src/store/modules/otheStore.js

@@ -0,0 +1,19 @@
+const otheStore = {
+  state: {
+    shotsNum: 0,
+  },
+
+  mutations: {
+    SET_SHOTS_NUM: (state, value) => {
+      state.shotsNum = value;
+    },
+  },
+
+  actions: {
+    setShotsNum({ commit }, value) {
+      commit('SET_SHOTS_NUM', value);
+    },
+  },
+};
+
+export default otheStore;

+ 1 - 0
src/utils/TXApiFun.js

@@ -136,6 +136,7 @@ export function getTicketFun(jsApiList = ['getLocation'], configType = 'config')
       } else {
         console.log('获取签名失败');
         reject('获取签名失败');
+        alert('获取签名失败');
       }
     });
   });

+ 5 - 1
src/utils/request.js

@@ -10,7 +10,8 @@ import { toastLoading } from '@/utils/commonVant';
 axios.defaults.headers['Content-Type'] = 'application/json;charset=utf-8';
 const service = axios.create({
   baseURL: process.env.VUE_APP_BASE_API,
-  timeout: 0, //30000
+  // timeout: 30000,
+  timeout: 0,
   withCredentials: true,
 });
 // request拦截器
@@ -42,6 +43,9 @@ service.interceptors.response.use(
       //     duration:5000
       // });
       return res.data;
+    } else if (code == -1) {
+      // 图匠图片校验接口超时
+      return res.data;
     } else if (code !== 200) {
       Toast({
         message: msg,

+ 248 - 0
src/views/AIImage/AIImageDetail.vue

@@ -0,0 +1,248 @@
+<template>
+  <div class="AIImageDetail" v-if="data">
+    <van-nav-bar class="navBar" title="异常反馈" left-arrow @click-left="onClickLeft">
+    </van-nav-bar>
+    <div class="message">
+      <div class="storeTitle">
+        <div class="storeName">{{ data.storeName }}</div>
+        <div class="storeCode" style="margin-left: 5px; margin-top: 2px">
+          (<span style="color: #0057ba; vertical-align: -1px">{{ data.storeCode }}</span
+          >)
+        </div>
+      </div>
+      <div class="item">
+        <div class="label">门店类型:</div>
+        <div class="value">{{ data.storeCategoryName }}</div>
+      </div>
+      <div class="item">
+        <div class="label">地址:</div>
+        <div class="value">{{ data.addressLine }}</div>
+      </div>
+      <div class="item">
+        <div class="label">拜访人:</div>
+        <div class="value">{{ data.visitUserNickName }}</div>
+      </div>
+      <div class="item">
+        <div class="label">拜访时间:</div>
+        <div class="value">{{ data.createTime }}</div>
+      </div>
+      <div class="item">
+        <div class="label">拍摄类型:</div>
+        <div class="value">{{ data.identifyType | filterType }}</div>
+      </div>
+      <div class="item" style="color: red">
+        <div class="label">识别结果:</div>
+        <div class="value">{{ data.unqualifiedReason }}</div>
+      </div>
+      <div class="item">
+        <div class="label">业务员反馈AI识别不正确:{{ data.feedbackMessage || '未反馈' }}</div>
+      </div>
+      <div class="item" style="display: flex; justify-content: center">
+        <van-image
+          v-if="data.fileUrl"
+          height="400px"
+          :src="data.fileUrl"
+          @click="previewImgs(data.fileUrl)"
+          fit="contain">
+          <template v-slot:loading>
+            <van-loading type="spinner" size="40" />
+          </template>
+        </van-image>
+      </div>
+    </div>
+    <p class="titleText">请主管了解情况后回复原因及解决方案</p>
+    <div class="feedbackReason">
+      <div class="result">
+        <div class="title">
+          <span style="color: red">*</span>
+          <span>AI识别结果</span>
+        </div>
+        <van-radio-group v-model="AIResult" :disabled="approveState == '1'">
+          <van-radio name="1">正确</van-radio>
+          <van-radio name="0">不正确</van-radio>
+        </van-radio-group>
+      </div>
+      <div class="cause">
+        <div class="title">
+          <span style="color: red">*</span>
+          <span>{{ causeTitle }}</span>
+        </div>
+        <van-field
+          v-model="causeMessage"
+          :disabled="approveState == '1'"
+          rows="1"
+          autosize
+          type="textarea"
+          :placeholder="'请输入' + causeTitle" />
+      </div>
+    </div>
+    <div class="confirmBtn" v-if="approveState == '0'">
+      <van-button type="info" @click="confirm">提交</van-button>
+    </div>
+  </div>
+</template>
+<script>
+import { ImagePreview } from 'vant';
+import { getPhotoApproveDetail, savePhotoApprove } from '@/api/AIImage';
+export default {
+  name: 'AIImageDetail',
+  data() {
+    return {
+      data: null,
+      photoApproveId: null,
+      AIResult: '1',
+      causeTitle: '',
+      causeMessage: '',
+      approveState: '0',
+    };
+  },
+  watch: {
+    AIResult: {
+      handler(val) {
+        if (val == 1) {
+          this.causeTitle = '拜访照异常原因及解决方案';
+        } else {
+          this.causeTitle = '反馈AI识别结果有误';
+        }
+      },
+      immediate: true,
+    },
+  },
+  filters: {
+    filterType(val) {
+      if (val == 1) {
+        return '店招内容识别';
+      } else if (val == 2) {
+        return '门店代码识别';
+      } else if (val == 3) {
+        return '调色机识别';
+      } else if (val == 4) {
+        return '更换店招';
+      }
+    },
+  },
+  activated() {
+    this.photoApproveId = this.$route.query.photoApproveId;
+    this.getDeytail();
+  },
+  mounted() {},
+  methods: {
+    getDeytail() {
+      this.toastLoading(0, '加载中...', true);
+      getPhotoApproveDetail({ photoApproveId: this.photoApproveId }).then((res) => {
+        this.toastLoading().clear();
+        if (res.code == 200) {
+          this.data = res.data;
+          this.approveState = res.data.approveState; //反馈状态:0 未审批 1已审批
+          if (this.approveState == '0') {
+            let latestPhotoApprove = res.data.latestPhotoApprove;
+            if (latestPhotoApprove) {
+              this.AIResult = latestPhotoApprove.resultCorrect || '1';
+              this.causeMessage =
+                latestPhotoApprove.reasonsSolutions || latestPhotoApprove.feedbackError;
+              this.$dialog
+                .confirm({
+                  title: '系统提示',
+                  message:
+                    latestPhotoApprove.approveTime +
+                    '已反馈该店拜访照异常原因及解决方案,此次拜访照仍异常,请及时跟进',
+                  confirmButtonText: '确定',
+                  showCancelButton: false,
+                })
+                .then(() => {});
+            }
+          } else {
+            this.AIResult = res.data.resultCorrect || '1';
+            this.causeMessage = res.data.reasonsSolutions || res.data.feedbackError;
+          }
+        }
+      });
+    },
+    previewImgs(val) {
+      ImagePreview([val]);
+    },
+    confirm() {
+      if (!this.causeMessage) {
+        this.$toast('请输入' + this.causeTitle);
+        return;
+      }
+      savePhotoApprove({
+        photoApproveId: this.photoApproveId, //	long	主键
+        resultCorrect: this.AIResult, //	string	AI识别是否正确: 1 正确 0不正确
+        reasonsSolutions: this.AIResult == '1' ? this.causeMessage : '', //	string	原因及解决方案
+        feedbackError: this.AIResult == '0' ? this.causeMessage : '', //	string	反馈AI识别不正确
+      }).then((res) => {
+        if (res.code == 200) {
+          this.$toast.loading({
+            duration: 1000,
+            message: '已反馈给本部',
+            forbidClick: true,
+            onClose: () => {
+              this.onClickLeft();
+            },
+          });
+        } else {
+          this.$toast('提交失败');
+        }
+      });
+    },
+    onClickLeft() {
+      this.$router.replace({
+        path: '/AIImage',
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.AIImageDetail {
+  .message {
+    padding: 10px;
+    background: #fff;
+    .storeTitle {
+      display: flex;
+      font-size: 16px;
+      font-weight: bold;
+      padding: 5px 0;
+    }
+    .item {
+      display: flex;
+      font-size: 13px;
+      padding: 3px 0;
+      .label {
+        /* width: 80px; */
+        text-align: left;
+      }
+      .value {
+        flex: 1;
+      }
+    }
+  }
+  .titleText {
+    padding: 10px;
+    font-size: 16px;
+    font-weight: 600;
+    margin: 0;
+  }
+  .feedbackReason {
+    padding: 10px;
+    background: #fff;
+    font-size: 16px;
+    .title {
+      padding: 8px 0;
+    }
+    .van-radio {
+      padding: 5px 0 5px 10px;
+    }
+  }
+  .confirmBtn {
+    position: sticky;
+    bottom: 0;
+    padding-top: 20px;
+    button {
+      width: 100%;
+      background-color: #1a77cc;
+    }
+  }
+}
+</style>

+ 176 - 0
src/views/AIImage/list.vue

@@ -0,0 +1,176 @@
+<template>
+  <div class="AIImage">
+    <van-nav-bar class="navBar" title="图片识别异常" left-arrow @click-left="onClickLeft">
+    </van-nav-bar>
+    <van-tabs class="myTab" type="card" v-model="tabVal" color="#0057ba" @change="tabChange">
+      <van-tab title="未反馈" name="0" :disabled="disabled"></van-tab>
+      <van-tab title="已反馈" name="1" :disabled="disabled"></van-tab>
+    </van-tabs>
+    <div class="content">
+      <van-pull-refresh v-model="isLoading" @refresh="onRefresh">
+        <div
+          class="list_item"
+          v-for="(item, index) in listData"
+          :key="index"
+          @click="toDetail(item)">
+          <div class="storeTitle">
+            <div class="storeName">{{ item.storeName }}</div>
+            <div class="storeCode" style="margin-left: 5px; margin-top: 2px">
+              (<span style="color: #0057ba; vertical-align: -1px">{{ item.storeCode }}</span
+              >)
+            </div>
+          </div>
+          <div class="item">
+            <div class="label">门店类型:</div>
+            <div class="value">{{ item.storeCategoryName }}</div>
+          </div>
+          <div class="item">
+            <div class="label">地址:</div>
+            <div class="value">{{ item.addressLine }}</div>
+          </div>
+          <div class="item">
+            <div class="label">拜访人:</div>
+            <div class="value">{{ item.visitUserNickName }}</div>
+          </div>
+          <div class="item">
+            <div class="label">拜访时间:</div>
+            <div class="value">{{ item.createTime }}</div>
+          </div>
+          <div class="item">
+            <div class="label">拍摄类型:</div>
+            <!-- 1:店招内容识别,2:门店代码识别,3:调色机识别,4:更换店招 -->
+            <div class="value">{{ item.identifyType | filterType }}</div>
+          </div>
+          <div class="item">
+            <div class="label">识别结果:</div>
+            <div class="value">{{ item.unqualifiedReason }}</div>
+          </div>
+        </div>
+        <van-empty description="暂无数据" v-if="listData.length == 0" />
+      </van-pull-refresh>
+    </div>
+  </div>
+</template>
+<script>
+import { getPhotoApproveList } from '@/api/AIImage';
+export default {
+  name: 'list',
+  data() {
+    return {
+      tabVal: 0,
+      disabled: false,
+      listData: [],
+      isLoading: false,
+    };
+  },
+  filters: {
+    filterType(val) {
+      if (val == 1) {
+        return '店招内容识别';
+      } else if (val == 2) {
+        return '门店代码识别';
+      } else if (val == 3) {
+        return '调色机识别';
+      } else if (val == 4) {
+        return '更换店招';
+      }
+    },
+  },
+  watch: {
+    $route(to, from) {
+      if (from.path == '/My/index') {
+        this.tabVal = 0;
+      }
+    },
+  },
+  activated() {
+    this.getList();
+  },
+  created() {},
+  mounted() {},
+  methods: {
+    tabChange(tab) {
+      this.getList();
+    },
+    onRefresh() {
+      this.getList();
+    },
+    getList() {
+      this.toastLoading(0, '加载中...', true);
+      getPhotoApproveList({ approveState: this.tabVal }).then((res) => {
+        this.toastLoading().clear();
+        this.isLoading = false;
+        if (res.code == 200) {
+          this.listData = res.rows;
+        }
+      }, 1000);
+    },
+    // 详情
+    toDetail(item) {
+      this.$router.push({
+        path: '/AIImageDetail',
+        query: {
+          photoApproveId: item.photoApproveId,
+        },
+      });
+    },
+    onClickLeft() {
+      this.$router.replace({
+        path: '/My',
+      });
+    },
+  },
+};
+</script>
+<style lang="scss" scoped>
+.AIImage {
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  .content {
+    flex: 1;
+    padding: 10px;
+    .list_item {
+      padding: 10px 16px;
+      box-sizing: border-box;
+      background: #fff;
+      border-radius: 5px;
+      margin-bottom: 10px;
+      .storeTitle {
+        display: flex;
+        font-size: 16px;
+        font-weight: bold;
+        padding: 5px 0;
+      }
+      .item {
+        display: flex;
+        font-size: 13px;
+        padding: 3px 0;
+        color: #666;
+        .label {
+          /* width: 80px; */
+          text-align: left;
+        }
+        .value {
+          flex: 1;
+        }
+      }
+    }
+  }
+}
+</style>
+<style lang="scss">
+.AIImage {
+  .van-pull-refresh {
+    height: 100%;
+  }
+  .van-tabs__wrap,
+  .van-tabs__nav--card {
+    margin: 0 !important;
+    border-left: 0;
+    border-right: 0;
+    height: 40px;
+  }
+}
+</style>

+ 1 - 0
src/views/deviceOutside/suishenbangOutstoreVisit.vue

@@ -557,6 +557,7 @@ export default {
           locationCity: this.city,
           locationRemark: this.address,
           locationAccuracy: this.locationAccuracy,
+          photoIdentifyType: val.photoIdentifyType,
         },
       });
     },

File diff suppressed because it is too large
+ 385 - 214
src/views/deviceWithin/addStoreVisit.vue


+ 1 - 0
src/views/deviceWithin/storeVisit.vue

@@ -690,6 +690,7 @@ export default {
             types: this.$route.query.type,
             locationAccuracy: this.locationAccuracy,
             insert: this.insert,
+            photoIdentifyType: val.photoIdentifyType,
           },
         });
       }

+ 3 - 1
src/views/deviceWithin/taskTips.vue

@@ -1,6 +1,8 @@
 <template>
   <div class="tips">
-    <span class="examples" v-if="examplePhoto" @click="openExamplesImg(examplePhoto)">示例</span>
+    <span class="examples" v-if="examplePhoto" @click="openExamplesImg(examplePhoto)"
+      >拍摄要求</span
+    >
     <span class="phone" v-if="contactPhone">
       <van-icon name="phone" size="18px" /><a :href="'tel:' + contactPhone" class="call">{{
         contactPhone

+ 4 - 1
src/views/storeManagement/storeAdd.vue

@@ -324,12 +324,13 @@
                         :type="1"></upload-img>
                     </div>
                     <div v-else>
+                      <!-- :photoIdentifyType="fromValue.ifJzStoreType != 1 ? '1' : ''" -->
+                      <!-- 新建门店 关闭图像识别,如需开启将上面photoIdentifyType 复制到 upload-img  -->
                       <upload-img
                         :uploadid="uploadid2"
                         :imgArr="fromValue.img"
                         @newimgarr="newimgarr1"
                         :imgText="fromValue.ifJzStoreType != 1 ? '门店照' : '家装前台照片'"
-                        :photoIdentifyType="fromValue.ifJzStoreType != 1 ? '1' : ''"
                         :type="1"
                         ref="uploadImgVStore"></upload-img>
                     </div>
@@ -951,6 +952,8 @@ export default {
         this.getLocation();
       });
     });
+    // 拍照次数重置
+    store.dispatch('setShotsNum', 0);
   },
   methods: {
     addressFns(val) {

+ 5 - 2
src/views/storeManagement/success.vue

@@ -38,7 +38,10 @@
         ></van-col
       > -->
     </van-row>
-    <van-row gutter="20" style="display: flex; justify-content: center">
+    <van-row
+      gutter="20"
+      style="display: flex; justify-content: center"
+      v-if="fromValue && this.fromValue.storeId">
       <van-col>
         <van-button
           style="width: 120px"
@@ -64,7 +67,7 @@ export default {
   name: 'success',
   data() {
     return {
-      fromValue: {},
+      fromValue: null,
       cont: 0,
       timer: null,
       flag: true,

+ 20 - 0
src/views/week/index.vue

@@ -73,6 +73,24 @@
             <template #right-icon>
               <van-icon name="arrow" size="16" />
             </template>
+          </van-cell> </van-cell-group
+        ><!--拜访照片识别异常反馈 部主管及以上-->
+        <van-cell-group inset class="mtb10">
+          <van-cell title="拜访照片识别异常反馈" to="/AIImage">
+            <template #icon>
+              <van-icon :name="history" class="zicon" />
+            </template>
+            <template #title>
+              <span>拜访照片识别异常反馈</span>
+              <span
+                style="font-weight: 600; font-size: 16px; color: red; margin-left: 15px"
+                v-if="photoApprovalNum"
+                >({{ photoApprovalNum }})</span
+              >
+            </template>
+            <template #right-icon>
+              <van-icon name="arrow" size="16" />
+            </template>
           </van-cell>
         </van-cell-group>
         <!--      汇报类-->
@@ -230,6 +248,7 @@ export default {
       thisWeekRemarkNum: null,
       notAllocationNum: 0, //待分配客资数量
       isAssignFlag: false,
+      photoApprovalNum: 0,
     };
   },
   watch: {
@@ -313,6 +332,7 @@ export default {
         this.customerClueNum = res.data.customerClueNum;
         this.updataTime = res.data.reportTargetAll.updateTime;
         this.thisWeekRemarkNum = res.data.thisWeekRemarkNum; //查询本周点评的数量,null不需要展示
+        this.photoApprovalNum = res.data.photoApprovalNum; //首页照片异常待反馈数量
         if (res.data.deptLevel === 0) {
           this.showDaily = false;
           this.showWeekly = false;

+ 24 - 14
vue.config.js

@@ -1,15 +1,15 @@
-"use strict";
-const path = require("path");
+'use strict';
+const path = require('path');
 function resolve(dir) {
   return path.join(__dirname, dir);
 }
-const name = process.env.VUE_APP_TITLE || "门店拜访"; // 网页标题
+const name = process.env.VUE_APP_TITLE || '门店拜访'; // 网页标题
 const port = 8888; // 端口
 module.exports = {
   // publicPath: process.env.NODE_ENV === "production" ? "/mobile/" : "/",
-  publicPath: "/mobile/",
-  outputDir: "mobile",
-  assetsDir: "static",
+  publicPath: '/mobile/',
+  outputDir: 'mobile',
+  assetsDir: 'static',
   lintOnSave: false,
   productionSourceMap: false,
   devServer: {
@@ -20,7 +20,7 @@ module.exports = {
         target: process.env.VUE_APP_Target,
         changeOrigin: true,
         pathRewrite: {
-          ["^" + process.env.VUE_APP_BASE_API]: process.env.VUE_APP_BASE_API + "",
+          ['^' + process.env.VUE_APP_BASE_API]: process.env.VUE_APP_BASE_API + '',
         },
       },
     },
@@ -30,19 +30,29 @@ module.exports = {
     name: name,
     resolve: {
       alias: {
-        "@": resolve("src"),
+        '@': resolve('src'),
       },
     },
   },
   chainWebpack(config) {
-    config.plugins.delete("preload"); // TODO: need test
-    config.plugins.delete("prefetch"); // TODO: need test
-    config.when(process.env.NODE_ENV !== "development", (config) => {
-      config.optimization.runtimeChunk("single"),
+    config.plugins.delete('preload'); // TODO: need test
+    config.plugins.delete('prefetch'); // TODO: need test
+    config.when(process.env.NODE_ENV !== 'development', (config) => {
+      config.optimization.runtimeChunk('single'),
         {
-          from: path.resolve(__dirname, "./public/robots.txt"), //防爬虫文件
-          to: "./", //到根目录下
+          from: path.resolve(__dirname, './public/robots.txt'), //防爬虫文件
+          to: './', //到根目录下
         };
     });
   },
+  css: {
+    loaderOptions: {
+      sass: {
+        // 使用 Dart Sass 配置
+        implementation: require('sass'),
+        // 更新为 additionalData 语法
+        additionalData: `@use "@/assets/styles/variable.scss" as *;`,
+      },
+    },
+  },
 };