Переглянути джерело

Merge remote-tracking branch 'origin/master'

yjwang 2 тижнів тому
батько
коміт
23dc0c24ce

+ 1 - 1
.env.prod-jnmy

@@ -16,7 +16,7 @@ ENV = 'production'
 VUE_APP_BASE_API = '/prod-api'
 
 #默认 1、会员 2、企微
-VUE_APP_COURSE_DEFAULT = 1
+VUE_APP_COURSE_DEFAULT = 2
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 1 - 1
.env.prod-kyt

@@ -16,7 +16,7 @@ ENV = 'production'
 VUE_APP_BASE_API = '/prod-api'
 
 #默认 1、会员 2、企微
-VUE_APP_COURSE_DEFAULT = 1
+VUE_APP_COURSE_DEFAULT = 2
 
 # 路由懒加载
 VUE_CLI_BABEL_TRANSPILE_MODULES = true

+ 2 - 1
package.json

@@ -20,13 +20,14 @@
     "build:prod-yzt": "vue-cli-service build --mode prod-yzt",
     "build:prod-xfk": "vue-cli-service build --mode prod-xfk",
     "build:prod-myhk": "vue-cli-service build --mode prod-myhk",
+    "build:prod-fzbt": "vue-cli-service build --mode prod-fzbt",
     "build:prod-sft": "vue-cli-service build --mode prod-sft",
-    "build:prod-fcky": "vue-cli-service build --mode prod-fcky",
     "build:prod-zsjk": "vue-cli-service build --mode prod-zsjk",
     "build:prod-lmjy": "vue-cli-service build --mode prod-lmjy",
     "build:prod-bnkc": "vue-cli-service build --mode prod-bnkc",
     "build:prod-whhm": "vue-cli-service build --mode prod-whhm",
     "build:prod-drk": "vue-cli-service build --mode prod-drk",
+    "build:prod-zkzh": "vue-cli-service build --mode prod-zkzh",
     "build:prod-qdtst": "vue-cli-service build --mode prod-qdtst",
     "build:prod-bjczwh": "vue-cli-service build --mode prod-bjczwh",
     "build:prod-jkj": "vue-cli-service build --mode prod-jkj",

+ 28 - 0
src/api/qw/externalContact.js

@@ -269,3 +269,31 @@ export function getCustomerCourseSop(query) {
     params: query
   })
 }
+
+
+export function batchUpdateExternalContactNotes(data) {
+  return request({
+    url: '/qw/externalContact/batchUpdateExternalContactNotes',
+    method: 'post',
+    data: data
+  })
+}
+
+// 查询企业微信客户流失删除统计列表
+export function delLossStatistics(query) {
+  return request({
+    url: '/qw/externalContact/delLossStatistics',
+    method: 'get',
+    params: query
+  })
+}
+
+
+// 导出企业微信客户
+export function delLossStatisticsExport(query) {
+  return request({
+    url: '/qw/externalContact/delLossStatisticsExport',
+    method: 'get',
+    params: query
+  })
+}

BIN
src/assets/logo/hcl.png


+ 5 - 3
src/components/course/userCoursePeriod.vue

@@ -42,7 +42,7 @@
             <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
                 <template slot-scope="scope">
                     <el-button size="mini" type="text" icon="el-icon-setting"
-                        @click="handlePeriodSettings(scope.row)">选择视频</el-button>
+                        @click="handlePeriodSettings(scope.row)">选择课节</el-button>
                 </template>
             </el-table-column>
         </el-table>
@@ -394,7 +394,7 @@ export default {
                     batchSendCourse(this.imSendForm).then(res => {
                         if (res.errCode == 0) {
                             this.imCancel();
-                            this.$message.success('发送成功')
+                            this.$message.success(res.errMsg)
                         }
                     })
                 }
@@ -410,7 +410,8 @@ export default {
                 id: row.id,
                 periodId: row.periodId,
                 courseId: row.courseId,
-                videoId: row.videoId
+                videoId: row.videoId,
+                projectId: row.projectId,
             }
         },
         imCancel() {
@@ -430,6 +431,7 @@ export default {
                 title: null,
                 effectiveDuration: null,
                 id: null,
+                projectId: null,
             };
             this.resetForm("imSendForm")
         },

+ 132 - 2
src/views/company/companyUser/index.vue

@@ -110,6 +110,37 @@
           <el-table-column label="员工后台昵称" align="center" prop="nickName" :show-overflow-tooltip="true" 员工后台  width="100"/>
           <el-table-column label="部门" align="center" prop="deptName" :show-overflow-tooltip="true" />
           <el-table-column label="手机号码" align="center" prop="phonenumber" width="120" />
+          <el-table-column label="二维码" align="center" prop="qrCodeWeixin">
+            <template slot-scope="scope">
+              <!-- 显示已上传的二维码 -->
+              <el-image
+                v-if="scope.row.qrCodeWeixin"
+                style="width: 80px; height: 80px; margin-bottom: 5px; display: block; margin-left: auto; margin-right: auto;"
+                :src="scope.row.qrCodeWeixin"
+                :preview-src-list="[scope.row.qrCodeWeixin]"
+                fit="contain">
+                <div slot="error" class="image-slot">
+                  <i class="el-icon-picture-outline"></i>
+                </div>
+              </el-image>
+              <!-- 上传组件 -->
+              <el-upload
+                class="avatar-uploader"
+                action="#"
+                :show-file-list="false"
+                :http-request="(options) => handleCustomUpload(options, scope.row)"
+                :before-upload="(file) => beforeImageUpload(file, scope.row)"
+              >
+                <el-button size="small" type="primary" :loading="scope.row.uploading">
+                  {{ scope.row.qrCodeWeixin ? '更换图片' : '上传图片' }}
+                  <i class="el-icon-upload el-icon--right"></i>
+                </el-button>
+              </el-upload>
+              <div v-if="scope.row.uploadError" class="el-upload__tip" style="color: red;">
+                {{ scope.row.uploadError }}
+              </div>
+            </template>
+          </el-table-column>
           <el-table-column label="状态" align="center">
             <template slot-scope="scope">
               <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
@@ -347,7 +378,7 @@
 <!--            </el-form-item>-->
 <!--          </el-col>-->
 <!--        </el-row>-->
-        <el-row>
+<!--        <el-row>
           <el-col :span="24">
             <el-form-item label="看课域名">
               <el-input
@@ -360,7 +391,7 @@
               <el-button type="primary" style="margin-left: 20px" @click="generateDomain">生成域名</el-button>
             </el-form-item>
           </el-col>
-        </el-row>
+        </el-row>-->
         <el-row>
           <el-col :span="24">
             <el-form-item label="备注">
@@ -468,11 +499,13 @@ import { syncDept } from '@/api/qw/qwDept';
 import { getMyQwUserList,getMyQwCompanyList } from "@/api/qw/user";
 import  selectUser  from "@/views/company/components/selectQwUser.vue";
 import { getConfigByKey } from "@/api/company/companyConfig";
+import axios from "axios";
 export default {
   name: "User",
   components: { Treeselect ,selectUser},
   data() {
     return {
+      uploadUrl: process.env.VUE_APP_BASE_API+"/company/user/common/uploadOSS",
       // 遮罩层
       loading: false,
       qwUserList:[],
@@ -1108,7 +1141,104 @@ export default {
         }
       });
     },
+    /**
+     * 上传文件之前的钩子,参数为上传的文件,若返回 false 或者返回 Promise且被 reject,则停止上传。
+     * @param {File} file - 用户选择的文件对象
+     * @param {Object} row - 当前行的数据对象
+     */
+    beforeImageUpload(file, row) {
+      // 清除之前的错误信息
+      this.$set(row, 'uploadError', '');
+      const isJPG = file.type === 'image/jpeg';
+      const isPNG = file.type === 'image/png';
+      const isGIF = file.type === 'image/gif'; // 根据需要添加更多格式
+      const isValidFormat = isJPG || isPNG || isGIF;
+      const isLt2M = file.size / 1024 / 1024 < 2; // 限制图片大小为 2MB
+      if (!isValidFormat) {
+        const errorMsg = '上传二维码图片只能是 JPG/PNG/GIF 格式!';
+        this.$message.error(errorMsg);
+        this.$set(row, 'uploadError', errorMsg); // 在行内显示错误
+        return false;
+      }
+      if (!isLt2M) {
+        const errorMsg = '上传二维码图片大小不能超过 2MB!';
+        this.$message.error(errorMsg);
+        this.$set(row, 'uploadError', errorMsg); // 在行内显示错误
+        return false;
+      }
+      return true; // 校验通过,允许上传
+    },
+    /**
+     * 自定义上传方法
+     * @param {Object} options - Element UI upload 组件传递的参数,包含 file, onSuccess, onError, onProgress 等
+     * @param {Object} row - 当前行的数据对象
+     */
+    async handleCustomUpload(options, row) {
+
+      const file = options.file;
+      const formData = new FormData();
+      formData.append('file', file);
+
+      formData.append('userId',row.userId)
 
+      this.$set(row, 'uploading', true);
+      this.$set(row, 'uploadError', '');
+      try {
+        const response = await axios.post(this.uploadUrl, formData, {
+          headers: {
+            'Content-Type': 'multipart/form-data',
+          },
+          onUploadProgress: progressEvent => {
+            const percentCompleted = Math.round((progressEvent.loaded * 100) / progressEvent.total);
+            console.log(`上传进度: ${percentCompleted}%`);
+          }
+        });
+        if (response.data && (response.data.url || (response.data.data && response.data.data.url))) {
+          const imageUrl = response.data.url || response.data.data.url;
+          this.$set(row, 'qrCodeWeixin', imageUrl); // 更新行数据中的图片URL
+          this.$message.success('图片上传成功!');
+          options.onSuccess(response.data, file); // 通知el-upload上传成功 (虽然我们自定义了,但调用一下也无妨)
+        } else {
+          const errorMsg = response.data.message || '图片上传失败,服务器未返回有效URL';
+          this.$message.error(errorMsg);
+          this.$set(row, 'uploadError', errorMsg);
+          options.onError(new Error(errorMsg), file); // 通知el-upload上传失败
+        }
+      } catch (error) {
+        console.error('上传失败:', error);
+        let errorMsg = '图片上传失败';
+        if (error.response && error.response.data && error.response.data.message) {
+          errorMsg = error.response.data.message;
+        } else if (error.message) {
+          errorMsg = error.message;
+        }
+        this.$message.error(errorMsg);
+        this.$set(row, 'uploadError', errorMsg);
+        options.onError(error, file); // 通知el-upload上传失败
+      } finally {
+        this.$set(row, 'uploading', false); // 无论成功失败,结束上传状态
+      }
+    },
+    requestUpload() {
+    },
+    beforeUpload(){
+      console.log(file.type)
+      const isPic =
+        file.type === 'image/jpeg' ||
+        file.type === 'image/png' ||
+        file.type === 'image/gif' ||
+        file.type === 'image/jpg' ||
+        file.type === 'audio/mpeg'
+      const isLt2M = file.size / 1024 / 1024 < 2
+      if (!isPic) {
+        this.$message.error('上传图片只能是 JPG、JPEG、PNG、GIF 格式!')
+        return false
+      }
+      if (!isLt2M) {
+        this.$message.error('上传头像图片大小不能超过 2MB!')
+      }
+      return isPic && isLt2M
+    },
   },
 };
 </script>

+ 20 - 1
src/views/course/courseWatchLog/index.vue

@@ -122,6 +122,20 @@
           :key="qecCalendarKey"
         />
       </el-form-item>
+      <el-form-item label="是否为会员" prop="isVip">
+        <el-select
+          filterable
+          v-model="queryParams.isVip"
+          placeholder="请选择是否为会员"
+          clearable size="small">
+          <el-option
+            v-for="dict in isVipList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
 
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -565,6 +579,10 @@ export default {
         pageSize: 10,
       },
 
+      isVipList: [
+        { dictLabel: '是', dictValue: 1 },
+        { dictLabel: '否', dictValue: 0 }
+      ],
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -589,6 +607,7 @@ export default {
         scheduleStartTime: null,
         scheduleEndTime: null,
         sendType:process.env.VUE_APP_COURSE_DEFAULT,
+        isVip: null,
       },
       // 表单参数
       form: {},
@@ -603,7 +622,7 @@ export default {
     });
     this.getList();
     this.getDicts("sys_course_watch_log_type").then(response => {
-      this.logTypeOptions = response.data.filter(item => item.dictLabel !== "待看课");
+      this.logTypeOptions = response.data;
     });
   },
   methods: {

+ 20 - 1
src/views/course/courseWatchLog/watchLog.vue

@@ -143,6 +143,20 @@
           :key="qecCalendarKey"
         />
       </el-form-item>
+      <el-form-item label="是否为会员" prop="isVip">
+        <el-select
+          filterable
+          v-model="queryParams.isVip"
+          placeholder="请选择是否为会员"
+          clearable size="small">
+          <el-option
+            v-for="dict in isVipList"
+            :key="dict.dictValue"
+            :label="dict.dictLabel"
+            :value="dict.dictValue"
+          />
+        </el-select>
+      </el-form-item>
 
       <el-form-item>
         <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
@@ -586,6 +600,10 @@ export default {
         corpId:null,
       },
 
+      isVipList: [
+        { dictLabel: '是', dictValue: 1 },
+        { dictLabel: '否', dictValue: 0 }
+      ],
       // 查询参数
       queryParams: {
         pageNum: 1,
@@ -610,6 +628,7 @@ export default {
         scheduleStartTime: null,
         scheduleEndTime: null,
         sendType:process.env.VUE_APP_COURSE_DEFAULT,
+        isVip: null,
       },
       // 表单参数
       form: {},
@@ -623,7 +642,7 @@ export default {
     });
     this.getList();
     this.getDicts("sys_course_watch_log_type").then(response => {
-      this.logTypeOptions = response.data.filter(item => item.dictLabel !== "待看课");
+      this.logTypeOptions = response.data;
     });
 
     this.getDicts("sys_company_or").then(response => {

+ 0 - 8
src/views/member/list.vue

@@ -213,14 +213,6 @@
             @click="handleAudit(scope.row)"
             v-if="scope.row.isCurrentCompanyUser === 1 && scope.row.status === 0"
           >审核会员</el-button>
-          <el-button
-            size="mini"
-            type="text"
-            icon="el-icon-edit"
-            @click="handleUpdate(scope.row)"
-            v-hasPermi="['user:fsUser:edit']"
-          >修改</el-button>
-
           <el-button
             size="mini"
             type="text"

+ 0 - 1
src/views/member/mylist.vue

@@ -145,7 +145,6 @@
               size="mini"
               :disabled="multiple"
               @click="batchSend"
-              v-hasPermi="['course:userVideo:audit']"
               >批量IM发送</el-button>
             </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>

+ 24 - 9
src/views/qw/externalContact/deptIndex.vue

@@ -234,7 +234,17 @@
           v-hasPermi="['qw:externalContact:deptEdit']"
         >批量修改备注</el-button>
       </el-col>
-
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="handleBatchUpdateNotesFilter"
+          v-hasPermi="['qw:externalContact:edit']"
+        >批量修改备注(筛选条件)
+        </el-button>
+      </el-col>
       <!-- <el-col :span="1.5">
         <el-button
           type="warning"
@@ -285,11 +295,11 @@
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
 
-    <el-tabs type="card" v-model="isBindActiveName" @tab-click="handleClickX">
-      <el-tab-pane label="全部" name="all"></el-tab-pane>
+<!--    <el-tabs type="card" v-model="isBindActiveName" @tab-click="handleClickX">-->
+<!--      <el-tab-pane label="全部" name="all"></el-tab-pane>-->
       <!--      <el-tab-pane label="已绑定CRM" name="isBind"></el-tab-pane>-->
       <!--      <el-tab-pane label="未绑定CRM" name="noBind"></el-tab-pane>-->
-    </el-tabs>
+<!--    </el-tabs>-->
 
     <el-table v-loading="loading" :data="externalContactList" @selection-change="handleSelectionChange" border>
       <el-table-column type="selection" width="55" align="center" />
@@ -849,10 +859,12 @@ export default {
       exportLoading: false,
       tagOpen:false,
       notesOpen: {
-        type:1,
-        nameType:3,
-        open:false,
-        notes:null,
+        type: 1,
+        nameType: 3,
+        addType: 0,
+        filter: false,
+        open: false,
+        notes: null,
       },
       tagDelOpen:false,
       // 选中数组
@@ -1253,7 +1265,10 @@ export default {
       this.notesOpen.open=true;
 
     },
-
+    handleBatchUpdateNotesFilter() {
+      this.notesOpen.open = true;
+      this.notesOpen.filter = true;
+    },
     addUserTag(){
 
       if(this.ids==null||this.ids==""){

+ 134 - 1
src/views/qw/externalContact/index.vue

@@ -224,6 +224,28 @@
           v-hasPermi="['qw:externalContact:add']"
         >同步</el-button>
       </el-col> -->
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="handleBatchUpdateNotes"
+          v-hasPermi="['qw:externalContact:edit']"
+        >批量修改备注
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="handleBatchUpdateNotesFilter"
+          v-hasPermi="['qw:externalContact:edit']"
+        >批量修改备注(筛选条件)
+        </el-button>
+      </el-col>
       <el-col :span="1.5">
         <el-button
           type="success"
@@ -533,6 +555,53 @@
         <el-button @click="addTagCancel">取 消</el-button>
       </div>
     </el-dialog>
+    <el-dialog title="批量添加客户备注" :visible.sync="notesOpen.open" width="800px" append-to-body>
+      <el-card>
+        <el-row>
+          <el-col>
+            <el-radio-group v-model="notesOpen.nameType" style="margin-bottom: 2%">
+              <el-radio :label="1">
+                客户名称添加在【新备注】【前】
+              </el-radio>
+              <el-radio :label="2">
+                客户名称添加在【新备注】【后】
+              </el-radio>
+              <el-radio :label="3">
+                不添加客户名称
+              </el-radio>
+            </el-radio-group>
+          </el-col>
+          <el-col>
+            <el-radio-group v-model="notesOpen.type">
+              <el-radio
+                :label="1"
+              >添加【新备注】在最【前】面
+              </el-radio>
+              <el-radio
+                :label="2"
+              >添加【新备注】在最【后】面
+              </el-radio>
+              <el-radio
+                :label="3"
+              >替换所有备注
+              </el-radio>
+            </el-radio-group>
+          </el-col>
+          <el-col>
+            <el-input v-model="notesOpen.notes" placeholder="请输入客户备注(最多20个字,含已有的)" clearable size="small"
+                      maxlength="20" show-word-limit style="width: 500px;margin-top: 3%"/>
+            <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+              <i class="el-icon-info"></i>
+              由于企业微信官方限制,备注最多20个字,且自动会去除末尾超出的字
+            </div>
+          </el-col>
+        </el-row>
+      </el-card>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="notesSubmitForm()">确 定</el-button>
+        <el-button @click="notesCancel()">取消</el-button>
+      </div>
+    </el-dialog>
 
     <el-dialog title="批量移除标签" :visible.sync="tagDelOpen" width="800px" append-to-body>
       <div>搜索标签:
@@ -658,6 +727,7 @@ import {
   bindUserId,
   addTag,
   delTag,
+  batchUpdateExternalContactNotes,
   listExternalContact,
   getExternalContact,
   delExternalContact,
@@ -685,6 +755,14 @@ export default {
   components:{PaginationMore, mycustomer,customerDetails,SopDialog,selectUser,info},
   data() {
     return {
+      notesOpen: {
+        type: 1,
+        nameType: 3,
+        addType: 0,
+        filter: false,
+        open: false,
+        notes: null,
+      },
       user:{
         open:false,
         title:"修改客户"
@@ -1726,9 +1804,64 @@ export default {
           this.download(response.msg);
           this.exportLoading = false;
         }).catch(() => {});
-    }
+    },
+    handleBatchUpdateNotesFilter() {
+      this.notesOpen.open = true;
+      this.notesOpen.filter = true;
+    },
+    handleBatchUpdateNotes() {
+
+      if (this.ids == null || this.ids == "") {
+        return this.$message('请选择需要添加备注的客户');
+      }
+
+      this.notesOpen.open = true;
+      this.notesOpen.filter = false;
+
+    },
+    notesSubmitForm() {
+
+      if (this.notesOpen.notes == null || this.notesOpen.notes == "") {
+        return this.$message.error("请输入备注内容");
+      }
+
+      // let loadingRock = this.$loading({
+      //   lock: true,
+      //   text: '正在执行中请稍后~~请不要刷新页面!!',
+      //   spinner: 'el-icon-loading',
+      //   background: 'rgba(0, 0, 0, 0.7)'
+      // });
+
+      let obj = JSON.parse(JSON.stringify(this.queryParams))
+      console.log(obj);
+      if(obj.tagIds !== null && obj.tagIds !== undefined && obj.tagIds !== ''){
+        obj.tagIds = obj.tagIds.split(",");
+      }
+      batchUpdateExternalContactNotes({
+        addType: 0,
+        userIds: this.ids,
+        notes: this.notesOpen.notes,
+        type: this.notesOpen.type,
+        nameType: this.notesOpen.nameType,
+        filter: this.notesOpen.filter,
+        param: obj
+      }).then(res => {
+
+        this.resultMessage = res.msg;
+        this.$message.success("正在执行中...");
+        // this.resultDialogVisible = true; // 显示弹窗
+        // this.resultTitle = '批量修改备注结果';
+
+      }).finally(res => {
+        this.getList();
+        // loadingRock.close();
+        this.notesCancel();
+      })
+
+    },
   }
 };
+
 </script>
 <style scoped>
 /* CSS 样式 */

+ 133 - 2
src/views/qw/externalContact/myExternalContact.vue

@@ -178,6 +178,28 @@
     </el-form>
 
     <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="handleBatchUpdateNotes"
+          v-hasPermi="['qw:externalContact:edit']"
+        >批量修改备注
+        </el-button>
+      </el-col>
+      <el-col :span="1.5">
+        <el-button
+          type="success"
+          plain
+          icon="el-icon-edit"
+          size="mini"
+          @click="handleBatchUpdateNotesFilter"
+          v-hasPermi="['qw:externalContact:edit']"
+        >批量修改备注(筛选条件)
+        </el-button>
+      </el-col>
       <!-- <el-col :span="1.5">
         <el-button
           type="primary"
@@ -542,7 +564,53 @@
         <el-button @click="addTagCancel">取 消</el-button>
       </div>
     </el-dialog>
-
+    <el-dialog title="批量添加客户备注" :visible.sync="notesOpen.open" width="800px" append-to-body>
+      <el-card>
+        <el-row>
+          <el-col>
+            <el-radio-group v-model="notesOpen.nameType" style="margin-bottom: 2%">
+              <el-radio :label="1">
+                客户名称添加在【新备注】【前】
+              </el-radio>
+              <el-radio :label="2">
+                客户名称添加在【新备注】【后】
+              </el-radio>
+              <el-radio :label="3">
+                不添加客户名称
+              </el-radio>
+            </el-radio-group>
+          </el-col>
+          <el-col>
+            <el-radio-group v-model="notesOpen.type">
+              <el-radio
+                :label="1"
+              >添加【新备注】在最【前】面
+              </el-radio>
+              <el-radio
+                :label="2"
+              >添加【新备注】在最【后】面
+              </el-radio>
+              <el-radio
+                :label="3"
+              >替换所有备注
+              </el-radio>
+            </el-radio-group>
+          </el-col>
+          <el-col>
+            <el-input v-model="notesOpen.notes" placeholder="请输入客户备注(最多20个字,含已有的)" clearable size="small"
+                      maxlength="20" show-word-limit style="width: 500px;margin-top: 3%"/>
+            <div style="color: #999;font-size: 14px;display: flex;align-items: center;">
+              <i class="el-icon-info"></i>
+              由于企业微信官方限制,备注最多20个字,且自动会去除末尾超出的字
+            </div>
+          </el-col>
+        </el-row>
+      </el-card>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="notesSubmitForm()">确 定</el-button>
+        <el-button @click="notesCancel()">取消</el-button>
+      </div>
+    </el-dialog>
     <el-dialog title="批量移除标签" :visible.sync="tagDelOpen" width="800px" append-to-body>
       <div>搜索标签:
         <el-input v-model="tagChange.tagName" placeholder="请输入标签名称" clearable size="small" style="width: 200px;margin-right: 10px" />
@@ -666,6 +734,7 @@ import {
   myList,
   bindUserId,
   addTag,
+  batchUpdateExternalContactNotes,
   delTag,
   listExternalContact,
   getExternalContact,
@@ -696,6 +765,14 @@ export default {
   components:{PaginationMore, mycustomer,customerDetails,SopDialog,selectUser,info},
   data() {
     return {
+      notesOpen: {
+        type: 1,
+        nameType: 3,
+        addType: 0,
+        filter: false,
+        open: false,
+        notes: null,
+      },
       user:{
         open:false,
         title:"修改客户"
@@ -1735,7 +1812,61 @@ export default {
           this.download(response.msg);
           this.exportLoading = false;
         }).catch(() => {});
-    }
+    },
+    handleBatchUpdateNotesFilter() {
+      this.notesOpen.open = true;
+      this.notesOpen.filter = true;
+    },
+    handleBatchUpdateNotes() {
+
+      if (this.ids == null || this.ids == "") {
+        return this.$message('请选择需要添加备注的客户');
+      }
+
+      this.notesOpen.open = true;
+      this.notesOpen.filter = false;
+
+    },
+    notesSubmitForm() {
+
+      if (this.notesOpen.notes == null || this.notesOpen.notes == "") {
+        return this.$message.error("请输入备注内容");
+      }
+
+      // let loadingRock = this.$loading({
+      //   lock: true,
+      //   text: '正在执行中请稍后~~请不要刷新页面!!',
+      //   spinner: 'el-icon-loading',
+      //   background: 'rgba(0, 0, 0, 0.7)'
+      // });
+
+      let obj = JSON.parse(JSON.stringify(this.queryParams))
+      console.log(obj);
+      if(obj.tagIds !== null && obj.tagIds !== undefined && obj.tagIds !== ''){
+        obj.tagIds = obj.tagIds.split(",");
+      }
+      batchUpdateExternalContactNotes({
+        addType: 0,
+        userIds: this.ids,
+        notes: this.notesOpen.notes,
+        type: this.notesOpen.type,
+        nameType: this.notesOpen.nameType,
+        filter: this.notesOpen.filter,
+        param: obj
+      }).then(res => {
+
+        this.resultMessage = res.msg;
+        this.$message.success("正在执行中...");
+        // this.resultDialogVisible = true; // 显示弹窗
+        // this.resultTitle = '批量修改备注结果';
+
+      }).finally(res => {
+        this.getList();
+        // loadingRock.close();
+        this.notesCancel();
+      })
+
+    },
   }
 };
 </script>

+ 164 - 0
src/views/qw/qwUserDelLossStatistics/index.vue

@@ -0,0 +1,164 @@
+<template>
+  <div class="app-container">
+    <el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="120px">
+      <el-form-item label="企微员工账号" prop="userId">
+        <el-input
+          v-model="queryParams.userId"
+          placeholder="请输入企微员工账号"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+
+      <el-form-item label="员工名称" prop="userName">
+        <el-input
+          v-model="queryParams.userName"
+          placeholder="请输入企微员工名称"
+          clearable
+          size="small"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item label="时间">
+          <el-date-picker
+          v-model="dateRange"
+          type="daterange"
+          range-separator="至"
+          start-placeholder="开始日期"
+          end-placeholder="结束日期"
+          value-format="yyyy-MM-dd"
+          style="width: 240px"
+        />
+        </el-form-item>
+      <el-form-item>
+        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
+        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button
+          type="warning"
+          plain
+          icon="el-icon-download"
+          size="mini"
+          :loading="exportLoading"
+          @click="handleExport"
+          v-hasPermi="['qw:externalContact:export']"
+        >导出</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+    
+    
+    <el-table v-loading="loading" :data="externalContactList" @selection-change="handleSelectionChange" border>
+      <el-table-column label="企微员工账号" align="center" prop="userId" />
+      <el-table-column label="企微员工名称" align="center" prop="qwUserName"/>
+      <el-table-column label="删除数量" align="center" prop="delCount" />
+      <el-table-column label="流失数量" align="center" prop="lossCount" />
+    </el-table>
+    <pagination
+      v-show="total>0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+  </div>
+</template>
+
+<script>
+import { delLossStatisticsExport, delLossStatistics } from "@/api/qw/externalContact";
+export default {
+  name: "ExternalContact",
+  data() {
+    return {
+      dateRange: [],
+      // 遮罩层
+      loading: true,
+      // 导出遮罩层
+      exportLoading: false,
+      // 选中数组
+      ids: [],
+      // 非单个禁用
+      single: true,
+      // 非多个禁用
+      multiple: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 企业微信客户表格数据
+      externalContactList: [],
+      // 查询参数
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        userId: null,
+        qwUserName:null,
+        startTime: null,
+        endTime: null,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+      }
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询企业微信客户删除流失统计列表 */
+    getList() {
+      this.loading = true;
+      if (this.dateRange && this.dateRange.length === 2) {
+        this.queryParams.startTime = this.dateRange[0];
+        this.queryParams.endTime = this.dateRange[1];
+      } else {
+        this.queryParams.startTime = null;
+        this.queryParams.endTime = null;
+      }
+      delLossStatistics(this.queryParams).then(response => {
+        this.externalContactList = response.rows;
+        this.total = response.total;
+        this.loading = false;
+      });
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.dateRange = [];  
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    // 多选框选中数据
+    handleSelectionChange(selection) {
+      this.ids = selection.map(item => item.id)
+      this.single = selection.length!==1
+      this.multiple = !selection.length
+    },
+    /** 导出按钮操作 */
+    handleExport() {
+      const queryParams = this.queryParams;
+      this.$confirm('是否确认导出所有企业微信客户数据项?', "警告", {
+          confirmButtonText: "确定",
+          cancelButtonText: "取消",
+          type: "warning"
+        }).then(() => {
+          this.exportLoading = true;
+          return delLossStatisticsExport(queryParams);
+        }).then(response => {
+          this.download(response.msg);
+          this.exportLoading = false;
+        }).catch(() => {});
+    }
+  }
+};
+</script>

+ 1 - 1
src/views/qw/sop/updateSop.vue

@@ -495,7 +495,7 @@ export default {
       this.updateQwUserDialog.open = true
     },
     handleUpdateSopTemp(){
-      this.selectListSopTemp(this.form.type,1)
+      this.selectListSopTemp(this.form.sendType,1)
     },
     //刷新部分数据
     refreshData(row){

+ 1 - 12
src/views/qw/sopTemp/index.vue

@@ -271,16 +271,6 @@
           <el-input-number v-model="form.num" :min="1" label="每天催课次数" @change="sendNumChange"></el-input-number>
         </el-form-item>
         <el-form-item label="催课时间" v-if="form.sendType == 11 && !form.id">
-<!--          <el-time-picker-->
-<!--            v-for="item in form.timeList"-->
-<!--            class="custom-input"-->
-<!--            v-model="item.value"-->
-<!--            value-format="HH:mm"-->
-<!--            format="HH:mm"-->
-<!--            :picker-options="{ selectableRange: startTimeRange }"-->
-<!--            placeholder="时间"-->
-<!--            style="width: 200px;height: 20px;margin-left: 10px;margin-top: 10px">-->
-<!--          </el-time-picker>-->
           <div v-for="(item, index) in form.timeList" :key="index" style="margin-bottom: 10px;">
             <el-time-picker
               class="custom-input"
@@ -718,14 +708,13 @@ export default {
 
           }
 
-
-
           let f = JSON.parse(JSON.stringify(this.form));
           if (f.timeList && f.timeList.length > 0) {
             f.timeDesc = f.timeList.map(item => item.desc);
             f.timeList = f.timeList.map(item => item.value);
           }
 
+          console.log("f-----------",f)
           const loading = this.$loading({
             lock: true,
             text: 'Loading',