duanshenglang 5 дней назад
Родитель
Сommit
6d7089ae8b

+ 76 - 7
lottery/css/select-tag.css

@@ -18,7 +18,47 @@
 .search_box {
   padding: 0 15px;
 }
+.search_icon {
+  width: 24px;
+  height: 24px;
+}
+
+.search_input {
+  padding: 0;
+}
+
+.search_input .van-search__content {
+  padding: 0;
+  border-radius: 19px;
+}
+
+.search_input .van-cell {
+  padding: 0;
+}
+
+.search_input .van-field__body {
+  display: flex;
+  align-items: center;
+  position: relative;
+}
+
+.search_input .van-field__body input {
+  border: none;
+  width: 100%;
+  height: 40px;
+  background: #FAFAFA;
+  padding: 10px 36px 10px 15px;
+  border-radius: 19px;
+}
 
+.search_input .van-field__body input:focus {
+  outline: none;
+}
+
+.search_input .van-field__body .van-field__right-icon {
+  position: absolute;
+  right: 12px;
+}
 .check_group {
   display: flex;
   align-items: center;
@@ -27,20 +67,26 @@
 }
 
 .tag_list {
-  height: 366px;
+  height: 406px;
+  overflow: auto;
+}
+.tag_list1 {
+  height: 371px;
   overflow: auto;
 }
-
 .tag_item {
   padding: 15px;
   border-bottom: 1px solid #F1F1F1;
 }
-
+.tag_top {
+  display: flex;
+  align-items: center;
+  padding-bottom: 15px;
+}
 .tag_item_title {
   font-size: 14px;
   color: #222222;
   line-height: 20px;
-  padding-bottom: 15px;
 }
 
 .tag_item_data {
@@ -49,7 +95,7 @@
   gap: 10px;
 }
 
-.tag_item_data span {
+.tag_unchecked {
   height: 25px;
   font-size: 12px;
   color: #999999;
@@ -59,7 +105,16 @@
   border-radius: 5px;
   border: 1px solid #F1F1F1;
 }
-
+.tag_checked {
+  height: 25px;
+  font-size: 12px;
+  color: #136DFB;
+  padding: 4px 8px;
+  box-sizing: border-box;
+  background: rgba(19, 109, 251, 0.05);
+  border-radius: 5px;
+  border: 1px solid #136DFB;
+}
 .tag_footer {
   display: flex;
   align-items: center;
@@ -68,7 +123,13 @@
   gap: 10px;
   box-shadow: 0px -3px 6px 1px rgba(0, 0, 0, 0.05);
 }
-
+.tag_footers {
+  display: flex;
+  align-items: center;
+  padding: 10px 29px 10px 30px;
+  box-sizing: border-box;
+  gap: 10px;
+}
 .tag_re_btn {
   height: 50px;
   font-weight: 500;
@@ -94,4 +155,12 @@
   background: #1677FF;
   border-radius: 20px;
   white-space: nowrap;
+}
+.van-radio__icon {
+  height: 16px;
+  line-height: 16px;
+}
+.van-checkbox__icon {
+  height: 16px;
+  line-height: 16px;
 }

BIN
lottery/img/qw/article1.png


BIN
lottery/img/qw/close_img.png


BIN
lottery/img/qw/close_mater.png


BIN
lottery/img/qw/con_add.png


BIN
lottery/img/qw/dept_icon.png


BIN
lottery/img/qw/file1.png


BIN
lottery/img/qw/folder.png


BIN
lottery/img/qw/form1.png


BIN
lottery/img/qw/imgList.png


BIN
lottery/img/qw/imgList1.png


BIN
lottery/img/qw/imgList_type.png


BIN
lottery/img/qw/imgList_type1.png


BIN
lottery/img/qw/img_type.png


BIN
lottery/img/qw/img_type1.png


BIN
lottery/img/qw/link1.png


BIN
lottery/img/qw/mem_icon.png


BIN
lottery/img/qw/noselect_img.png


BIN
lottery/img/qw/select_icon.png


BIN
lottery/img/qw/select_img.png


BIN
lottery/img/qw/sop.png


BIN
lottery/img/qw/video.png


BIN
lottery/img/qw/video1.png


+ 170 - 39
lottery/js/select-tag.js

@@ -3,47 +3,48 @@ var demo_str = `
     <div class="clientTag_title">
       <div class="close_icon"></div>
       <div>按客户标签筛选</div>
-      <img class="close_icon" src="./img/qw/close_icon.png" alt="" @click="popupVisible = false">
+      <img class="close_icon" src="../img/qw/close_icon.png" alt="" @click="popupVisible = false">
     </div>
     <div class="search_box">
       <van-search class="search_input" placeholder="搜索客户昵称/客户群昵称" v-model="keyword" search :clearable="false" left-icon="" @search="handleSearch">
         <!-- 自定义右侧图标 -->
         <template v-slot:right-icon>
-          <img class="search_icon" src="./img/qw/search_icon.png" alt="" @click="handleSearch" />
+          <img class="search_icon" src="../img/qw/search_icon.png" alt="" @click="handleSearch" />
         </template>
       </van-search>
     </div>
-    <div class="check_group">
-      <van-checkbox v-model="tagType" @click="changeTagType(e)">
+    <van-radio-group v-model="tagType" direction="horizontal" v-if="showLabelFilter">
+      <van-radio :name="1">
+        标签满足其一
         <template #icon="props">
-          <div :class="props.tagType ? 'check_div_active' : 'check_div'"></div>
+          <div :class="props.checked ? 'check_div_active' : 'check_div'"></div>
         </template>
-        <span>标签满足其一</span>
-      </van-checkbox>
-      <van-checkbox v-model="tagType" @click="changeTagType(e)">
+      </van-radio>
+      <van-radio :name="0">
+        标签同时满足
         <template #icon="props">
-          <div :class="props.tagType ? 'check_div_active' : 'check_div'"></div>
+          <div :class="props.checked ? 'check_div_active' : 'check_div'"></div>
         </template>
-        <span>标签同时满足</span>
-      </van-checkbox>
-    </div>
-    <div class="tag_list">
-      <div class="tag_item">
-        <div class="tag_item_title">标签名称</div>
-        <div class="tag_item_data">
-          <span>标签名称</span><span>标签名称</span><span>标签名称</span><span>标签名称</span><span>标签名称</span>
+      </van-radio>
+    </van-radio-group>
+    <div class="tag_list1">
+      <div class="tag_item" v-for="(item, index) in tagGroupList" :key="index">
+        <div class="tag_top">
+          <van-checkbox v-model="item.single" @change="selectAllTag(item)">
+            <template #icon="props">
+              <div :class="props.checked ? 'check_div_active' : 'check_div'"></div>
+            </template>
+          </van-checkbox>
+          <div class="tag_item_title">{{ item.groupName }}</div>
         </div>
-      </div>
-      <div class="tag_item">
-        <div class="tag_item_title">标签名称</div>
         <div class="tag_item_data">
-          <span>标签名称</span><span>标签dsfgdf名称</span><span>标签名称</span><span>标签名ds称</span><span>标签名称</span>
+          <span :class="{tag_checked: tag.checked, tag_unchecked: !tag.checked}" v-for="(tag, tagIndex) in item.tagList" :key="tagIndex" @click="handleTag(tag, item)">{{ tag.name }}</span>
         </div>
       </div>
     </div>
     <div class="tag_footer">
-      <div class="tag_re_btn" @click="handleTagClick('重选')">重选</div>
-      <div class="tag_ok_btn" @click="handleTagClick('确定')">确定</div>
+      <div class="tag_re_btn" @click="handleClear">重选</div>
+      <div class="tag_ok_btn" @click="handleSubmit">确定</div>
     </div>
   </van-popup>
 `
@@ -51,45 +52,175 @@ var demoComponent = Vue.extend({
   template:demo_str ,
   data:function(){
     return {
+      httpUrl: '',
+      env: '',
       keyword: '',
-      tagType: false,
+      tagType: 1,
       popupVisible: this.showClientTag, // 本地状态,避免直接修改 prop
+      tagGroupList: [],
+      showTagList: [],
     }
   },
   props: {
+    labelFilter: {
+      type: Number,
+      default: 1
+    },
     showClientTag: {
       type: Boolean,
       default: () => false
+    },
+    selectTagList: {
+      type: Array,
+      default: () => []
+    },
+    showLabelFilter: {
+      type: Boolean,
+      default: true
     }
   },
   watch: {
-    // 外部修改时同步到内部
     showClientTag(val) {
       this.popupVisible = val
+      if (val) {
+        this.showTagList = this.selectTagList || []
+        this.getTagList()
+        this.tagType = this.labelFilter
+        console.log('tagType', this.tagType)
+      }
     },
-    // 内部关闭/打开时告知父级
     popupVisible(val) {
       this.$emit('update:showClientTag', val)
     }
   },
   created() {
+    this.env = this.getQueryParam('env')
+
+    if (!this.env || this.env === 'prod') {
+      this.httpUrl = 'https://wlapi.wefanbot.com'
+    } else {
+      this.httpUrl = 'http://test.wefanbot.com:18993'
+    }
   },
   methods: {
-    changeTagType(e) {
-      // this.tagType = e.target.tagType
+    getTagList() {
+      const headers = new Headers()
+      headers.append('token', 'k1176728601917100001')
+      headers.append('tenancyId', '103548289110001')
+      headers.append('userId', '100884320310007')
+      fetch(this.httpUrl + `/scrm/v1/wxcp-tag/m/findListWithGroup`, {
+          method: 'GET',
+          headers: headers
+        }).then(res => {
+          return res.json()
+        }).then(result => {
+          let { data, code, msg } = result
+          if (code === 1) {
+            if (data && data.length > 0) {
+              const groupedData = {}
+              data.forEach(item => {
+                  if (!groupedData[item.groupId]) {
+                      groupedData[item.groupId] = {
+                          ...item,
+                          single: false,
+                          tagList: []
+                      }
+                  }
+                  this.$set(item, 'checked', false)
+                  const tag = {
+                      id: item.tagId,
+                      name: item.tagName,
+                      checked: item.checked,
+                  }
+                  groupedData[item.groupId].tagList.push(tag)
+                  delete groupedData[item.groupId].tagId
+                  delete groupedData[item.groupId].tagName
+                  delete groupedData[item.groupId].tagSort
+              })
+              // 将分组结果转换为数组形式
+              this.tagGroupList = Object.values(groupedData)
+              this.tagGroupList.forEach(item => {
+                  item.tagList.forEach(t => {
+                      this.selectTagList.forEach(tl => {
+                          if (t.id === tl.id) {
+                              this.$set(t, 'checked', true)
+                          }
+                      })
+                  })
+                  if (item.tagList.every(obj => obj['checked'] === true)) {
+                      this.$set(item, 'single', true)
+                  } else {
+                      this.$set(item, 'single', false)
+                  }
+              })
+            }
+          } else {
+            vant.Toast.fail(msg)
+          }
+        })
     },
-    handleTagClick(type) {
-      if (type === '重选') {
-        this.keyword = ''
-        this.tagType = false
-      } else if (type === '确定') {
-        // this.$emit('change-tag', {
-        //   keyword: this.keyword,
-        //   tagType: this.tagType,
-        // })
-      }
+    handleTag(val, data) {
+      this.tagGroupList.forEach(item => {
+        if (data.groupId === item.groupId) {
+          item.tagList.forEach(t => {
+            if (t.id === val.id) {
+              this.$set(t, 'checked', !t.checked)
+            }
+          })
+          if (val.checked) {
+              this.showTagList.push(val)
+              if (item.tagList.every(obj => obj['checked'] === true)) {
+                  this.$set(item, 'single', true)
+              } else {
+                  this.$set(item, 'single', false)
+              }
+          } else {
+              this.showTagList = this.showTagList.filter(item => item.id !== val.id)
+              this.$set(item, 'single', false)
+          }
+        }
+      })
+    },
+    selectAllTag(data) {
+        this.tagGroupList.forEach(item => {
+            if (data.groupId === item.groupId) {
+                item.tagList.forEach(t => {
+                    this.$set(t, 'checked', data.single)
+                    if (data.single) {
+                        this.showTagList.push(t)
+                        // 去重
+                        const map = new Map()
+                        this.showTagList = this.showTagList.filter(v => !map.has(v.id) && map.set(v.id, v))
+                    } else {
+                        this.showTagList = this.showTagList.filter(s => s.id !== t.id)
+                    }
+                })
+            }
+        })
+    },
+    handleClear() {
+      this.showTagList = []
+      this.tagGroupList.forEach(item => {
+        item.single = false
+        item.tagList.forEach(t => {
+            this.$set(t, 'checked', false)
+        })
+      })
+    },
+    handleSubmit() {
+      this.$emit('add', this.tagType, this.showTagList)
+      this.$emit('update:showClientTag', false)
     },
-    handleSearch () {}
+    handleSearch() { },
+    // 截取url中的数据
+      getQueryParam(paramName) {
+        // 获取当前URL的查询字符串部分  
+        const queryString = window.location.search;
+        // 创建一个URLSearchParams对象  
+        const urlParams = new URLSearchParams(queryString);
+        // 返回指定参数的值,如果不存在则返回null  
+        return urlParams.get(paramName);
+      },
   },
 })
 

+ 62 - 27
lottery/qw.html

@@ -427,7 +427,7 @@
     gap: 10px;
     height: calc(100vh - 189px);
     overflow: auto;
-    padding-bottom: 46px;
+    padding-bottom: 100px;
     box-sizing: border-box;
   }
   .noclient_list {
@@ -456,6 +456,24 @@
     box-sizing: border-box;
     height: 68px;
     display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+  }
+  .van-radio__icon {
+    width: 16px;
+    height: 16px;
+  }
+  .van-radio__label {
+    font-size: 14px;
+    line-height: 20px;
+    color: #222222;
+  }
+  .van-radio--horizontal {
+    margin-right: 20px;
+  }
+  .van-radio-group {
+    margin-top: 15px;
+    margin-left: 15px;
   }
   .check_div {
     width: 16px;
@@ -487,12 +505,16 @@
   .client_info {
     display: flex;
     align-items: center;
+    flex: 1;
+    min-width: 0;
+    overflow: hidden;
   }
   .client_info img{
     width: 48px;
     height: 48px;
     border-radius: 8px;
     margin-right: 10px;
+    flex-shrink: 0;
   }
   .client_info_text {
     font-weight: 500;
@@ -501,12 +523,17 @@
     display: flex;
     flex-direction: column;
     gap: 9px;
+    flex: 1;
+    min-width: 0;
+    overflow: hidden;
   }
   .client_tag {
-    height: 21px;
     display: flex;
     align-items: center;
     gap: 8px;
+    overflow: hidden;
+    flex-wrap: nowrap;
+    width: 100%;
   }
   .client_tag span {
     height: 21px;
@@ -518,10 +545,8 @@
     background: #FAFAFA;
     border: 1px solid #F1F1F1;
     box-sizing: border-box;
-    max-width: 100px; /* 必须设置宽度 */
-    overflow: hidden;
     white-space: nowrap;
-    text-overflow: ellipsis;
+    flex-shrink: 0;
   }
   .van-checkbox__icon .van-icon {
     width: 16px;
@@ -610,7 +635,7 @@
     align-items: center;
     font-weight: 500;
     font-size: 14px;
-    line-: #222222;
+    color: #222222;
   }
   .reach_tools img {
     width: 20px;
@@ -663,6 +688,9 @@
     color: #666666;
     line-height: 17px;
   }
+  .van-checkbox {
+    width: 31px;
+  }
 </style>
 
 <body>
@@ -671,7 +699,7 @@
       <!-- 首页 -->
       <div v-if="activeTabbar === 'home'">
         <div v-if="bind" :style="{'height': 'calc(100vh - 46px)'}">
-          <iframe src="qw/qwJxs.html" width="100%" height="100%" frameborder="0"></iframe>
+          <iframe :src="`qw/qwJxs.html?bId=${bId}&env=${env}`" width="100%" height="100%" frameborder="0"></iframe>
         </div>
         <div v-else>
           <div class="home_bg">
@@ -766,7 +794,7 @@
           </div>
         </div>
         <!-- 标签筛选组件 -->
-        <select-tag :show-Client-Tag.sync="showClientTag"></select-tag>
+        <select-tag :show-Client-Tag.sync="showClientTag" @add="handleAdd"></select-tag>
         <!-- 客户、客户群列表 -->
         <div class="client_content" :style="activeTab === '客户' ? 'top: 144px' : 'top: 100px'">
           <div class="client_content_box">
@@ -790,7 +818,9 @@
                     <div class="client_info_text">
                       <div>{{item.name}}</div>
                       <div class="client_tag">
-                        <span v-for="(tag, tagIndex) in item.tagList.slice(0, 2)" :key="tagIndex">{{tag}}</span>
+                        <span v-for="(tag, tagIndex) in item.tagList" :key="tagIndex">
+                          {{tag}}
+                        </span>
                       </div>
                     </div>
                   </div>
@@ -798,7 +828,7 @@
               </van-checkbox-group>
             </div>
             <div class="client_list" v-else-if="groupList.length > 0">
-              <div class="group_item" v-for="item in groupList" :key="item.id" @click="handleGroupClientDetail">
+              <div class="group_item" v-for="item in groupList" :key="item.id" @click="handleGroupClientDetail(item)">
                 <div class="group_info">
                   <div class="group_name">{{item.name}}</div>
                   <div class="group_data">
@@ -817,7 +847,7 @@
                 <div class="notask_text">暂无数据</div>
               </div>
             </div>
-            <div class="send_foot" v-if="checkedClientIds.length > 0">
+            <div class="send_foot" v-if="checkedClientIds.length > 0 && activeTab === '客户'">
               <div class="send_btn" @click="handleMass">创建群发</div>
             </div>
           </div>
@@ -841,7 +871,7 @@
                 <div class="tools_box_info_text">你的专属外呼助理</div>
               </div>
             </div>
-            <div v-if="!bind">
+            <div v-if="bind">
               <div class="reach_tools reach_tools_title">
                 <img src="./img/qw/task_icon.png" alt="">
                 <span>任务管理</span>
@@ -893,7 +923,6 @@
         bId: null,
         env: '',
         memberId: null,
-        tenancyId: null,
 
         bind: false, // 首页
         taskList: [],
@@ -938,16 +967,13 @@
       this.env = this.getQueryParam('env')
 
       if (!this.env || this.env === 'prod') {
-        // this.httpUrl = 'https://wlapi.wefanbot.com'
-        this.httpUrl = 'http://192.168.1.128:18993'
+        this.httpUrl = 'https://wlapi.wefanbot.com'
       } else {
-        // this.httpUrl = 'http://192.168.1.251:18993'
-        this.httpUrl = 'http://192.168.1.128:18993'
+        this.httpUrl = 'http://test.wefanbot.com:18993'
       }
       if (this.getQueryParam('memberId')) {
         // 已授权
         this.memberId = this.getQueryParam('memberId')
-        this.tenancyId = this.getQueryParam('tenancyId')
         this.getCpH5Login()
       } else {
         // 授权
@@ -992,7 +1018,7 @@
               localStorage.setItem('userId', data.userId)
             } else {
               this.clientData()
-              // this.getTaskList()
+              this.getTaskList()
             }
           } else {
             vant.Toast.fail(msg)
@@ -1062,7 +1088,7 @@
       handleTabClick(tab) {
         this.activeTabbar = tab
         if (tab === 'home') {
-          // this.getTaskList()
+          this.getTaskList()
           this.clientData()
         } else if (tab === 'client') {
           this.clientStartTime = null
@@ -1088,11 +1114,11 @@
       },
       // 全部任务
       handleAllTask() {
-        window.location.href = `qw/toDoTask.html?memberId=${this.memberId}`
+        window.location.href = `qw/toDoTaskList.html?memberId=${this.memberId}&env=${this.env}&bId=${this.bId}`
       },
       // 任务详情
       handleTaskDetail(item) {
-        window.location.href = `qw/taskDetail.html?taskId=${item.todoTaskId}&type=${item.type}&env=${this.env}`
+        window.location.href = `qw/taskDetail.html?taskId=${item.todoTaskId}&type=${item.type}&env=${this.env}&bId=${this.bId}`
       },
       handleChange(tab) {
         this.activeTab = tab
@@ -1104,6 +1130,11 @@
           this.pageGroup()
         }
       },
+      handleAdd(tagType, tagList) {
+        this.labelFilter = tagType
+        this.tagIds = tagList.length ? tagList.map(item => item.id) : []
+        this.pageClient()
+      },
       // 搜索客户
       handleSearch() {
         this.pageClient()
@@ -1119,6 +1150,8 @@
             startTime: this.clientStartTime,
             endTime: this.clientEndTime,
             keyword: this.keyword,
+            labelFilter: this.labelFilter,
+            tagIds: this.tagIds,
             page: 1,
             pageCount: 1000,
           }),
@@ -1195,16 +1228,17 @@
         window.location.href = `userProfile.html?bId=${this.bId}&env=${this.env}&memberId=${item.memberId}&externalUserId=${item.externalUserid}`
       },
       // 客户群详情
-      handleGroupClientDetail() {
-        // window.location.href = `qw/clientGroupDetail.html?bId=11`
+      handleGroupClientDetail(item) {
+        window.location.href = `qw/clientGroupDetail.html?bId=${this.bId}&memberId=${this.memberId}&groupId=${item.id}&env=${this.env}`
       },
       // 营销群发
       handleMass() {
-        // window.location.href = `qw/mass.html?bId=11`
+        localStorage.setItem('clientData', JSON.stringify(this.selectedClients))
+        window.location.href = `qw/mass.html?bId=${this.bId}&env=${this.env}&memberId=${this.memberId}`
       },
       // 客户群发任务
       handleClientMass() {
-        window.location.href = `qw/clientMass.html?bId=11`
+        window.location.href = `qw/clientMassList.html?bId=${this.bId}&env=${this.env}&memberId=${this.memberId}`
       },
       // 勾选客户变更
       onChangeChecked (value) {
@@ -1214,9 +1248,10 @@
         this.selectedClients = this.clientList
           .filter(item => value.includes(item.id))
           .map(item => ({
-            id: item.id,
+            clientId: item.id,
             externalUserid: item.externalUserid,
             // 如果后面还需要其它字段,也可以一起带上
+            name: item.name,
           }))
         console.log('当前选中的客户列表(含 id 和 externalUserid): ', this.selectedClients)
       },

+ 62 - 23
lottery/qw/clientGroupDetail.html

@@ -73,14 +73,24 @@
   }
 
   .task_item_text {
+    display: flex;
+    flex-direction: column;
+    gap: 9px;
+  }
+  .task_item_name {
     font-weight: 500;
     font-size: 14px;
     color: #222222;
     display: flex;
-    flex-direction: column;
-    gap: 9px;
+    align-items: center;
+    height: 20px;
+  }
+  .wx_tag {
+    font-weight: 400;
+    font-size: 12px;
+    color: #4CDE93;
+    padding-left: 5px;
   }
-
   .client_tag {
     display: flex;
     align-items: center;
@@ -121,21 +131,23 @@
 <body>
   <div id="box" class="box">
     <div class="page5">
-      <!-- <div class="notask">
-        <img src="./img/qw/no_task.png" alt="">
-        <div class="notask_text">暂无待办任务</div>
-      </div> -->
-      <div class="task_item">
-        <div class="task_item_title">
-          <img src="../img/avatar.png" alt="">
-          <div class="task_item_text">
-            <div>客户昵称</div>
-            <div class="client_tag">
-              <span>标签</span><span>标签</span><span>标签</span><span>标签</span>
+      <div v-if="groupMembersList.length">
+        <div class="task_item" v-for="(item, index) in groupMembersList" :key="index" @click="handleClick(item)">
+          <div class="task_item_title">
+            <img :src="item.avatar ? item.avatar : '../img/avatar.png'" alt="">
+            <div class="task_item_text">
+              <div class="task_item_name">{{item.userName}}<span class="wx_tag" v-if="item.clientType === 1">@微信</span></div>
+              <div class="client_tag">
+                <span>标签</span>
+              </div>
             </div>
           </div>
         </div>
       </div>
+      <div class="notask" v-else>
+        <img src="./img/qw/no_task.png" alt="">
+        <div class="notask_text">暂无待办任务</div>
+      </div>
     </div>
   </div>
 </body>
@@ -148,7 +160,8 @@
         bId: null,
         env: '',
         memberId: null,
-        corpId: null,
+        groupId: null,
+        groupMembersList: [],
       }
     },
     created() {
@@ -160,16 +173,42 @@
       } else {
         this.httpUrl = 'http://test.wefanbot.com:18993'
       }
-      // if (this.getQueryParam('memberId')) {
-      //   // 已授权
-      //   this.memberId = this.getQueryParam('memberId')
-      //   this.corpId = this.getQueryParam('corpId')
-      // } else {
-      //   // 授权
-      //   this.getAuth()
-      // }
+      this.memberId = this.getQueryParam('memberId')
+      this.groupId = this.getQueryParam('groupId')
+      this.getClientGroupDetail()
     },
     methods: {
+      // 获取客户群详情
+      getClientGroupDetail() {
+        fetch(this.httpUrl + '/scrm/v1/wxcp-workbench/p/pageGroupMember', {
+          method: 'post',
+          body: JSON.stringify({
+            bid: this.bId,
+            memberId: this.memberId,
+            groupId: this.groupId,
+            page: 1,
+            pageCount: 1000,
+          }),
+          headers: {
+            'Content-Type': 'application/json'
+          }
+        }).then(res => {
+          return res.json()
+        }).then(result => {
+          let { data, code, msg } = result
+          if (code === 1) {
+            this.groupMembersList = data.records
+          } else {
+            vant.Toast.fail(msg)
+          }
+        })
+      },
+      // 点击客户
+      handleClick(item) {
+        if (item.jump) {
+          window.location.href = `../userProfile.html?bId=${this.bId}&env=${this.env}&memberId=${this.memberId}&externalUserId=${item.userId}`
+        }
+      },
       // 截取url中的数据
       getQueryParam(paramName) {
         // 获取当前URL的查询字符串部分  

Разница между файлами не показана из-за своего большого размера
+ 927 - 109
lottery/qw/clientMass.html


+ 11 - 8
lottery/qw/clientMassDetail.html

@@ -485,7 +485,10 @@
         httpUrl: '',
         bId: null,
         env: '',
-        memberId: null,
+        token: null,
+        tenancyId: null,
+        userId: null,
+        taskId: null,
         keyword: '',
         formValidate: {
           name: '',
@@ -536,10 +539,10 @@
       } else {
         this.httpUrl = 'http://test.wefanbot.com:18993'
       }
-      this.token = 'k1176129370564100001'
-      this.tenancyId = '103548289110001'
-      this.userId = '103548289110001'
-      this.id = '117654331810001'
+      this.token = localStorage.getItem('tokenValue')
+      this.tenancyId = localStorage.getItem('tenancyIdValue')
+      this.userId = localStorage.getItem('userId')
+      this.taskId = this.getQueryParam('id')
       this.getDetail()
     },
     methods: {
@@ -559,7 +562,7 @@
         headers.append('token', this.token)
         headers.append('tenancyId', this.tenancyId)
         headers.append('userId', this.userId)
-        fetch(this.httpUrl + `/scrm/v1/wxcp-send/m/findById2?id=${this.id}`, {
+        fetch(this.httpUrl + `/scrm/v1/wxcp-send/m/findById2?id=${this.taskId}`, {
           method: 'GET',
           headers: headers
         }).then(res => {
@@ -611,7 +614,7 @@
         fetch(this.httpUrl + '/scrm/v1/wxcp-send/m/findMembersPage', {
           method: 'post',
           body: JSON.stringify({
-            sendId: this.id ,
+            sendId: this.taskId ,
             status: this.filterIndex,
             page: 1,
             pageCount: 1000,
@@ -642,7 +645,7 @@
         fetch(this.httpUrl + '/scrm/v1/wxcp-send/m/findClientsPage', {
           method: 'post',
           body: JSON.stringify({
-            sendId: this.id,
+            sendId: this.taskId,
             status: this.filterIndex,
             page: 1,
             pageCount: 1000,

+ 366 - 0
lottery/qw/clientMassList.html

@@ -0,0 +1,366 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport"
+    content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no, viewport-fit=cover" />
+  <title>客户群详情</title>
+  <!-- 引入样式文件 -->
+  <link rel="stylesheet"
+    href="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1758012584633/vant.css" />
+  <!-- 必须先引入vue,  后使用vant-ui -->
+  <script
+    src="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1742017957144/vue.js"></script>
+  <!-- 引入vant的组件库-->
+  <!-- 引入 Vant 的 JS 文件 -->
+  <script
+    src="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1758012748487/vant.min.js"></script>
+
+  <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
+  <script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script>
+  <!-- <script src="js/vconsole.min.js"></script>
+    <script>
+  		  var vConsole = new window.VConsole();
+  		</script> -->
+</head>
+<style>
+  body {
+    margin: 0;
+    padding: 0;
+  }
+
+  .box {
+    width: 100vw;
+    min-height: 100vh;
+    box-sizing: border-box;
+    background: #F7F9FC;
+  }
+
+  .page5 {
+    width: 100vw;
+    box-sizing: border-box;
+    padding: 15px;
+  }
+  .client_top {
+    width: 100%;
+    height: 70px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 1;
+    background: #FFFFFF;
+  }
+  .client_top_box {
+    padding: 15px 15px 0px;
+  }
+  .search_icon {
+    width: 24px;
+    height: 24px;
+  }
+  .search_input {
+    padding: 0;
+  }
+  .search_input .van-search__content {
+    padding: 0;
+    border-radius: 19px;
+  }
+  .search_input .van-cell {
+    padding: 0;
+  }
+  .search_input .van-field__body {
+    display: flex;
+    align-items: center;
+    position: relative;
+  }
+  .search_input .van-field__body input{
+    border: none;
+    width: 100%;
+    height: 40px;
+    background: #FAFAFA;
+    padding: 10px 36px 10px 15px;
+    border-radius: 19px;
+  }
+  .search_input .van-field__body input:focus {
+    outline: none;
+  }
+  .search_input .van-field__body .van-field__right-icon{
+    position: absolute;
+    right: 12px;
+  }
+
+  .task_list {
+    width: 100%;
+    height: calc(100vh - 70px);
+    position: absolute;
+    top: 70px;
+    left: 0;
+    z-index: 1;
+    background: #F7F9FC;
+    overflow: auto;
+    padding-bottom: 60px;
+    box-sizing: border-box;
+  }
+  .task_content {
+    padding: 15px;
+  }
+  .task_item {
+    width: 100%;
+    background: #FFFFFF;
+    border-radius: 15px;
+    border: 1px solid #FFFFFF;
+    box-sizing: border-box;
+    margin-bottom: 10px;
+    padding: 15px;
+  }
+
+  .task_item:last-child {
+    margin-bottom: 0;
+  }
+
+  .task_item_title {
+    display: flex;
+    justify-content: space-between;
+    font-weight: 500;
+    font-size: 16px;
+    color: #222222;
+    line-height: 20px;
+  }
+  .task_item_name {
+    width: 160px;
+    /* 必须设置宽度 */
+    overflow: hidden;
+    white-space: nowrap;
+    text-overflow: ellipsis;
+  }
+  .client_tag {
+    font-size: 12px;
+    line-height: 17px;
+  }
+  .task_item_time {
+    font-size: 12px;
+    color: #999999;
+    line-height: 17px;
+    padding-top: 8px;
+  }
+  .notask {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding-top: 198px;
+  }
+
+  .notask img {
+    width: 102px;
+    height: 90px;
+  }
+
+  .notask_text {
+    font-size: 14px;
+    color: #CCCCCC;
+    line-height: 20px;
+    padding-top: 20px;
+  }
+  .send_foot {
+    position: absolute;
+    bottom: 10px;
+    left: 0;
+    width: 100%;
+    z-index: 2;
+  }
+  .send_btn {
+    font-weight: 500;
+    font-size: 14px;
+    color: #FFFFFF;
+    line-height: 20px;
+    padding: 15px 0;
+    border-radius: 20px;
+    background: #1677FF;
+    margin: 0 35px;
+    text-align: center;
+  }
+  .status1 {
+    color: #136DFB;
+  }
+  .status2 {
+    color: #FFA041;
+  }
+  .status3 {
+    color: #FF4141;
+  }
+</style>
+
+<body>
+  <div id="box" class="box">
+    <div class="page5">
+      <div v-if="tableData.length">
+        <div class="client_top">
+          <div class="client_top_box">
+            <van-search class="search_input" placeholder="搜索客户昵称/客户群昵称" v-model="keyword" :clearable="false" left-icon="" @search="initData">
+              <!-- 自定义右侧图标 -->
+              <template v-slot:right-icon>
+                <img class="search_icon" src="../img/qw/search_icon.png" alt="" @click="initData" />
+              </template>
+            </van-search>
+          </div>
+        </div>
+        <div class="task_list">
+          <div class="task_content">
+            <div class="task_item" v-for="item in tableData" :key="item.id" @click="handleClientMassDetail(item)">
+              <div class="task_item_title">
+                <div class="task_item_name">{{item.name}}</div>
+                <div v-if="item.isNotify === -2" class="client_tag status3">无符合条件客户</div>
+                <div v-else-if="item.isNotify === -1" class="client_tag status3">创建失败</div>
+                <div v-else-if="item.isNotify === 0" class="client_tag status2">待通知</div>
+                <div v-else-if="item.isNotify === 1" class="client_tag status1">已通知</div>
+                <div v-else-if="item.isNotify === 3" class="client_tag status2">创建中</div>
+              </div>
+              <div class="task_item_time">创建时间:{{item.createdTime}}</div>
+            </div>
+          </div>
+        </div>
+        <div class="send_foot">
+          <div class="send_btn" @click="handleClientMass">创建客户群发</div>
+        </div>
+      </div>
+      <div class="notask" v-else>
+        <img src="./img/qw/no_task.png" alt="">
+        <div class="notask_text">暂无数据</div>
+      </div>
+    </div>
+  </div>
+</body>
+<script>
+  new Vue({
+    el: '#box',
+    data() {
+      return {
+        httpUrl: '',
+        bId: null,
+        env: '',
+        token: null,
+        tenancyId: null,
+        userId: null,
+        memberId: null,
+        keyword: '',
+        tableData: [],
+      }
+    },
+    created() {
+      this.bId = this.getQueryParam('bId')
+      this.env = this.getQueryParam('env')
+      this.memberId = this.getQueryParam('memberId')
+
+      if (!this.env || this.env === 'prod') {
+        this.httpUrl = 'https://wlapi.wefanbot.com'
+      } else {
+        this.httpUrl = 'http://test.wefanbot.com:18993'
+      }
+      this.token = localStorage.getItem('tokenValue')
+      this.tenancyId = localStorage.getItem('tenancyIdValue')
+      this.userId = localStorage.getItem('userId')
+      this.initData()
+    },
+    methods: {
+      initData() {
+        const headers = new Headers()
+        headers.append('token', this.token)
+        headers.append('tenancyId', this.tenancyId)
+        headers.append('userId', this.userId)
+        fetch(this.httpUrl + '/scrm/v1/wxcp-send/m/findListByPage', {
+          method: 'post',
+          body: JSON.stringify({
+            keyword: this.keyword,
+            page: 1,
+            pageCount: 1000,
+          }),
+          headers: {
+            'Content-Type': 'application/json',
+            'token': this.token,
+            'tenancyId': this.tenancyId,
+            'userId': this.userId,
+          }
+        }).then(res => {
+          return res.json()
+        }).then(result => {
+          let { data, code, msg } = result
+          if (code === 1) {
+            this.tableData = data.records || []
+            this.tableData.forEach(item => {
+              item.createdTime = this.timeFormat(item.createdTime)
+            })
+          } else {
+            vant.Toast.fail(msg)
+          }
+        })
+      },
+      onSearch() {
+        this.initData()
+      },
+      handleClientMassDetail(item) {
+        window.location.href = `clientMassDetail.html?bId=${this.bId}&env=${this.env}&id=${item.id}`
+      },
+      handleClientMass() {
+        window.location.href = `clientMass.html?bId=${this.bId}&env=${this.env}&memberId=${this.memberId}`
+      },
+      timeFormat(time, format = 'yyyy-MM-dd hh:mm:ss') {
+        if (time === undefined || time === '' || time === null) {
+          return '/';
+        }
+
+        const date = new Date(time);
+        const o = {
+          'M+': date.getMonth() + 1, // 月份
+          'd+': date.getDate(),      // 日
+          'h+': date.getHours(),     // 小时
+          'm+': date.getMinutes(),   // 分钟
+          's+': date.getSeconds(),   // 秒
+          'q+': Math.floor((date.getMonth() + 3) / 3), // 季度
+          'S': date.getMilliseconds() // 毫秒
+        };
+
+        // 处理年份
+        if (/(y+)/.test(format)) {
+          format = format.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length));
+        }
+
+        // 处理日期和时间部分
+        for (let k in o) {
+          if (new RegExp('(' + k + ')').test(format)) {
+            let value = o[k];
+            let padding = RegExp.$1.length === 1 ? '' : '00'; // 根据格式字符串中的长度决定是否补零
+            format = format.replace(RegExp.$1, ('' + value).padStart(padding.length + value.toString().length - value.toString().length, '0'));
+          }
+        }
+
+        // 如果格式只包含时间部分,移除日期部分可能的占位符
+        if (!/(M+|d+)/.test(format)) {
+          // 移除任何可能存在的日期占位符(如:'yyyy-MM-dd ')
+          format = format.replace(/(\s*-\s*){2}/g, ''); // 移除两个'-'之间的任何内容
+          format = format.replace(/^\s*yyyy-\s*/, ''); // 移除开头的'yyyy-'
+        }
+
+        // 如果格式只包含日期部分,移除时间部分可能的占位符
+        if (!/(h+|m+|s+)/.test(format)) {
+          // 移除任何可能存在的时间占位符(如:' hh:mm:ss')
+          format = format.replace(/(\s*:\s*){2}/g, ''); // 移除两个':'之间的任何内容
+          format = format.replace(/\s*hh:\s*$/, ''); // 移除结尾的' hh:'
+        }
+
+        return format;
+      },
+      // 截取url中的数据
+      getQueryParam(paramName) {
+        // 获取当前URL的查询字符串部分  
+        const queryString = window.location.search;
+        // 创建一个URLSearchParams对象  
+        const urlParams = new URLSearchParams(queryString);
+        // 返回指定参数的值,如果不存在则返回null  
+        return urlParams.get(paramName);
+      },
+    }
+  })   
+</script>
+
+</html>

+ 334 - 50
lottery/qw/mass.html

@@ -47,12 +47,13 @@
     padding: 15px 10px;
   }
 
-  .task_item {
+  .send_item {
     width: 100%;
     background: #FFFFFF;
     border-radius: 15px;
     box-sizing: border-box;
-    padding: 15px 10px;
+    padding: 15px 9px 15px 10px;
+    margin-bottom: 15px;
   }
 
 
@@ -119,6 +120,8 @@
     flex-wrap: wrap;
     align-items: center;
     gap: 10px;
+    max-height: 52px;
+    overflow: hidden;
   }
 
   .mem_tag span {
@@ -130,32 +133,6 @@
     color: #136DFB;
   }
 
-  .send_detail {
-    height: calc(100vh - 204px);
-    background: #FFFFFF;
-    border-radius: 15px 15px 0 0;
-  }
-
-  .van-tabs .van-tabs__wrap {
-    border-radius: 15px 15px 0 0;
-  }
-
-  .van-tabs--line .van-tabs__wrap {
-    height: 40px;
-  }
-
-  .van-tabs__nav {
-    padding: 0 25px 15px;
-  }
-
-  .van-tab--active {
-    color: #222222 !important;
-  }
-
-  .van-tab {
-    color: #999999;
-  }
-
   .send_centent {
     padding: 20px 15px;
   }
@@ -179,11 +156,22 @@
     flex-wrap: nowrap;
     overflow-x: auto;
     gap: 10px;
-    padding: 0 5px 10px;
+    padding: 10px 5px 10px;
   }
-
-  .img_list img {
-    min-width: 105px;
+  .img_box {
+    position: relative;
+    width: 105px;
+    height: 140px;
+  }
+  .close_imgMater {
+    width: 24px;
+    height: 24px;
+    position: absolute;
+    top: -8px;
+    right: -8px;
+  }
+  .img_url {
+    width: 105px;
     height: 140px;
     border-radius: 10px;
     object-fit: cover;
@@ -213,14 +201,21 @@
     padding: 10px;
     box-sizing: border-box;
     border: 1px solid #EFEFEF;
+    position: relative;
   }
 
-  .matters_item img {
-    width: 40px;
-    height: 40px;
+  .matters_item_img {
+    width: 24px;
+    height: 24px;
     margin-right: 8px;
   }
-
+  .close_mater {
+    width: 24px;
+    height: 24px;
+    position: absolute;
+    top: -8px;
+    right: -8px;
+  }
   .matters_item_info {
     display: flex;
     flex-direction: column;
@@ -244,6 +239,7 @@
   .matters_list {
     display: flex;
     flex-wrap: wrap;
+    justify-content: space-between;
     gap: 10px;
   }
 
@@ -341,26 +337,122 @@
     color: #999999;
     line-height: 17px;
   }
+  .van-cell {
+    padding: 15px 5px 0;
+  }
+  .matter_btn_box {
+    padding: 20px 15px 10px;
+  }
+  .matter_btn {
+    height: 50px;
+    padding: 15px 0;
+    background: rgba(19, 109, 251, 0.1);
+    border-radius: 20px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #136DFB;
+    text-align: center;
+    box-sizing: border-box;
+  }
+  .send_btn_box {
+    padding: 15px 25px 10px;
+  }
+  .send_btn {
+    height: 50px;
+    padding: 15px 0;
+    background: #1677FF;
+    border-radius: 20px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #FFFFFF;
+    text-align: center;
+    box-sizing: border-box;
+  }
+  textarea {
+    height: 68px !important;
+    line-height: 23px;
+  }
 </style>
 
 <body>
   <div id="box" class="box">
     <div class="page5">
+      <van-dialog 
+        v-model="showReturn" 
+        title="提示" 
+        show-cancel-button
+        :before-close="beforeClose"
+        confirm-button-text="确定"
+        cancel-button-text="取消">
+        <div style="padding: 20px; text-align: center;">确认返回吗?</div>
+      </van-dialog>
       <div class="task_content">
-        <div class="task_item">
+        <div class="send_item">
           <div class="mem_title">
-            <div class="mem_title_left">生效成员</div>
+            <div class="mem_title_left">发送客户</div>
             <div class="mem_title_right">
-              <div>全部成员</div>
+              <div>全部客户</div>
               <img src="../img/qw/more_icon.png" alt="">
             </div>
           </div>
           <div class="mem_tag">
-            <span v-for="(item, index) in formValidate.memberList" :key="index">{{item.name}}</span>
+            <span v-for="(item, index) in formValidate.clients" :key="index">{{item.name}}</span>
           </div>
         </div>
-      </div>
-      <div class="send_detail">
+        <div class="send_item">
+          <div class="mem_title_left">发送内容</div>
+          <van-field v-model="formValidate.contentText" maxlength="500" type="textarea" placeholder="请输入要发送给客户的文本" />
+        </div>
+        <div class="send_item">
+          <div class="mem_title_left">素材</div>
+          <template v-if="formValidate.ctOthers && formValidate.ctOthers.length > 0">
+            <div class="img_list" v-if="formValidate.ctOthers[0].matterType === 1">
+              <div class="img_box" v-for="(item, index) in formValidate.ctOthers" :key="index">
+                <img class="img_url" :src="item.url" alt="">
+                <img class="close_imgMater" src="../img/qw/close_img.png" alt="" @click="handleCloseMatter(item)">
+              </div>
+            </div>
+            <div class="video_container" v-if="formValidate.ctOthers[0].matterType === 2">
+              <video id="myVideo" style="width: 105px; height: 140px;" :src="formValidate.ctOthers[0].videoPath"></video>
+              <img id="playButton" class="play_button" src="../img/qw/play_icon.png" alt="" @click="playPause">
+            </div>
+            <div class="matters_item" v-if="formValidate.ctOthers[0].matterType === 5">
+              <img class="matters_item_img" src="../img/qw/link.png" alt="">
+              <div class="matters_item_info">
+                <div class="matters_item_title">{{formValidate.ctOthers[0].linkTitle}}</div>
+                <div>自定义链接</div>
+              </div>
+              <img class="close_mater" src="../img/qw/close_mater.png" alt="" @click="handleCloseMatter(item)">
+            </div>
+          </template>
+          <template v-if="formValidate.matters && formValidate.matters.length > 0">
+            <div class="img_list" v-if="matterImgList.length > 0">
+              <div class="img_box" v-for="(item, index) in matterImgList" :key="index">
+                <img class="img_url" :src="item.url" alt="">
+                <img class="close_imgMater" src="../img/qw/close_img.png" alt="" @click="handleCloseMatter(item)">
+              </div>
+            </div>
+            <div class="matters_list">
+              <div class="matters_item" v-for="(item, index) in matterOthersList" :key="index">
+                <img class="matters_item_img" v-if="item.contentType === 0" src="../img/qw/article.png" alt="">
+                <img class="matters_item_img" v-else-if="item.contentType === 2" src="../img/qw/form.png" alt="">
+                <img class="matters_item_img" v-else-if="item.contentType === 3" src="../img/qw/file.png" alt="">
+                <img class="matters_item_img" v-else src="../img/qw/link.png" alt="">
+                <div class="matters_item_info">
+                  <div class="matters_item_title">{{item.title || item.name}}</div>
+                  <div>{{matterTypeList.find(type => type.contentType === item.contentType)?.name || '未知类型'}}</div>
+                </div>
+                <img class="close_mater" src="../img/qw/close_mater.png" alt="" @click="handleCloseMatter(item)">
+              </div>
+            </div>
+          </template>
+          <div class="matter_btn_box">
+            <div class="matter_btn" @click="handleAddMatter">添加素材</div>
+          </div>
+        </div>
+        <div class="send_btn_box">
+          <div class="send_btn" @click="sendMass">一键群发</div>
+        </div>
       </div>
     </div>
   </div>
@@ -374,18 +466,15 @@
         bId: null,
         env: '',
         memberId: null,
+        showReturn: false,
+        isConfirmingBack: false, // 标记是否已确认返回
+        pushStateCount: 0, // 记录 pushState 的次数
+        initialHistoryLength: 0, // 记录初始历史记录长度
         formValidate: {
-          name: '',
-          memberList: [],
+          clients: [],
           contentText: '',
           ctOthers: [],
           matters: [],
-          isAll: 1,
-          id: null,
-          type: 1,
-          sendTime: '',
-          notifyTime: '',
-          createdTime: ''
         },
         matterImgList: [],
         matterOthersList: [],
@@ -409,8 +498,203 @@
       } else {
         this.httpUrl = 'http://test.wefanbot.com:18993'
       }
+      this.memberId = this.getQueryParam('memberId')
+      this.formValidate.clients = JSON.parse(localStorage.getItem('clientData')) || []
+      this.formValidate.matters = JSON.parse(localStorage.getItem('selectedMaters')) || []
+      
+      if (this.formValidate.matters && this.formValidate.matters.length > 0) {
+        this.formValidate.matters.forEach(item => {
+          if (item.contentType === 19) {
+            this.matterImgList.push(item)
+          } else {
+            this.matterOthersList.push(item)
+          }
+        })
+      }
+    },
+    mounted() {
+      // 监听浏览器返回按钮
+      if (window.history && window.history.pushState) {
+        // 记录初始历史记录长度
+        this.initialHistoryLength = window.history.length;
+        history.pushState(null, null, document.URL);
+        this.pushStateCount = 1; // 记录初始 pushState
+        window.addEventListener('popstate', this.handleBack, false);
+      }
+      
+      // 监听微信/企业微信返回按钮
+      if (typeof wx !== 'undefined' && wx.ready) {
+        wx.ready(() => {
+          wx.hideOptionMenu();
+        });
+      }
+      if (typeof ww !== 'undefined' && ww.ready) {
+        ww.ready(() => {
+          ww.hideOptionMenu();
+        });
+      }
+    },
+    beforeDestroy() {
+      // 移除事件监听
+      window.removeEventListener('popstate', this.handleBack, false);
     },
     methods: {
+      handleBack(e) {
+        // 如果已经确认返回,直接返回,不显示弹框
+        if (this.isConfirmingBack) {
+          return;
+        }
+        
+        // 如果弹框已经显示,阻止默认返回行为,但不重复显示弹框
+        if (this.showReturn) {
+          // 阻止返回,重新添加历史记录
+          if (window.history && window.history.pushState) {
+            history.pushState(null, null, document.URL);
+            this.pushStateCount++;
+          }
+          return;
+        }
+        
+        // 阻止默认返回行为
+        if (window.history && window.history.pushState) {
+          history.pushState(null, null, document.URL);
+          this.pushStateCount++;
+        }
+        
+        // 显示确认弹框
+        this.showReturn = true;
+      },
+      beforeClose(action, done) {
+        if (action === 'confirm') {
+          // 点击确定,设置确认返回标志
+          this.isConfirmingBack = true;
+          // 清理数据
+          localStorage.removeItem('selectedMaters');
+          localStorage.removeItem('clientData');
+
+          // 移除事件监听,避免触发弹框
+          window.removeEventListener('popstate', this.handleBack, false);
+          // 关闭弹框
+          done();
+          // 直接跳转到 qw.html 页面
+          setTimeout(() => {
+            // 获取当前 URL 的查询参数,保留必要的参数
+            const params = new URLSearchParams(window.location.search);
+            const memberId = params.get('memberId') || this.memberId;
+            const bId = params.get('bId') || this.bId;
+            const env = params.get('env') || this.env;
+            
+            // 构建跳转 URL
+            let targetUrl = '../qw.html';
+            if (memberId || bId || env) {
+              const queryParams = [];
+              if (memberId) queryParams.push(`memberId=${memberId}`);
+              if (bId) queryParams.push(`bId=${bId}`);
+              if (env) queryParams.push(`env=${env}`);
+              if (queryParams.length > 0) {
+                targetUrl += '?' + queryParams.join('&');
+              }
+            }
+            
+            window.location.href = targetUrl;
+          }, 150);
+        } else {
+          // 点击取消,关闭弹框,重新添加历史记录防止返回
+          done();
+          if (window.history && window.history.pushState) {
+            history.pushState(null, null, document.URL);
+            this.pushStateCount++;
+          }
+        }
+      },
+      // 播放暂停视频
+      playPause() {
+        const video = document.getElementById('myVideo')
+        const playButton = document.getElementById("playButton")
+        if (video.paused) {
+          video.play()
+        } else {
+          video.pause()
+        }
+      },
+      // 一键群发
+      sendMass() {
+        if (!this.formValidate.contentText && this.formValidate.ctOthers.length === 0 && this.formValidate.matters.length === 0) {
+          vant.Toast.fail('请添加要发送的内容')
+          return
+        }
+        fetch(this.httpUrl + '/scrm/v1/wxcp-workbench/p/clientSend', {
+          method: 'post',
+          body: JSON.stringify({
+            bid: this.bId,
+            memberId: this.memberId,
+            ...this.formValidate,
+          }),
+          headers: {
+            'Content-Type': 'application/json',
+          }
+        }).then(res => {
+          return res.json()
+        }).then(result => {
+          let { data, code, msg } = result
+          if (code === 1) {
+            vant.Toast.success('发送成功')
+            localStorage.removeItem('selectedMaters')
+            localStorage.removeItem('clientData')
+            window.history.back()
+          } else {
+            vant.Toast.fail(msg)
+          }
+        })
+      },
+      // 添加素材
+      handleAddMatter() {
+        window.location.href = `material.html?memberId=${this.memberId}&bId=${this.bId}&env=${this.env}`
+      },
+      handleCloseMatter(item) {
+        this.formValidate.matters = this.formValidate.matters.filter(matter => matter.id !== item.id)
+        this.matterOthersList = this.matterOthersList.filter(matter => matter.id !== item.id)
+        this.matterImgList = this.matterImgList.filter(img => img.id !== item.id)
+        this.deleteItemFromLocalStorage('selectedMaters', item.id)
+      },
+      // 从localStorage中删除指定id的对象项
+      deleteItemFromLocalStorage(storageKey, idToDelete) {
+        try {
+          // 1. 从localStorage获取数据
+          const storedData = localStorage.getItem(storageKey);
+
+          if (!storedData) {
+            console.warn(`未找到键为 "${storageKey}" 的数据`);
+            return false;
+          }
+
+          // 2. 解析JSON数据
+          const dataArray = JSON.parse(storedData);
+
+          if (!Array.isArray(dataArray)) {
+            console.error(`"${storageKey}" 中存储的不是数组`);
+            return false;
+          }
+
+          // 3. 过滤掉要删除的项
+          const filteredArray = dataArray.filter(item => item.id !== idToDelete);
+
+          // 4. 检查是否有删除
+          if (filteredArray.length === dataArray.length) {
+            console.warn(`未找到id为 "${idToDelete}" 的项`);
+            return false;
+          }
+
+          // 5. 将过滤后的数组存回localStorage
+          localStorage.setItem(storageKey, JSON.stringify(filteredArray));
+
+          console.log(`成功删除id为 "${idToDelete}" 的项`);
+          return true;
+        } catch (error) {
+          console.error('删除localStorage项时出错:', error);
+          return false;
+        }
+      },
       // 截取url中的数据
       getQueryParam(paramName) {
         // 获取当前URL的查询字符串部分  

+ 835 - 0
lottery/qw/material.html

@@ -0,0 +1,835 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+  <meta charset="UTF-8">
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="viewport"
+    content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no, viewport-fit=cover" />
+  <title>添加素材</title>
+  <!-- 引入样式文件 -->
+  <link rel="stylesheet"
+    href="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1758012584633/vant.css" />
+  <!-- 必须先引入vue,  后使用vant-ui -->
+  <link rel="stylesheet" href="../css/select-tag.css">
+  <script
+    src="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1742017957144/vue.js"></script>
+  <!-- 引入vant的组件库-->
+  <!-- 引入 Vant 的 JS 文件 -->
+  <script
+    src="https://wl-1306604067.cos.ap-guangzhou.myqcloud.com/production/ct/103548289110001/1758012748487/vant.min.js"></script>
+
+  <script src="https://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>
+  <script src="https://open.work.weixin.qq.com/wwopen/js/jwxwork-1.0.0.js"></script>
+  <!-- <script src="js/vconsole.min.js"></script>
+    <script>
+  		  var vConsole = new window.VConsole();
+  		</script> -->
+</head>
+<style>
+  body {
+    margin: 0;
+    padding: 0;
+  }
+
+  .box {
+    width: 100vw;
+    min-height: 100vh;
+    box-sizing: border-box;
+    background: #F7F9FC;
+  }
+
+  .page5 {
+    width: 100vw;
+    box-sizing: border-box;
+    padding: 15px;
+  }
+  .page_top {
+    width: 100%;
+    height: 144px;
+    position: absolute;
+    top: 0;
+    left: 0;
+    z-index: 1;
+    background: #FFFFFF;
+    border-radius: 0px 0px 15px 15px;
+  }
+  .client_top_box {
+    padding: 15px 15px 0px;
+  }
+  .search_icon {
+    width: 24px;
+    height: 24px;
+  }
+  .search_input {
+    padding: 0;
+  }
+  .search_input .van-search__content {
+    padding: 0;
+    border-radius: 19px;
+  }
+  .search_input .van-cell {
+    padding: 0;
+  }
+  .search_input .van-field__body {
+    display: flex;
+    align-items: center;
+    position: relative;
+  }
+  .search_input .van-field__body input{
+    border: none;
+    width: 100%;
+    height: 40px;
+    background: #FAFAFA;
+    padding: 10px 36px 10px 15px;
+    border-radius: 19px;
+  }
+  .search_input .van-field__body input:focus {
+    outline: none;
+  }
+  .search_input .van-field__body .van-field__right-icon{
+    position: absolute;
+    right: 12px;
+  }
+  .type_list {
+    display: flex;
+    align-items: flex-end;
+    gap: 25px;
+    padding: 15px 0px 13px 15px;
+    overflow-x: auto;
+  }
+  .type_list img {
+    width: 40px;
+    height: 40px;
+  }
+  .type_item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+  }
+  .type_title {
+    font-size: 12px;
+    line-height: 17px;
+    white-space: nowrap;
+    color: #999999;
+  }
+  .type_active {
+    color: #222222;
+    padding-top: 8px;
+  }
+  .page_content {
+    background: #FFFFFF;
+    border-radius: 15px 15px 0px 0px;
+    position: absolute;
+    top: 156px;
+    left: 0;
+    z-index: 1;
+    width: 100%;
+  }
+  .page_box {
+    padding: 15px 15px 0;
+  }
+  .path_list {
+    display: flex;
+    align-items: center;
+    flex-wrap: nowrap;
+    white-space: nowrap;
+    width: 100%;
+    overflow-x: auto;
+    margin-bottom: 25px;
+  }
+  .path_span {
+    font-size: 12px;
+    color: #999999;
+    line-height: 17px;
+  }
+  .page_foot {
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    width: 100%;
+  }
+  .filter_list {
+    display: flex;
+    flex-wrap: nowrap;
+    white-space: nowrap;
+    overflow-x: auto;
+    padding: 10px 0px 10px 15px;
+    background: #FAFAFA;
+  }
+  .filter_span {
+    font-size: 12px;
+    color: #999999;
+  }
+  .filter_count_blue {
+    color: #136DFB;
+  }
+  .foot_btn {
+    display: flex;
+    gap: 10px;
+    padding: 10px 30px;
+    background: #FFFFFF;
+    box-shadow: 0px -3px 6px 1px rgba(0,0,0,0.05);
+  }
+  .btn_reset {
+    border-radius: 20px;
+    border: 1px solid #CCCCCC;
+    font-weight: 500;
+    font-size: 14px;
+    color: #999999;
+    padding: 15px 0;
+    width: 100%;
+    text-align: center;
+  }
+  .btn_confirm {
+    border-radius: 20px;
+    background: #1677FF;
+    font-weight: 500;
+    font-size: 14px;
+    color: #FFFFFF;
+    padding: 15px 0;
+    width: 100%;
+    text-align: center;
+  }
+  .data_list {
+    gap: 25px;
+    overflow-y: auto;
+  }
+  .data_item {
+    display: flex;
+    align-items: center;
+    padding: 0 5px;
+    margin-bottom: 25px;
+  }
+  .data_item img {
+    width: 40px;
+    height: 40px;
+    margin-right: 12px;
+  }
+  .data_title {
+    width: 224px;
+    font-size: 14px;
+    color: #222222;
+    line-height: 20px;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+  .check_div {
+    width: 16px;
+    height: 16px;
+    border-radius: 50%;
+    border: 1px solid #CCCCCC;
+    box-sizing: border-box;
+    margin-left: 43px;
+  }
+  .check_div_active {
+    width: 16px;
+    height: 16px;
+    border-radius: 50%;
+    border: 1px solid #136DFB;
+    box-sizing: border-box;
+    margin-left: 43px;
+  }
+  .check_div_active:after{
+    content: "";
+    display: block;
+    width: 10px;
+    height: 10px;
+    border-radius: 50%;
+    background: #136DFB;
+    position: relative;
+    top: 2px;
+    left: 2px;
+  }
+  .path_line {
+    font-size: 12px;
+    color: #999999;
+    line-height: 17px;
+    padding: 0 10px;
+  }
+  .path_item:last-child {
+    color: #222222;
+  }
+  .filter_list1 {
+    display: flex;
+    flex-wrap: nowrap;
+    white-space: nowrap;
+    height: 146px;
+    overflow-y: auto;
+    background: #FFFFFF;
+    box-sizing: border-box;
+    padding: 5px 15px 20px;
+    gap: 15px;
+  }
+  .filter_list1 img {
+    width: 60px;
+    height: 60px;
+    font-size: 14px;
+    line-height: 20px;
+    margin-bottom: 10px;
+  }
+  .filter_img {
+    width: 100%;
+    color: #136DFB;
+    background: rgba(19, 109, 251, 0.05);
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    border-radius: 15px;
+  }
+  .filter_exc {
+    width: 100%;
+    color: #999999;
+    background: #FAFAFA;
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    border-radius: 15px;
+  }
+  .imgList_list {
+    display: flex;
+    flex-wrap: wrap;
+    overflow-x: auto;
+    padding: 5px 15px 0;
+    background: #FFFFFF;
+    height: 446px;
+    overflow-y: auto;
+    box-sizing: border-box;
+    gap: 15px;
+    align-content: flex-start;
+  }
+  .imgList_list div {
+    width: 105px;
+    height: 140px;
+    border-radius: 10px;
+    position: relative;
+  }
+  .imgList_item {
+    width: 105px;
+    height: 140px;
+    border-radius: 10px;
+    object-fit: cover;
+  }
+  .imgList_select {
+    width: 16px;
+    height: 16px;
+    position: absolute;
+    top: 5px;
+    right: 5px;
+  }
+</style>
+
+<body>
+  <div id="box" class="box">
+    <div class="page5">
+      <!-- 选择图集格式 -->
+      <van-popup v-model="showImgType" duration="0.2" round position="bottom" :close-on-click-overlay="false"
+        :style="{ width: '100%', height: '266px' }">
+        <div class="clientTag_title">
+          <div class="close_icon"></div>
+          <div>选择图集格式</div>
+          <img class="close_icon" src="../img/qw/close_icon.png" alt="" @click="showImgType = false">
+        </div>
+        <div class="filter_list1">
+          <div :class="filterIndex ? 'filter_img' : 'filter_exc'" @click="filterIndex = 1">
+            <img :src="filterIndex ? '../img/qw/img_type.png' : '../img/qw/img_type1.png'" alt="">
+            <div>选择图片</div>
+          </div>
+          <div :class="filterIndex ? 'filter_exc' : 'filter_img'" @click="filterIndex = 0">
+            <img :src="filterIndex ? '../img/qw/imgList_type1.png' : '../img/qw/imgList_type.png'" alt="">
+            <div>图集链接</div>
+          </div>
+        </div>
+        <div class="tag_footers">
+          <div class="tag_re_btn" @click="showImgType = false">取消</div>
+          <div class="tag_ok_btn" @click="handleImgType">确定</div>
+        </div>
+      </van-popup>
+      <!-- 选择图集图片 -->
+      <van-popup v-model="showImgList" duration="0.2" round position="bottom" :close-on-click-overlay="false"
+        :style="{ width: '100%', height: '566px' }">
+        <div class="clientTag_title">
+          <div class="close_icon"></div>
+          <div>选择图片</div>
+          <img class="close_icon" src="../img/qw/close_icon.png" alt="" @click="handleCancel">
+        </div>
+        <div class="imgList_list">
+          <div v-for="(item, index) in imgList" :key="item.id" @click="handleSelect(item)">
+            <img class="imgList_item" :src="item.url" alt="">
+            <img class="imgList_select" :src="item.selected ? '../img/qw/select_img.png' : '../img/qw/noselect_img.png'" alt="">
+          </div>
+        </div>
+        <div class="tag_footers">
+          <div class="tag_re_btn" @click="handleCancel">取消</div>
+          <div class="tag_ok_btn" @click="handleImgList">保存</div>
+        </div>
+      </van-popup>
+      <div class="page_top">
+        <div class="client_top_box">
+          <van-search class="search_input" placeholder="搜索客户昵称/客户群昵称" v-model="keyword" :clearable="false" left-icon=""
+            @search="handleSearch">
+            <!-- 自定义右侧图标 -->
+            <template v-slot:right-icon>
+              <img class="search_icon" src="../img/qw/search_icon.png" @click="handleSearch" />
+            </template>
+          </van-search>
+        </div>
+        <div class="type_list">
+          <div class="type_item" v-for="item in typeList" :key="item.contentType" @click="handleTypeClick(item.contentType)">
+            <img :src="item.contentType === contentType ? item.icon : item.icon1" alt="">
+            <div :class="{'type_active': item.contentType === contentType}" class="type_title">{{item.title}}</div>
+          </div>
+        </div>
+      </div>
+      <div class="page_content">
+        <div class="page_box">
+          <div class="path_list">
+            <div class="path_span">
+              <span @click="getTreeData">全部</span>
+              <span class="path_item" v-for="(item, index) in pathList" :key="index" @click="handlePathClick(item)">
+                <span class="path_line">/</span>{{item.text}}</span>
+            </div>
+          </div>
+          <div class="data_list" :style="{height: selectedMaters.length > 0 ? 'calc(100vh - 320px)' : 'calc(100vh - 213px)'}">
+            <div class="data_item" v-for="item in treeData" :key="item.id" @click="handleFolderClick(item)">
+              <img src="../img/qw/folder.png" alt="" />
+              <div class="data_title">{{item.text}}</div>
+            </div>
+            <van-checkbox-group v-model="checkedMaterIds" @change="onChangeChecked">
+              <div class="data_item" v-for="item in itemList" :key="item.id">
+                <img v-if="contentType === 0" src="../img/qw/article.png" alt="" />
+                <img v-else-if="contentType === 2" src="../img/qw/form.png" alt="" />
+                <img v-else-if="contentType === 3" src="../img/qw/file.png" alt="" />
+                <img v-else-if="contentType === 16" src="../img/qw/video.png" alt="" />
+                <img v-else-if="contentType === 17" src="../img/qw/imgList.png" alt="" />
+                <img v-else-if="contentType === 4" src="../img/qw/link.png" alt="" />
+                <div class="data_title">{{item.title || item.name}}</div>
+                <van-checkbox :name="item.id" @click="handleCheckboxClick(item)">
+                  <template #icon="props">
+                    <div :class="props.checked ? 'check_div_active' : 'check_div'"></div>
+                  </template>
+                </van-checkbox>
+              </div>
+            </van-checkbox-group>
+          </div>
+        </div>
+      </div>
+      <div class="page_foot">
+        <div class="filter_list"><span class="filter_span">全部:</span>
+          <div class="filter_span" v-html="formatcontentTypes()"></div>
+        </div>
+        <div class="foot_btn">
+          <div class="btn_reset">重置</div>
+          <div class="btn_confirm" @click="handleConfirm">确定</div>
+        </div>
+      </div>
+    </div>
+  </div>
+</body>
+<script>
+  new Vue({
+    el: '#box',
+    data() {
+      return {
+        httpUrl: '',
+        bId: null,
+        env: '',
+        memberId: null,
+        tenancyId: null,
+        keyword: '',
+        contentType: 0, // 当前选中的类型
+        typeList: [
+          {
+            contentType: 0,
+            icon: '../img/qw/article.png',
+            icon1: '../img/qw/article1.png',
+            title: '文章'
+          },
+          {
+            contentType: 2,
+            icon: '../img/qw/form.png',
+            icon1: '../img/qw/form1.png',
+            title: '表单'
+          },
+          {
+            contentType: 3,
+            icon: '../img/qw/file.png',
+            icon1: '../img/qw/file1.png',
+            title: '文件'
+          },
+          {
+            contentType: 16,
+            icon: '../img/qw/video.png',
+            icon1: '../img/qw/video1.png',
+            title: '视频'
+          },
+          {
+            contentType: 17,
+            icon: '../img/qw/imgList.png',
+            icon1: '../img/qw/imgList1.png',
+            title: '图集'
+          },
+          {
+            contentType: 4,
+            icon: '../img/qw/link.png',
+            icon1: '../img/qw/link1.png',
+            title: '外部链接'
+          },
+        ],
+        materTypeList: [
+          {
+            contentType: 0,
+            title: '文章'
+          },
+          {
+            contentType: 2,
+            title: '表单'
+          },
+          {
+            contentType: 3,
+            title: '文件'
+          },
+          {
+            contentType: 16,
+            title: '视频'
+          },
+          {
+            contentType: 17,
+            title: '图集'
+          },
+          {
+            contentType: 4,
+            title: '外部链接'
+          },
+          {
+            contentType: 19,
+            title: '图集项图片'
+          },
+        ],
+        treeData: [], // 总树数据
+        allTreeData: [], // 总树扁平化数据
+        loading: false,
+        itemList: [], // 当前树下的数据
+        checkedMaterIds: [],
+        selectedMaters: [], // 当前选中的所有素材数据
+        pathList: [], // 当前路径
+        contentTypeCount: {}, // 当前选中的类型数量
+        filterIndex: 0,
+        showImgType: false,
+        showImgList: false,
+        imgList: [], // 图集图片列表
+      }
+    },
+    created() {
+      this.bId = this.getQueryParam('bId')
+      this.env = this.getQueryParam('env')
+
+      if (!this.env || this.env === 'prod') {
+        this.httpUrl = 'https://wlapi.wefanbot.com'
+      } else {
+        this.httpUrl = 'http://test.wefanbot.com:18993'
+      }
+      this.selectedMaters = localStorage.getItem('selectedMaters') ? JSON.parse(localStorage.getItem('selectedMaters')) : []
+      this.contentTypeCount = this.countByType(this.selectedMaters)
+      if (this.getQueryParam('memberId')) {
+        // 已授权
+        this.memberId = this.getQueryParam('memberId')
+        this.getCpH5Login()
+      }
+    },
+    methods: {
+      getCpH5Login() {
+        fetch(this.httpUrl + '/scrm/v1/wxcp-workbench/p/cpH5Login', {
+          method: 'post',
+          body: JSON.stringify({
+            bid: this.bId,
+            memberId: this.memberId,
+          }),
+          headers: {
+            'Content-Type': 'application/json'
+          }
+        }).then(res => {
+          return res.json()
+        }).then(result => {
+          let { data, code, msg } = result
+          if (code === 1) {
+            this.tenancyId = data.tenancyId
+            this.getTreeData()
+          } else {
+            vant.Toast.fail(msg)
+          }
+        })
+      },
+      // 获取树数据
+      getTreeData() {
+        this.treeData = []
+        this.allTreeData = []
+        this.itemList = []
+        this.pathList = []
+        let url = ''
+        if (this.contentType === 0) {
+          url = '/scrm/v1/wxcp-chat-tool/p/ctArticleGroupTree'
+        } else if (this.contentType === 2) {
+          url = '/scrm/v1/wxcp-chat-tool/p/ctFormGroupTree'
+        } else if (this.contentType === 3) {
+          url = '/scrm/v1/wxcp-chat-tool/p/ctFileGroupTree'
+        } else if (this.contentType === 16) {
+          url = '/scrm/v1/wxcp-chat-tool/p/ctVideoGroupTree'
+        } else if (this.contentType === 17) {
+          url = '/scrm/v1/wxcp-chat-tool/p/ctImgGroupTree'
+        } else if (this.contentType === 4) {
+          url = '/scrm/v1/wxcp-chat-tool/p/ctUrlGroupTree'
+        }
+        this.loading = true
+        fetch(this.httpUrl + url, {
+          method: 'post',
+          body: JSON.stringify({
+            keyword: '',
+          }),
+          headers: {
+            'Content-Type': 'application/json',
+            'tenancyId': this.tenancyId,
+          }
+        }).then(res => {
+          return res.json()
+        }).then(result => {
+          let { data, code, msg } = result
+          if (code === 1) {
+            this.treeData = data || []
+            this.allTreeData = this.flattenTreeData(data, true)
+          } else {
+            vant.Toast.fail(msg)
+          }
+        }).finally(() => {
+          this.loading = false
+        })
+      },
+      handlePathClick (item) {
+        // 找到目标元素的索引
+        const index = this.pathList.findIndex(arr => arr.id === item.id)
+        // 如果找到了该元素
+        if (index !== -1) {
+          // 从该索引位置开始删除到数组末尾
+          this.pathList.splice(index)
+          this.handleFolderClick(item)
+        }
+      },
+      // 点击文件夹,获取子文件夹数据
+      handleFolderClick(item) {
+        this.pathList.push(item)
+        this.treeData = []
+        this.itemList = []
+        let url = ''
+        if (this.contentType === 0) {
+          url = '/scrm/v1/wxcp-chat-tool/p/pageArticle'
+        } else if (this.contentType === 2) {
+          url = '/scrm/v1/wxcp-chat-tool/p/pageForm'
+        } else if (this.contentType === 3) {
+          url = '/scrm/v1/wxcp-chat-tool/p/pageFile'
+        } else if (this.contentType === 16) {
+          url = '/scrm/v1/wxcp-chat-tool/p/pageVideo'
+        } else if (this.contentType === 17) {
+          url = '/scrm/v1/wxcp-chat-tool/p/pageImg'
+        } else if (this.contentType === 4) {
+          url = '/scrm/v1/wxcp-chat-tool/p/pageUrl'
+        }
+        fetch(this.httpUrl + url, {
+          method: 'post',
+          body: JSON.stringify({
+            groupId: item.id,
+            page: 1,
+            pageCount: 1000,
+          }),
+          headers: {
+            'Content-Type': 'application/json',
+            'tenancyId': this.tenancyId,
+          }
+        }).then(res => {
+          return res.json()
+        }).then(result => {
+          let { data, code, msg } = result
+          if (code === 1) {
+            this.itemList = data.records || []
+            if (this.itemList.length > 0) {
+              // 回显已选择的数据
+              this.checkedMaterIds = this.extractSameIds(this.itemList, this.selectedMaters)
+            }
+            if (this.allTreeData.filter(all => all.id === item.id)[0] && this.allTreeData.filter(all => all.id === item.id)[0].children) {
+              this.treeData = this.allTreeData.filter(all => all.id === item.id)[0].children
+            } else {
+              this.treeData = []
+            }
+          } else {
+            vant.Toast.fail(msg)
+          }
+        })
+      },
+      extractSameIds(itemList, selectedMaters) {
+        const arr2Ids = new Set(selectedMaters.map(item => item.id))
+        return itemList.filter(item => arr2Ids.has(item.id))
+          .map(item => item.id)
+      },
+      // 点击类型,获取树数据
+      handleTypeClick(type) {        // 点击谁,就把谁的图标变成item.icon,其余的变成icon1
+        this.contentType = type
+        this.getTreeData()
+      },
+      onChangeChecked(value) {
+        // 更新当前页面的勾选状态
+        // this.checkedMaterIds = value
+        
+        // 当前页面选中的,并标记类型
+        const currentSelected = this.itemList
+          .filter(item => this.checkedMaterIds.includes(item.id))
+          .map(item => ({
+            ...item,
+            contentType: this.contentType // 标记素材类型
+          }))
+        
+        // 先删除当前页面未选中的数据
+        const currentPageIds = new Set(this.itemList.map(item => item.id))
+        this.selectedMaters = this.selectedMaters.filter(item => {
+          // 如果是当前页面的数据,但未选中,则删除
+          if (currentPageIds.has(item.id)) {
+            return this.checkedMaterIds.includes(item.id)
+          }
+          // 如果不是当前页面的数据,保留
+          return true
+        })
+        
+        // 合并当前选中的和之前选中的,基于 id 去重(当前选中的优先,使用最新的类型)
+        const existingIdsMap = new Map()
+        this.selectedMaters.forEach(item => {
+          existingIdsMap.set(item.id, item)
+        })
+        
+        // 更新或添加当前选中的数据(优先使用当前页面的数据和类型)
+        currentSelected.forEach(item => {
+          existingIdsMap.set(item.id, item)
+        })
+        
+        this.selectedMaters = Array.from(existingIdsMap.values())
+        
+        console.log('当前选中的所有素材数据: ', this.selectedMaters)
+        this.contentTypeCount = this.countByType(this.selectedMaters)
+      },
+      handleSearch() {
+      },
+      // 统计数组中每个类型的数量
+      countByType(arr) {
+        return arr.reduce((countMap, item) => {
+          const type = item.contentType;
+          countMap[type] = (countMap[type] || 0) + 1;
+          return countMap;
+        }, {});
+      },
+      // 格式化选中类型的显示文本,格式:文章 x1、文件 x2(数字部分为蓝色)
+      formatcontentTypes() {
+        const typeCountArr = []
+        // 遍历 typeList 的顺序,确保显示顺序一致
+        this.materTypeList.forEach(typeItem => {
+          const count = this.contentTypeCount[typeItem.contentType] || 0
+          if (count > 0) {
+            // 将数字部分用span包裹,添加蓝色样式类
+            typeCountArr.push(`${typeItem.title} <span class="filter_count_blue">x${count}</span>`)
+          }
+        })
+        return typeCountArr.length > 0 ? typeCountArr.join('、') : ''
+      },
+      // 递归扁平化树数据
+      flattenTreeData(data, keepChildren = true) {
+        const result = []
+        function traverse(nodes) {
+          if (!Array.isArray(nodes)) return
+          nodes.forEach(node => {
+            const flatNode = { ...node }
+            // 如果不保留children,删除该属性
+            if (!keepChildren) {
+              delete flatNode.children
+            }
+            result.push(flatNode)
+            // 递归遍历子节点
+            if (node.children && node.children.length > 0) {
+              traverse(node.children)
+            }
+          })
+        }
+        traverse(data)
+        return result
+      },
+      handleConfirm() {
+        if (this.selectedMaters.length === 0) {
+          vant.Toast.fail('请选择素材')
+          return
+        }
+        localStorage.setItem('selectedMaters', JSON.stringify(this.selectedMaters))
+        window.history.back()
+      },
+      handleCheckboxClick (item) {
+        console.log(item)
+        if (this.contentType === 17) {
+          if (this.selectedMaters.some(s => s.id === item.id)) {
+            this.showImgType = true
+          }
+        }
+      },
+      handleImgType () {
+        if (this.filterIndex === 0) {
+          this.showImgType = false
+        } else {
+          this.showImgType = false
+          this.imgList = this.selectedMaters[this.selectedMaters.length-1].imgList
+          this.selectedMaters.pop() // 删除最后一条数据(点击自动添加进去的图集项链接)
+          this.selectedMaters.forEach(s => {
+            this.imgList.forEach(item => {
+              if (s.id === item.id) {
+                this.$set(item, 'selected', true)
+              } else {
+                this.$set(item, 'selected', false)
+              }
+            })
+          })
+          this.showImgList = true
+        }
+      },
+      handleSelect(data) {
+        this.imgList.forEach(item => {
+          if (item.id === data.id) {
+            this.$set(item, 'selected', !item.selected)
+          }
+        })
+      },
+      handleCancel () {
+        this.showImgList = false
+      },
+      handleImgList () {
+        this.imgList.forEach(item => {
+          if (item.selected) {
+            this.selectedMaters.push({
+              contentType: 19,
+              ...item,
+            })
+          }
+        })
+        this.showImgList = false
+        this.contentTypeCount = this.countByType(this.selectedMaters)
+      },
+      // 截取url中的数据
+      getQueryParam(paramName) {
+        // 获取当前URL的查询字符串部分  
+        const queryString = window.location.search;
+        // 创建一个URLSearchParams对象  
+        const urlParams = new URLSearchParams(queryString);
+        // 返回指定参数的值,如果不存在则返回null  
+        return urlParams.get(paramName);
+      },
+    }
+  })   
+</script>
+
+</html>

+ 515 - 61
lottery/qw/taskDetail.html

@@ -120,12 +120,6 @@
     background: linear-gradient(90deg, rgba(217,219,227,0) 0%, #D9DBE3 49%, rgba(217,219,227,0) 100%);
   }
 
-  .task_item_des {
-    display: flex;
-    align-items: flex-start;
-    padding: 0 15px 10px;
-  }
-
   .task_des_title {
     font-size: 14px;
     color: #222222;
@@ -229,80 +223,424 @@
     color: #136DFB;
     line-height: 20px;
   }
+
+  .followup_list {
+    width: 100%;
+    padding: 0 10px 20px;
+    background: #FFFFFF;
+    border-radius: 5px;
+    box-sizing: border-box;
+  }
+  .follow_time {
+    display: flex;
+    align-items: center;
+    font-weight: 400;
+    font-size: 14px;
+    color: #222222;
+    margin-bottom: 15px;
+  }
+  .time_dot {
+    background: #E6F0FF;
+    padding: 3px;
+    border-radius: 50%;
+    margin-right: 10px;
+    box-sizing: border-box;
+  }
+  .dot {
+    width: 6px;
+    height: 6px;
+    background: #1677FF;
+    border-radius: 50%;
+  }
+  .follow_item {
+    padding-right: 10px;
+    padding-left: 15px;
+    padding-bottom: 5px;
+  }
+  .follow_padding {
+    padding-top: 15px; 
+    padding-bottom: 5px;
+    background: #F7F9FC;
+    border-radius: 5px;
+  }
+  .follow_item_time {
+    font-weight: 500;
+    font-size: 14px;
+    color: #222222;
+    display: flex;
+    align-items: center;
+    margin-bottom: 3px;
+  }
+  .item_time {
+    font-weight: 400;
+    font-size: 12px;
+    color: #999999;
+    padding-right: 10px;
+  }
+  .item_box {
+    display: flex;
+    align-items: flex-end;
+  }
+  .follow_item_line {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding-left: 13px;
+    padding-right: 21px;
+  }
+  .line_dot {
+    width: 6px;
+    height: 6px;
+    background: #1677FF;
+    border-radius: 50%;
+    margin-bottom: 5px;
+  }
+  .line {
+      width: 1px;
+      height: 66px;
+      border-left: 1px dashed #1677FF;
+    }
+  .item_content {
+    background: #FFFFFF;
+    border-radius: 5px;
+    padding: 10px;
+    font-weight: 400;
+    font-size: 12px;
+    color: #222222;
+    width: 100%;
+    height: 100%;
+    display: flex;
+    align-items: center;
+  }
+  .item_content img {
+    width: 37px;
+    height: 37px;
+    margin-right: 10px;
+  }
+  .item_tip {
+    font-size: 12px;
+    color: #222222;
+    line-height: 18px;
+    max-width: 120px;
+    display: -webkit-box;
+    -webkit-line-clamp: 2;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+  .item_tip2 {
+    font-size: 12px;
+    color: #222222;
+    line-height: 17px;
+    gap: 5px;
+    max-width: 120px;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+  .foot_box {
+    display: flex;
+    justify-content: center;
+    padding: 0 25px;
+    margin-bottom: 10px;
+  }
+  .foot_btn {
+    width: 100%;
+    height: 50px;
+    background: #1677FF;
+    border-radius: 20px;
+    font-weight: 500;
+    font-size: 14px;
+    color: #FFFFFF;
+    line-height: 50px;
+    text-align: center;
+  }
+
+  /* 客户SOP任务详情 */
+  
+  .top_tip {
+    background: #afdefd;
+    display: flex;
+    justify-content: center;
+    color: #FFF;
+    font-size: 14px;
+    padding: 6px 0;
+  }
+
+  .logo {
+    width: 20px;
+    height: 20px;
+    margin-right: 10px;
+  }
+
+  .content {
+    padding: 20px;
+    margin-bottom: 10px;
+    background: #FFF;
+  }
+
+  .icon_logo {
+    width: 20px;
+    vertical-align: bottom;
+  }
+
+  .sop_content {
+    background: #f1f9ff;
+    padding: 10px 20px;
+    border-radius: 10px;
+    font-size: 14px;
+  }
+
+  .font {
+    color: #1890ff;
+  }
+
+  .send_title {
+    padding: 20px;
+    background: #FFF;
+    border-bottom: 1px solid #d2d3d4;
+  }
+
+  .send_content {
+    padding: 20px;
+    background: #FFF;
+    margin-bottom: 10px;
+  }
+
+  .send_item {
+    background: #F8F8F8;
+    padding: 20px;
+    margin-bottom: 10px;
+    border-radius: 10px;
+    font-size: 14px;
+  }
+
+  .send_logo {
+    width: 100px;
+    border-radius: 10px;
+  }
+
+  .follow_client {
+    display: flex;
+    align-items: center;
+    justify-content: space-between;
+    padding: 20px;
+    font-size: 14px;
+    background: #FFF;
+  }
+
+  .client_msg {
+    display: flex;
+    flex-direction: column;
+  }
+
+  .avatar {
+    width: 60px;
+    height: 60px;
+    border-radius: 10px;
+  }
+
+  .follow_btn {
+    background: #f1f9ff;
+    color: #1890ff;
+    padding: 5px;
+    border-radius: 4px;
+    font-size: 12px;
+  }
+  /* 客户SOP任务详情结束 */
 </style>
 
 <body>
   <div id="box" class="box">
     <div class="page5">
       <!-- 任务详情 -->
-      <div class="task_item">
-        <div class="task_item_title">
-          <img :src="taskData.clientAvatar || '../img/avatar.png'" alt="">
-          <div class="task_item_text">
-            <div>{{taskData.clientName}}</div>
-            <div class="task_item_time">任务时间: {{formatDate(taskData.createdTime)}}</div>
+      <div v-if="type === 'CLIENT_SOP'">
+        <div class="content">
+          <div class="sop_content">
+            <img class="icon_logo" :src="taskData.clientAvatar || './img/avatar.png'"></img>
+            <span>「{{taskData.clientName}}」在「{{timeFormat(taskData.triggerTime, 'yyyy/MM/dd hh:mm:ss')}}」</span>
+            <span class="font" v-if="taskData.triggerType">添加好友后触发客户SOP任务,</span>
+            <span class="font" v-else>打上标签后触发客户SOP任务,</span>
+            <span>快去跟进吧</span>
           </div>
         </div>
-        <div class="task_item_line"></div>
-        <div class="client_tag">
-          <div class="client_tag_title">客户标签:</div>
-          <span v-for="(item, index) in taskData.clientTags" :key="index">{{item}}</span>
+        <div class="send_title">推送内容</div>
+        <div class="send_content" v-if="taskData.contentText">
+          <div class="send_item">
+            <div class="sop_c">
+              <span>文字:{{ taskData.contentText }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="send_content" v-if="taskData.matters && taskData.matters.length > 0">
+          <div class="send_item" v-for="(m, index) in taskData.matters" :key="index">
+            <div v-if="m.contentType === 0" class="sop_c">
+              <span>文章:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 31" class="sop_c">
+              <span>海报:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 2" class="sop_c">
+              <span>表单:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 3" class="sop_c">
+              <span>文件:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 15" class="sop_c">
+              <span>视频:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 16" class="sop_c">
+              <span>视频链接:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 51" class="sop_c">
+              <span>视频号:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 33" class="sop_c">
+              <span>小程序:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 17" class="sop_c">
+              <span>图集:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 19" class="sop_c">
+              <span>图集项图片:{{ m.title }}</span>
+            </div>
+            <div v-if="m.contentType === 4" class="sop_c">
+              <span>外部链接:{{ m.title }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="send_content" v-if="taskData.ctOthers && taskData.ctOthers.length > 0">
+          <div class="send_item" v-for="(item, index) in taskData.ctOthers" :key="index">
+            <div class="sop_c" v-if="item.matterType === 0">
+              <span>{{ item.matterText }}</span>
+            </div>
+            <div class="sop_c" v-if="item.matterType === 1">
+              <img class="send_logo" :src="item.imgUrl" />
+            </div>
+            <div class="sop_c" v-if="item.matterType === 5">
+              <span>{{ item.linkTitle }}</span>
+            </div>
+            <div class="sop_c" v-if="item.matterType === 2">
+              <span>自定义视频</span>
+            </div>
+            <div class="sop_c" v-if="item.matterType === 4">
+              <span>{{ item.fileName }}</span>
+            </div>
+          </div>
+        </div>
+        <div class="send_title">跟进客户</div>
+        <div class="follow_client">
+          <img class="avatar" :src="taskData.clientAvatar || './img/avatar.png'"></img>
+          <div class="client_msg">
+            <span class="font">{{taskData.clientName}}</span>
+            <span>添加时间:{{timeFormat(taskData.addTime, 'yyyy/MM/dd hh:mm:ss')}}</span>
+          </div>
+          <div class="follow_btn" @click="toFollow">去跟进</div>
         </div>
-        <!-- <div class="task_item_des">
-          <div class="task_des_title task_ai">AI分析:</div>
-          <div class="task_des task_ai_des">{{taskData.analysisReason}}</div>
-        </div> -->
       </div>
-      <div class="task_detail" v-if="type === 'SELL_PROCESS'">
-        <div class="task_des_title">{{taskData.sellProcessName}}</div>
-        <div class="task_detail_con">
-          <div class="task_detail_con_item">
-            <div>进入当前阶段:</div>
-            <div class="task_detail_con_data">{{taskData.sellProcessName}}</div>
+      <div v-else>
+        <div class="task_item">
+          <div class="task_item_title">
+            <img :src="taskData.clientAvatar || '../img/avatar.png'" alt="">
+            <div class="task_item_text">
+              <div>{{taskData.clientName}}</div>
+              <div class="task_item_time">任务时间: {{formatDate(taskData.createdTime)}}</div>
+            </div>
           </div>
-          <div class="task_detail_con_item">
-            <div>停留时间:</div>
-            <div class="task_detail_con_data">{{taskData.lastContactTime}}</div>
+          <div class="task_item_line"></div>
+          <div class="client_tag">
+            <div class="client_tag_title">客户标签:</div>
+            <span v-for="(item, index) in taskData.clientTags" :key="index">{{item}}</span>
           </div>
-          <div class="task_detail_con_item">
-            <div>上次联系时间:</div>
-            <div class="task_detail_con_data">{{taskData.lastContactTime}}</div>
+        </div>
+        <div class="task_detail" v-if="type === 'SELL_PROCESS'">
+          <div class="task_des_title">{{taskData.sellProcessName}}</div>
+          <div class="task_detail_con">
+            <div class="task_detail_con_item">
+              <div>进入当前阶段:</div>
+              <div class="task_detail_con_data">{{taskData.sellProcessName}}</div>
+            </div>
+            <div class="task_detail_con_item">
+              <div>停留时间:</div>
+              <div class="task_detail_con_data">{{formatTimeDiff(taskData.processTime).simpleFormat()}}</div>
+            </div>
+            <div class="task_detail_con_item">
+              <div>上次联系时间:</div>
+              <div class="task_detail_con_data">{{taskData.lastContactTime}}</div>
+            </div>
+            <div class="task_detail_con_item">
+              <div>未联系时间:</div>
+              <div class="task_detail_con_data">{{formatTimeDiff(taskData.lastContactTime).simpleFormat()}}</div>
+            </div>
           </div>
-          <div class="task_detail_con_item">
-            <div>未联系时间:</div>
-            <div class="task_detail_con_data">{{taskData.lastContactTime}}</div>
+        </div>
+        <div v-if="type === 'INTENTION_SESSION' && taskData.intentionGroupList && taskData.intentionGroupList.length > 0">
+          <div class="task_detail" v-for="(item, index) in taskData.intentionGroupList" :key="index">
+            <div class="inten_title">{{item.nameList[0]}}</div>
+            <div class="ai_title">AI分析:</div>
+            <div class="task_ai_des">{{item.reason}}</div>
           </div>
         </div>
-      </div>
-      <div v-if="type === 'INTENTION_SESSION' && taskData.intentionGroupList && taskData.intentionGroupList.length > 0">
-        <div class="task_detail" v-for="(item, index) in taskData.intentionGroupList" :key="index">
-          <div class="inten_title">{{item.nameList[0]}}</div>
+        <div class="task_detail" v-if="type === 'SESSION_CLUE'">
+          <div class="clue_data">
+            <div class="clue_data_item">
+              <div class="ai_title">添加企微时间:</div>
+              <div class="clue_data_time">{{timeFormat(taskData.createdTime)}}</div>
+            </div>
+            <div class="clue_data_item">
+              <div class="ai_title">线索分类:</div>
+              <div class="clue_data_time">
+                <span>{{taskData.category}}</span>
+                <span style="color: #CCCCCC;">—</span>
+                <span style="color: #7D97BF;">{{taskData.subCategory}}</span></div>
+            </div>
+          </div>
+          <div class="ai_title">线索依据:</div>
+          <div class="task_ai_des" style="margin-bottom: 20px;">{{taskData.clueMsg}}</div>
           <div class="ai_title">AI分析:</div>
-          <div class="task_ai_des">{{item.reason}}</div>
+          <div class="task_ai_des">{{taskData.analysisReason}}</div>
         </div>
-      </div>
-      <div class="task_detail" v-if="type === 'SESSION_CLUE'">
-        <div class="clue_data">
-          <div class="clue_data_item">
-            <div class="ai_title">添加企微时间:</div>
-            <div class="clue_data_time">{{timeFormat(taskData.createdTime)}}</div>
-          </div>
-          <div class="clue_data_item">
-            <div class="ai_title">线索分类:</div>
-            <div class="clue_data_time">
-              <span>{{taskData.category}}</span>
-              <span style="color: #CCCCCC;">—</span>
-              <span style="color: #7D97BF;">{{taskData.subCategory}}</span></div>
+        <div class="task_detail" v-if="type === 'SENTIMENT_SESSION'">
+          <div class="ai_title">AI情绪分析:</div>
+          <div class="task_ai_des">{{taskData.sentimentReason}}</div>
+        </div>
+        <div class="task_detail" style="padding: 20px 10px;" v-if="type === 'CLIENT_LOG'">
+          <div class="followup_list" v-for="(item, key) in taskData.clientLogDetails" :key="key">
+            <div class="follow_time">
+              <div class="time_dot">
+                <div class="dot"></div>
+              </div>
+              <span>{{item.createdTime}}</span>
+            </div>
+            <div class="follow_padding">
+              <div class="follow_item">
+                <div class="follow_item_time">
+                  <span class="item_time">{{timeFormat(item.createdTime, 'hh:mm')}}</span>
+                  <span v-if="item.bizCode === 'READ'">客户动态</span>
+                  <span v-else-if="item.bizCode === 'SUBMIT'">留资</span>
+                </div>
+                <div class="item_box">
+                  <div class="follow_item_line">
+                    <div class="line_dot"></div>
+                    <div class="line"></div>
+                  </div>
+                  <div class="item_content">
+                    <img :src="item.cover || '../img/qw/mem_icon.png'" alt="">
+                    <div class="item_tip" v-if="item.bizCode === 'READ'">{{item.title}}</div>
+                    <div class="item_tip2" v-else-if="item.bizCode === 'SUBMIT'">
+                      <div>{{item.name}}</div>
+                      <div>{{item.phone}}</div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+            </div>
           </div>
         </div>
-        <div class="ai_title">线索依据:</div>
-        <div class="task_ai_des" style="margin-bottom: 20px;">{{taskData.analysisReason}}</div>
-        <div class="ai_title">AI分析:</div>
-        <div class="task_ai_des">{{taskData.analysisReason}}</div>
       </div>
-      <div class="task_detail" v-if="type === 'SENTIMENT_SESSION'">
-        <div class="ai_title">AI情绪分析:</div>
-        <div class="task_ai_des">{{taskData.sentimentReason}}</div>
+      <div class="foot_box">
+        <div class="foot_btn" @click="toFollow">去跟进</div>
       </div>
     </div>
   </div>
@@ -326,12 +664,13 @@
       this.taskId = this.getQueryParam('taskId')
       this.type = this.getQueryParam('type')
       if (!this.env || this.env === 'prod') {
-        this.httpUrl = 'http://test.wefanbot.com:18993'
+        this.httpUrl = 'https://wlapi.wefanbot.com'
       } else {
         this.httpUrl = 'http://test.wefanbot.com:18993'
       }
       this.toRead()
       this.getDetail()
+      this.getQyWxSign()
     },
     methods: {
       toRead() {
@@ -360,6 +699,12 @@
         } else if (this.type === 'SENTIMENT_SESSION') {
           // 投诉
           url = `/scrm/v1/wxc-sentiment-session/p/member/todo-task-info?todoTaskId=${this.taskId}`
+        } else if (this.type === 'CLIENT_LOG') {
+          // 客户动态
+          url = `/scrm/v1/client-log/p/member/todo-task-info?todoTaskId=${this.taskId}`
+        } else if (this.type === 'CLIENT_SOP') {
+          // 客户SOP
+          url = `/scrm/v1/wxcp-sop/p/h5SopHistory?historyId=${this.taskId}`
         }
         fetch(this.httpUrl + url)
           .then(res => {
@@ -430,6 +775,115 @@
 
         return `${year}-${month}-${day}`
       },
+      formatTimeDiff(targetTimeStr) {
+        const pageEnterTime = new Date();
+        // 解析目标时间
+        const targetTime = new Date(targetTimeStr);
+
+        // 计算时间差(毫秒)
+        const timeDiff = Math.abs(pageEnterTime - targetTime);
+
+        // 计算天、时、分
+        const days = Math.floor(timeDiff / (1000 * 60 * 60 * 24));
+        const hours = Math.floor((timeDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+        const minutes = Math.floor((timeDiff % (1000 * 60 * 60)) / (1000 * 60));
+
+
+        return {
+          days,
+          hours,
+          minutes,
+          totalDiff: timeDiff,
+          format: () => {
+            return `${days}天${hours}时${minutes}分`;
+          },
+          simpleFormat: () => `${days}天${hours}时${minutes}分`
+        };
+      },
+      toFollow() {
+        if (!this.taskData.clientExternalUserId && !this.taskData.externalUserid) {
+          vant.Toast.fail('无效')
+          return
+        }
+        wx.openEnterpriseChat({
+          externalUserIds: this.taskData.clientExternalUserId || this.taskData.externalUserid, // 参与会话的外部联系人列表,格式为userId1;userId2;…,用分号隔开。
+          groupName: "",  // 会话名称。单聊时该参数传入空字符串""即可。
+          chatId: "",
+          success: function (res) {
+            var chatId = res.chatId; //返回当前群聊ID,仅当使用agentConfig注入该接口权限时才返回chatId
+            // 回调
+            console.log('成功', res);
+
+          },
+          fail: function (res) {
+            // 回调
+            if (res.errMsg.indexOf('function not exist') > -1) {
+              alert('版本过低请升级');
+            }
+            console.log('失败', res);
+          },
+          complete: function (res) {
+            // 回调
+            console.log('完成', res);
+          }
+        });
+
+      },
+      getQyWxSign() {
+        fetch(this.httpUrl + '/scrm/v1/wxcp-corp/p/getAgentConfig', {
+          method: 'post',
+          body: JSON.stringify({
+            bid: this.bId,
+            url: window.location.href,
+          }),
+          headers: {
+            'Content-Type': 'application/json'
+          }
+        }).then(res => {
+          return res.json()
+        }).then(result => {
+          let { data, code, msg } = result
+          if (code === 1) {
+            wx.config({
+              beta: true,
+              debug: false,
+              appId: data.corpid, // 必填,企业号的唯一标识,此处填写企业号corpid
+              timestamp: data.timestamp, // 必填,生成签名的时间戳
+              nonceStr: data.nonceStr, // 必填,生成签名的随机串
+              signature: data.agentSignature, // 必填,签名,见附录1
+              jsApiList: ['checkJsApi', 'openEnterpriseChat'] // 必填,需要使用的JS接口列表
+            })
+            wx.agentConfig({
+              corpid: data.corpid,
+              agentid: data.agentId,
+              timestamp: data.timestamp, // 必填,生成签名的时间戳
+              nonceStr: data.nonceStr, // 必填,生成签名的随机串
+              signature: data.agentSignature, // 必填,签名,见附录1
+              jsApiList: ['openEnterpriseChat'], // 必填,需要使用的JS接口列表
+              success: function (res) {
+                // 回调
+                console.log('agentConfig成功', res);
+
+              },
+              fail: function (res) {
+                if (res.errMsg.indexOf('function not exist') > -1) {
+                  alert('版本过低请升级');
+                }
+              },
+              complete: function (res) {
+                // 回调
+                console.log('complete', res);
+              }
+            });
+
+          } else {
+            this.$message({
+              message: msg,
+              type: 'warning'
+            })
+          }
+        })
+      },
       // 截取url中的数据
       getQueryParam(paramName) {
         // 获取当前URL的查询字符串部分  

+ 14 - 3
lottery/qw/toDoTask.html

@@ -60,6 +60,15 @@
     display: flex;
     align-items: center;
     padding: 15px 15px 0;
+    position: relative;
+  }
+  .created_time {
+    font-size: 12px;
+    color: #999999;
+    line-height: 17px;
+    position: absolute;
+    top: 10px;
+    right: 10px;
   }
   .task_item_title img {
     width: 48px;
@@ -162,11 +171,13 @@
                 <span v-for="(tag, tagIndex) in item.businessTags" :key="tagIndex">{{ tag }}</span>
               </div>
             </div>
+            <div class="created_time">{{ item.createdTime }}</div>
           </div>
           <div class="task_item_line"></div>
           <div class="task_item_des">
             <div class="task_des_title">任务描述:</div>
-            <div class="task_des">{{ item.taskDesc }}</div>
+            <div class="task_des" v-if="taskType === 'CLIENT_LOG'" v-html="item.taskDesc"></div>
+            <div class="task_des" v-else>{{item.taskDesc}}</div>
           </div>
           <div class="task_btn_box">
             <div class="task_btn">查看详情</div>
@@ -198,7 +209,7 @@
       this.env = this.getQueryParam('env')
 
       if (!this.env || this.env === 'prod') {
-        this.httpUrl = 'http://test.wefanbot.com:18993'
+        this.httpUrl = 'https://wlapi.wefanbot.com'
       } else {
         this.httpUrl = 'http://test.wefanbot.com:18993'
       }
@@ -223,7 +234,7 @@
           })
       },
       handleTaskDetail(item) {
-        window.location.href = `taskDetail.html?taskId=${item.todoTaskId}&type=${item.type}&env=${this.env}`
+        window.location.href = `taskDetail.html?taskId=${item.todoTaskId}&type=${item.type}&env=${this.env}&bId=${this.bId}`
       },
       // 截取url中的数据
       getQueryParam(paramName) {

+ 8 - 3
lottery/qw/toDoTaskList.html

@@ -126,6 +126,7 @@
             <img class="task_item_img" v-if="item.type === 'MONITORED_MESSAGE'" src="../img/qw/cs.png" alt="">
             <img class="task_item_img" v-if="item.type === 'SENTIMENT_SESSION'" src="../img/qw/ts.png" alt="">
             <img class="task_item_img" v-if="item.type === 'CLIENT_LOG'" src="../img/qw/khdt.png" alt="">
+            <img class="task_item_img" v-if="item.type === 'CLIENT_SOP'" src="../img/qw/sop.png" alt="">
             <div class="task_item_title">
               <div class="task_item_text">{{taskType.find(t => t.type === item.type)?.name || '未知类型'}}</div>
               <div class="task_item_num">未处理任务:<span :style="item.unreadCount > 0 ? 'color: #FF4141;' : ''">{{item.unreadCount}}</span></div>
@@ -147,7 +148,7 @@
     data() {
       return {
         httpUrl: '',
-        bId: null,
+        bId: '',
         env: '',
         memberId: null,
         taskList: [],
@@ -174,6 +175,10 @@
           {
             type: 'CLIENT_LOG',
             name: '客户动态'
+          },
+          {
+            type: 'CLIENT_SOP',
+            name: 'SOP'
           }
         ]
       }
@@ -183,7 +188,7 @@
       this.env = this.getQueryParam('env')
 
       if (!this.env || this.env === 'prod') {
-        this.httpUrl = 'http://test.wefanbot.com:18993'
+        this.httpUrl = 'https://wlapi.wefanbot.com'
       } else {
         this.httpUrl = 'http://test.wefanbot.com:18993'
       }
@@ -207,7 +212,7 @@
           })
       },
       handleTaskDetail(item) {
-        window.location.href = `toDoTask.html?memberId=${this.memberId}&type=${item.type}&env=${this.env}`
+        window.location.href = `toDoTask.html?memberId=${this.memberId}&type=${item.type}&env=${this.env}&bId=${this.bId}`
       },
       // 截取url中的数据
       getQueryParam(paramName) {

Некоторые файлы не были показаны из-за большого количества измененных файлов