Browse Source

客户转发迁移

xdd 2 months ago
parent
commit
12c0523c87

+ 6 - 2
src/views/qw/groupMsg/index.vue

@@ -152,6 +152,7 @@
 
       <!-- 添加客户群发记录主对话框 -->
       <el-dialog v-loading="loading" :title="title" :visible.sync="open" width="1500px" @close="closeData" style="font-weight: bold;font-size: 20px" append-to-body>
+
         <div style="display: flex;flex-wrap: wrap">
           <div style="width: 1000px;">
             <el-form ref="form" :model="form" :rules="rules" label-width="160px" size="medium" style="font-size: 20px" >
@@ -578,12 +579,13 @@ import source from 'echarts/src/data/Source'
 import { getMyQwUserList,getMyQwCompanyList } from "@/api/qw/user";
 import {allListTagGroup} from "@/api/qw/tagGroup";
 import {listTag, searchTags} from "@/api/qw/tag";
-
+import  MaterialQw  from "@/views/qw/materialQw/index.vue";
 export default {
   name: "GroupMsg",
-  components: { CustomerGroupDetails, GroupChat, GroupMsgUser,ImageUpload,qwUserList,WelComeAdd},
+  components: { CustomerGroupDetails, GroupChat, GroupMsgUser,ImageUpload,qwUserList,WelComeAdd,MaterialQw},
   data() {
     return {
+      imageArr:[],
       // 遮罩层
       loading: true,
       // 导出遮罩层
@@ -1098,6 +1100,8 @@ export default {
     //选择群发的企业成员账号
     handlelistUser(){
 
+      console.log("imageArr",this.imageArr)
+
       this.listUser.title="选择企业成员"
       this.listUser.open=true;
 

+ 434 - 0
src/views/qw/materialQw/index.vue

@@ -0,0 +1,434 @@
+<template>
+  <div v-if="type == 'image'">
+    <ul v-for="(item,index) in value" :key="index" class="el-upload-list el-upload-list--picture-card">
+      <li tabindex="0" class="el-upload-list__item is-ready" :style="'width: '+width+'px;height: '+height+'px'">
+        <div>
+
+          <img :src="item.materialUrl" alt="" class="el-upload-list__item-thumbnail">
+
+          <span class="el-upload-list__item-actions">
+            <span v-if="index != 0" class="el-upload-list__item-preview" @click="moveMaterial(index,'up')">
+              <i class="el-icon-back" />
+            </span>
+            <span class="el-upload-list__item-preview" @click="zoomMaterial(index)">
+              <i class="el-icon-view" />
+            </span>
+            <span class="el-upload-list__item-delete" @click="deleteMaterial(index)">
+              <i class="el-icon-delete" />
+            </span>
+            <span v-if="index != value.length-1" class="el-upload-list__item-preview" @click="moveMaterial(index,'down')">
+              <i class="el-icon-right" />
+            </span>
+          </span>
+        </div>
+      </li>
+    </ul>
+    <div v-if="num > value.length" tabindex="0" class="el-upload el-upload--picture-card" :style="'width: '+width+'px;height: '+height+'px;'+'line-height:'+height+'px;'" @click="toSeleteMaterial">
+      <i class="el-icon-plus" />
+    </div>
+    <!-- 查看 -->
+    <el-dialog
+      append-to-body
+      :visible.sync="dialogVisible"
+      width="35%"
+    >
+      <img :src="materialUrl" alt="" style="width: 100%">
+    </el-dialog>
+    <!-- 素材列表 -->
+    <el-dialog
+      title="图片素材库"
+      append-to-body
+      :visible.sync="listDialogVisible"
+      width="70%"
+    >
+      <el-container>
+        <el-aside width="unset">
+          <div style="margin-bottom: 10px">
+            <el-button
+              class="el-icon-plus"
+              size="small"
+              @click="materialgroupAdd()"
+            >
+              添加分组
+            </el-button>
+          </div>
+          <div class="group-list">
+            <div class="group-item" v-for="(group) in materialGroupList">
+                <el-button  @click="selectGroup(group)" type="primary" plain >{{group.materialGroupName}}</el-button>
+            </div>
+          </div>
+        </el-aside>
+        <el-main>
+          <el-card>
+            <div slot="header">
+              <el-row>
+                <el-col :span="12">
+                  <span>{{ materialGroup.materialGroupName }}</span>
+                  <span v-if="materialGroup.materialGroupId >0">
+                    <el-button size="small" type="text" class="el-icon-edit" style="margin-left: 10px;" @click="materialgroupEdit(materialGroup)">重命名</el-button>
+                    <el-button size="small" type="text" class="el-icon-delete" style="margin-left: 10px;color: red" @click="materialgroupDelete(materialGroup)">删除</el-button>
+                  </span>
+                </el-col>
+                <el-col :span="12" style="text-align: right;">
+                  <el-upload
+                    :action="uploadUrl"
+                    :file-list="[]"
+                    :on-progress="handleProgress"
+                    :before-upload="beforeUpload"
+                    :on-success="handleSuccess"
+                    :data="{type: 1}"
+                    multiple
+                  >
+                    <el-button size="small" type="primary">上传图片</el-button>
+                  </el-upload>
+                </el-col>
+              </el-row>
+            </div>
+            <div v-loading="tableLoading">
+              <el-alert
+                v-if="tableData.length <= 0"
+                title="暂无数据"
+                type="info"
+                :closable="false"
+                center
+                show-icon
+              />
+              <el-row :gutter="5">
+                <el-checkbox-group v-model="urls" :max="num - value.length" >
+                  <el-col v-for="(item,index) in tableData" :key="index" :span="4">
+                    <el-card :body-style="{ padding: '5px' }">
+                      <el-image
+                        style="width: 100%;height: 100px"
+                        :src="item.materialUrl"
+                        fit="contain"
+                        :preview-src-list="[item.materialUrl]"
+                        :z-index="9999"
+                      />
+                      <div>
+                        <el-checkbox class="material-name" :label="item">
+                          选择
+                        </el-checkbox>
+                        <el-row>
+                          <el-col :span="24" class="col-do">
+                            <el-button type="text" size="medium" @click="materialDel(item)">删除</el-button>
+                          </el-col>
+                        </el-row>
+
+                      </div>
+                    </el-card>
+                  </el-col>
+                </el-checkbox-group>
+              </el-row>
+               <pagination
+                  v-show="total>0"
+                  :total="total"
+                  :page.sync="queryParams.pageNum"
+                  :limit.sync="queryParams.pageSize"
+                  @pagination="getMaterialList"
+                />
+
+            </div>
+          </el-card>
+        </el-main>
+      </el-container>
+      <span slot="footer" class="dialog-footer">
+        <el-button @click="listDialogVisible = false">取 消</el-button>
+        <el-button type="primary" @click="submit">确 定</el-button>
+      </span>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listMaterial, getMaterial, delMaterial, addMaterial, updateMaterial, } from "@/api/qw/material";
+import { listMaterialGroup, getMaterialGroup, delMaterialGroup, addMaterialGroup, updateMaterialGroup } from "@/api/qw/materialGroup";
+export default {
+  name: 'ImageSelect',
+  components: {},
+  props: {
+    // 素材数据
+    value: {
+      type: Array,
+      default() {
+        return []
+      }
+    },
+    // 素材类型
+    type: {
+      type: String,
+      required: true
+    },
+    // 素材限制选择数量,最多9个
+    num: {
+      type: Number,
+      default() {
+        return 9
+      }
+    },
+    // 宽度
+    width: {
+      type: Number,
+      default() {
+        return 150
+      }
+    },
+    // 宽度
+    height: {
+      type: Number,
+      default() {
+        return 150
+      }
+    },
+    corpId:{
+      type:String
+    }
+  },
+  data() {
+    return {
+      uploadUrl:process.env.VUE_APP_BASE_API+"/common/uploadOSS",
+      dialogVisible: false,
+      materialUrl: '',
+      listDialogVisible: false,
+      //素材组列表
+      materialGroupList: [],
+      materialGroupLoading: false,
+      //选择的某个素材组
+      materialGroup:{},
+      tableData: [],
+      resultNumber: 0,
+      total: 0,
+      //查询素材列表
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        groupType:2,
+        materialGroupName: null,
+        materialGroupId:null,
+        materialUrl: null,
+        createUserId: null,
+        corpId:null,
+      },
+      tableLoading: false,
+      urls: [],
+    }
+  },
+  mounted(){
+    this.getAllMaterialGroup();
+  },
+  methods: {
+    //查询素材组下的素材列表
+    selectGroup(item){
+      this.materialGroup=item;
+      this.queryParams.materialGroupId=item.materialGroupId;
+      this.queryParams.corpId=this.corpId;
+      this.getMaterialList();
+    },
+    //添加分组
+    materialgroupAdd() {
+      const that = this
+      this.$prompt('请输入分组名', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消'
+      }).then(({ value }) => {
+        addMaterialGroup({
+          materialGroupName: value,
+          groupType:2,
+          corpId:this.corpId,
+        }).then(function() {
+          that.materialGroup={};
+          that.getAllMaterialGroup();
+        })
+      }).catch(() => {
+
+      })
+    },
+    //删除素材组
+    materialgroupDelete(materialgroupObj) {
+      const that = this
+      this.$confirm('是否确认删除该分组?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(function() {
+        delMaterialGroup(materialgroupObj.materialGroupId)
+          .then(function() {
+            that.materialGroup={};
+            that.getAllMaterialGroup()
+          })
+      })
+      this.resetGroup();
+      this.getAllMaterialGroup();
+    },
+    //修改素材组名称
+    materialgroupEdit(materialgroupObj) {
+      const that = this
+      this.$prompt('请输入分组名', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        inputValue: materialgroupObj.materialGroupName
+      }).then(({ value }) => {
+        updateMaterialGroup({
+          materialGroupId: materialgroupObj.materialGroupId,
+          materialGroupName: value,
+          groupType:2,
+          corpId: this.corpId,
+        }).then(function() {
+          that.materialGroup={};
+          that.getAllMaterialGroup()
+        })
+      }).catch(() => {
+
+      })
+    },
+    //获取所有素材分组
+    getAllMaterialGroup() {
+      this.materialGroupLoading = true;
+      listMaterialGroup({groupType:2,corpId: this.corpId}).then(response => {
+        this.materialGroupList = response.rows
+        this.materialGroupLoading = false;
+      });
+    },
+    resetGroup(){
+      this.queryParams= {
+        pageNum: 1,
+          pageSize: 10,
+          groupType:2,
+          materialGroupName: null,
+          materialGroupId:null,
+          materialUrl: null,
+          createUserId: null,
+      };
+    },
+    //查询分组 下的素材
+    getMaterialList() {
+      this.tableLoading = true;
+      this.queryParams.corpId=this.corpId;
+      listMaterial(this.queryParams).then(response => {
+        this.tableData = response.rows;
+        this.total = response.total;
+        this.tableLoading = false;
+      });
+    },
+    //移动素材
+    moveMaterial(index, type) {
+      if (type == 'up') {
+        const tempOption = this.value[index - 1]
+        this.$set(this.value, index - 1, this.value[index])
+        this.$set(this.value, index, tempOption)
+      }
+      if (type == 'down') {
+        const tempOption = this.value[index + 1]
+        this.$set(this.value, index + 1, this.value[index])
+        this.$set(this.value, index, tempOption)
+      }
+    },
+    //缩小图片
+    zoomMaterial(index) {
+      this.dialogVisible = true
+      this.materialUrl = this.value[index]
+    },
+    //删除素材
+    deleteMaterial(index) {
+      const that = this
+      this.$confirm('是否确认删除?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(function() {
+        that.value.splice(index, 1)
+        that.urls = []
+      })
+    },
+
+    toSeleteMaterial() {
+      this.listDialogVisible = true
+      this.getAllMaterialGroup()
+      this.getMaterialList();
+    },
+    //删除素材
+    materialDel(item) {
+      const that = this
+      this.$confirm('是否确认删除该素材?', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(function() {
+        delMaterial(item.materialId)
+          .then(function() {
+            that.queryParams.pageNum=0;
+            that.getMaterialList();
+          })
+      })
+    },
+
+    //上传进度
+    handleProgress(event, file, fileList) {
+    },
+    //上传成功后
+    handleSuccess(response, file, fileList) {
+      const that = this
+      addMaterial({
+        materialType: 'image',
+        materialGroupId: this.queryParams.materialGroupId,
+        materialName: file.name,
+        materialUrl: response.url,
+        groupType:2,
+        corpId: this.corpId,
+      }).then(() => {
+        this.resultNumber++
+        if (fileList.length === this.resultNumber) {
+          that.getMaterialList()
+          this.resultNumber = 0
+        }
+      })
+    },
+    //上传之前
+    beforeUpload(file) {
+      const isPic =
+        file.type === 'image/jpeg' ||
+        file.type === 'image/png' ||
+        file.type === 'image/jpg'
+      const isLt2M = file.size / 1024 / 1024 < 10
+      if (!isPic) {
+        this.$message.error('上传图片只能是 JPG、JPEG、PNG 格式!')
+        return false
+      }
+      if (!isLt2M) {
+        this.$message.error('上传图片大小不能超过 10MB!')
+      }
+      return isPic && isLt2M
+    },
+    //提交
+    submit() {
+      this.$set(this.value, this.value.length, this.urls[0])
+
+      this.listDialogVisible = false
+
+    }
+  }
+}
+</script>
+
+<style rel="stylesheet/scss" lang="scss" scoped>
+  ::v-deep .el-icon-circle-close{
+    color: red;
+  }
+  .material-name{
+    padding: 8px 0px;
+  }
+  .col-do{
+    text-align: center;
+  }
+  .button-do{
+    padding: unset!important;
+    font-size: 12px;
+  }
+  .group-list{
+    display: flex;
+    flex-direction:column;
+    align-items: flex-start;
+  }
+  .group-item{
+    margin: 5px;
+  }
+</style>

+ 496 - 19
src/views/qw/sop/addSop.vue

@@ -71,6 +71,17 @@
               </el-tag>
             </div>
           </el-form-item>
+          <el-form-item label="客户评级" prop="isRating" style="margin-top: 2%">
+            <el-switch
+              v-model="form.isRating"
+              active-color="#13ce66"
+              inactive-color="#ff4949"
+              :active-value="1"
+              :inactive-value="2">
+            </el-switch>
+            <span v-if="form.isRating == '1'" style="margin-left: 10px;color: #13ce66">已开启</span>
+            <span v-if="form.isRating == '2'" style="margin-left: 10px;color: #ff4949">已关闭</span>
+          </el-form-item>
           <el-form-item label="标签规则" prop="filterType">
             <el-radio-group v-model="form.filterType">
               <el-radio
@@ -82,23 +93,49 @@
             </el-radio-group>
           </el-form-item>
           <el-form-item label="选择的标签" prop="tags">
-            <el-select v-model="tags" remote multiple placeholder="请选择" filterable  style="width: 100%;">
-              <el-option
-                v-for="dict in tagList"
-                :label="dict.name"
-                :value="dict.tagId">
-              </el-option>
-            </el-select>
+<!--            <el-select v-model="tags" remote multiple placeholder="请选择" filterable  style="width: 100%;">-->
+<!--              <el-option-->
+<!--                v-for="dict in tagList"-->
+<!--                :label="dict.name"-->
+<!--                :value="dict.tagId">-->
+<!--              </el-option>-->
+<!--            </el-select>-->
+            <div @click="hangleChangeTags()" style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 390px">
+              <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
+                <el-tag type="success"
+                        closable
+                        :disable-transitions="false"
+                        v-for="list in this.tagsIdsChangeSelectList"
+                        :key="list.tagId"
+                        @close="handleCloseTags(list)"
+                        style="margin: 3px;"
+                >{{list.name}}
+                </el-tag>
+              </div>
+            </div>
 
           </el-form-item>
           <el-form-item label="排除的标签" prop="excludeTags">
-            <el-select v-model="excludeTags" remote multiple placeholder="请选择" filterable  style="width: 100%;">
-              <el-option
-                v-for="dict in tagList"
-                :label="dict.name"
-                :value="dict.tagId">
-              </el-option>
-            </el-select>
+<!--            <el-select v-model="excludeTags" remote multiple placeholder="请选择" filterable  style="width: 100%;">-->
+<!--              <el-option-->
+<!--                v-for="dict in tagList"-->
+<!--                :label="dict.name"-->
+<!--                :value="dict.tagId">-->
+<!--              </el-option>-->
+<!--            </el-select>-->
+            <div @click="hangleChangeOutTags()" style="cursor: pointer; border: 1px solid #e6e6e6; background-color: white; overflow: hidden; flex-grow: 1;width: 390px">
+              <div style="min-height: 35px; max-height: 200px; overflow-y: auto;">
+                <el-tag type="success"
+                        closable
+                        :disable-transitions="false"
+                        v-for="list in this.outTagsIdsChangeSelectList"
+                        :key="list.tagId"
+                        @close="handleCloseOutTags(list)"
+                        style="margin: 3px;"
+                >{{list.name}}
+                </el-tag>
+              </div>
+            </div>
           </el-form-item>
         </div>
         <div v-if="form.type==1">
@@ -251,6 +288,43 @@
     <el-dialog title="选择模板"  :visible.sync="tempOpen" append-to-body  >
       <sop-temp ref="SopTempComments" @sopTemp="sopTemp"></sop-temp>
     </el-dialog>
+
+      <!--  选择/排除标签   -->
+      <el-dialog :title="changeTagDialog.title" :visible.sync="changeTagDialog.open" style="width:100%;height: 100%" append-to-body>
+
+        <div>搜索标签:
+          <el-input v-model="queryTagParams.tagName" placeholder="请输入标签名称" clearable size="small" style="width: 200px;margin-right: 10px" />
+          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleSearchTags">搜索</el-button>
+          <el-button type="primary" icon="el-icon-plus" size="mini" @click="cancelSearchTags">重置</el-button>
+        </div>
+        <div v-for="item in tagGroupList" :key="item.id"  >
+          <div style="font-size: 20px;margin-top: 20px;margin-bottom: 20px;">
+            <span class="name-background">{{ item.name }}</span>
+          </div>
+          <div class="tag-container">
+            <a
+              v-for="tagItem in item.tag"
+              class="tag-box"
+              @click="tagSelection(tagItem)"
+              :class="{ 'tag-selected': tagItem.isSelected }"
+            >
+              {{ tagItem.name }}
+            </a>
+          </div>
+        </div>
+
+        <pagination
+          v-show="total>0"
+          :total="total"
+          :page.sync="queryTagParams.pageNum"
+          :limit.sync="queryTagParams.pageSize"
+          @pagination="cancelSearchTags"
+        />
+        <div slot="footer" class="dialog-footer">
+          <el-button type="primary" @click="tagSubmitForm(changeTagDialog.type)">确 定</el-button>
+          <el-button @click="tagCancel(changeTagDialog.type)">取消</el-button>
+        </div>
+      </el-dialog>
     </div>
   </div>
 </template>
@@ -267,6 +341,7 @@ import sopLogsDetails from '@/views/qw/sopLogs/sopLogsList.vue'
 import { listTag, getTag, } from "@/api/qw/tag";
 import {listWxUserGroup, sopListWxUserGroup} from "@/api/wxUser/wxUserGroup";
 import SopTemp from "@/views/qw/sopTemp/sopTemp.vue";
+import {allListTagGroup} from "@/api/qw/tagGroup";
 export default {
   name: "addSop",
   components: {SopTemp, CustomerGroupDetails, qwUserList,companyUserList,ImageUpload,sopLogsDetails},
@@ -289,7 +364,37 @@ export default {
       // 非单个禁用
       single: true,
       setting:[],
+
+      //标签组
+      tagGroupList:[],
+
       tagList:[],
+
+      //标签
+      changeTagDialog:{
+        title:"",
+        open:false,
+        type:null,
+      },
+      //选择的标签
+      tagsIdsChangeSelectList:null,
+      //排除的标签
+      outTagsIdsChangeSelectList:null,
+      //标签弹窗选择
+      tagChange:{
+        open:false,
+        index:null,
+      },
+
+      queryTagParams: {
+        pageNum: 1,
+        pageSize: 10,
+        total:0,
+        name:null,
+        corpId:null,
+      },
+
+
       // 非多个禁用
       multiple: true,
       // 显示搜索条件
@@ -355,6 +460,8 @@ export default {
 
     this.form.corpId= this.$route.params && this.$route.params.corpId;
 
+    this.queryTagParams.corpId= this.$route.params && this.$route.params.corpId;
+
     this.getDicts("sys_company_status").then(response => {
       this.statusOptions = response.data;
     });
@@ -392,12 +499,151 @@ export default {
         this.companyQwUserList = response.data;
       });
 
-      listTag({corpId:row}).then(response => {
+      this.cancelSearchTags();
+
+    },
+
+    handleSearchTags(){
+
+      searchTags(this.queryTagParams).then(response => {
+        this.tagGroupList = response.rows;
+        this.total = response.total;
+      });
+
+    },
+    cancelSearchTags(){
+      allListTagGroup(this.queryTagParams).then(response => {
+        this.tagGroupList = response.rows;
+        this.total = response.total;
+      });
+
+      listTag(this.queryTagParams).then(response => {
         this.tagList = response.rows;
       });
+    },
+    //标签的选择
+    tagSelection(row){
+      row.isSelected= !row.isSelected;
+      this.$forceUpdate();
+    },
+
+    //确定选择标签
+    tagSubmitForm(type){
+
+      if (type==1){
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+              if (!this.tagsIdsChangeSelectList) {
+                this.tagsIdsChangeSelectList = [];
+              }
+
+              // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+              let tagExists = this.tagsIdsChangeSelectList.some(
+                tag => tag.id === this.tagGroupList[i].tag[x].id
+              );
+
+              // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+              if (!tagExists) {
+                this.tagsIdsChangeSelectList.push(this.tagGroupList[i].tag[x]);
+              }
+            }
+          }
+        }
+        if (!this.tagsIdsChangeSelectList || this.tagsIdsChangeSelectList.length === 0) {
+          return this.$message('请选择标签');
+        }
+
+      }else if (type==2){
+
+        for (let i = 0; i < this.tagGroupList.length; i++) {
+          for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+            if (this.tagGroupList[i].tag[x].isSelected === true) {
+
+              if (!this.outTagsIdsChangeSelectList) {
+                this.outTagsIdsChangeSelectList = [];
+              }
+
+              // 检查当前 tag 是否已经存在于 tagListFormIndex[index] 中
+              let tagExists = this.outTagsIdsChangeSelectList.some(
+                tag => tag.id === this.tagGroupList[i].tag[x].id
+              );
+
+              // 如果 tag 不存在于 tagListFormIndex[index] 中,则新增
+              if (!tagExists) {
+                this.outTagsIdsChangeSelectList.push(this.tagGroupList[i].tag[x]);
+              }
+            }
+          }
+        }
+        if (!this.outTagsIdsChangeSelectList || this.outTagsIdsChangeSelectList.length === 0) {
+          return this.$message('请选择标签');
+        }
+
+      }
+      this.changeTagDialog.open = false;
+    },
+
+    //取消选择标签
+    tagCancel(){
+      this.changeTagDialog.open = false;
+    },
+
+    //选择标签
+    hangleChangeTags(){
+
+      this.changeTagDialog.title="选择标签"
+      this.changeTagDialog.open=true;
+      this.changeTagDialog.type=1;
 
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(
+        (this.tagsIdsChangeSelectList || []).map(tagItem => tagItem?.tagId)
+      );
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+        }
+      }
+
+    },
+
+
+    //选择排除标签
+    hangleChangeOutTags(){
+      this.changeTagDialog.title="选择排除的标签"
+      this.changeTagDialog.open=true;
+      this.changeTagDialog.type=2;
+
+      // 获取 tagListFormIndex 中的所有 tagId,用于快速查找
+      const selectedTagIds = new Set(
+        (this.outTagsIdsChangeSelectList || []).map(tagItem => tagItem?.tagId)
+      );
+      for (let i = 0; i < this.tagGroupList.length; i++) {
+        for (let x = 0; x < this.tagGroupList[i].tag.length; x++) {
+          this.tagGroupList[i].tag[x].isSelected = selectedTagIds.has(this.tagGroupList[i].tag[x].tagId);
+        }
+      }
     },
 
+    //删除一些选择的标签
+    handleCloseTags(list){
+      const ls = this.tagsIdsChangeSelectList.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.tagsIdsChangeSelectList.splice(ls, 1);
+        this.tagsIdsChangeSelectList = [...this.tagsIdsChangeSelectList];
+      }
+    },
+
+    //删除一些排除的标签
+    handleCloseOutTags(list){
+      const ls = this.outTagsIdsChangeSelectList.findIndex(t => t.tagId === list.tagId);
+      if (ls !== -1) {
+        this.outTagsIdsChangeSelectList.splice(ls, 1);
+        this.outTagsIdsChangeSelectList = [...this.outTagsIdsChangeSelectList];
+      }
+    },
     //查询模板
     selectListSopTemp(type){
       this.tempOpen = true;
@@ -497,6 +743,7 @@ export default {
         setting: null,
         createBy: null,
         createTime: null,
+        isRating:null,
         autoSopTime:{ autoSopType:2,autoStartTime:'00:00',autoEndTime:'24:00',autoSopSend:2},
       };
       this.resetForm("form");
@@ -544,14 +791,41 @@ export default {
           this.form.qwUserIds = this.userSelectList.join(",");
 
 
-          if (this.tags!=null && this.tags.length>0){
-            this.form.tags=(this.tags).toString()
+          if (this.tagsIdsChangeSelectList!=null && this.tagsIdsChangeSelectList.length>0){
+
+            // 确保 this.form.tags 是数组
+            if (!this.form.tags) {
+              this.form.tags = []; // 如果未定义,初始化
+            } else {
+              this.form.tags = []; // 清空已有数据
+            }
+
+            // 遍历并添加 tagId
+            this.tagsIdsChangeSelectList.forEach(tag => {
+              if (tag.tagId) { // 确保 tagId 存在
+                this.form.tags.push(tag.tagId);
+              }
+            });
+            this.form.tags=this.form.tags.join(",");
           }else {
             return  this.$message.error("选择的标签不能为空!!请选择筛选的标签")
           }
 
-          if (this.excludeTags!=null){
-            this.form.excludeTags=(this.excludeTags).toString()
+          if (this.outTagsIdsChangeSelectList!=null && this.outTagsIdsChangeSelectList.length>0){
+            // 确保 this.form.tags 是数组
+            if (!this.form.excludeTags) {
+              this.form.excludeTags = []; // 如果未定义,初始化
+            } else {
+              this.form.excludeTags = []; // 清空已有数据
+            }
+
+            // 遍历并添加 tagId
+            this.outTagsIdsChangeSelectList.forEach(tag => {
+              if (tag.tagId) { // 确保 tagId 存在
+                this.form.excludeTags.push(tag.tagId);
+              }
+            });
+            this.form.excludeTags=this.form.excludeTags.join(",");
           }
 
           this.form.setting=JSON.stringify(this.setting)
@@ -593,4 +867,207 @@ export default {
 .custom-input /deep/ .el-input__icon {
   line-height: 20px;
 }
+.user-box {
+  width: 50px;           /* 盒子宽度 */
+  height: 30px;           /* 盒子高度 */
+  padding: 5px 10px;          /* 内边距 */
+  border: 1px solid #d5d1d1;/* 边框 */
+  margin: 3px;            /* 外边距 */
+  background-color: #f9f9f9;/* 背景颜色 */
+  color: #333;            /* 文本颜色 */
+  font-size: 14px;        /* 字体大小 */
+  text-align: center;     /* 文本居中 */
+}
+.elFormItemClass{
+  margin: 20px 0px
+
+}
+
+.message-stayle{
+  display: flex;
+  justify-content: normal;
+  align-items: center;
+  margin-top: 10px;
+}
+.message-stayle .el-link {
+  white-space: normal; /* 允许换行 */
+  word-break: break-all; /* 单词中间断行 */
+  overflow-wrap: break-word; /* 允许在单词内换行 */
+}
+.message-stayle span {
+  word-break: break-all;
+}
+.container {
+  position: relative; /* 使 phone div 的 absolute 定位基于这个容器 */
+  width: 100%;
+  height: 100%;
+}
+.phone {
+  position: absolute; /* 定位到容器内 */
+  top: 5%;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 100%;
+  max-width: 375px;
+  height: 100%;
+  max-height: 90%;
+  border: 8px solid #ccc;
+  border-radius: 36px;
+  box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
+  background-color: #fff;
+  overflow: hidden;
+}
+
+.phone::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 50%;
+  width: 60px;
+  height: 5px;
+  background-color: #ccc;
+  border-radius: 10px;
+  transform: translateX(-50%);
+}
+
+.chat-interface {
+  display: flex;
+  flex-direction: column;
+  height: 100%;
+  background-color: #fff;
+}
+
+.header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  height: 50px;
+  padding: 0 15px;
+  background-color: #fff;
+  border-bottom: 1px solid #e0e0e0;
+}
+
+.header .back,
+.header .menu {
+  height: 25px;
+  width: 25px;
+}
+.header .title{
+  font-size: 16px;
+  margin: auto;
+}
+
+.message-area {
+  flex: 1;
+  background-color: rgb(240, 242, 245);
+  padding: 10px;
+  overflow-y: auto;
+}
+
+.footer {
+  display: flex;
+  align-items: center;
+  padding: 10px;
+  background-color: #fff;
+  border-top: 1px solid #e0e0e0;
+}
+
+.footer .voice-button,
+.footer .emoji-button,
+.footer .add-button {
+  width: 25px;
+  height: 25px;
+  border: none;
+  cursor: pointer;
+  flex-shrink: 0; /* 不允许缩小 */
+}
+.footer .emoji-button,
+.footer .add-button {
+  margin: 0px 5px;
+}
+
+
+.footer .input-box {
+  flex: 1;
+  border: 1px solid #e0e0e0;
+  margin-left: 5px;
+}
+
+.text-container {
+  max-height: 7.5em; /* 设置最大高度为6行,根据字体大小调整 */
+  overflow-y: auto; /* 内容超出时显示滚动条 */
+  line-height: 1.5em; /* 行高设置,确保每行高度一致 */
+}
+.el-form-item {
+  margin-bottom: 30px;
+}
+
+.custom-span {
+  display: block; /* 确保元素是块级元素 */
+  height: 110px; /* 设置固定高度 */
+  overflow-y: auto; /* 超出高度时显示滚动条 */
+  word-wrap: break-word; /* 自动换行 */
+  word-break: break-all; /* 在必要时进行换行 */
+}
+.custom-span-description{
+  display: block; /* 确保元素是块级元素 */
+  height: 110px; /* 设置固定高度 */
+  overflow-y: auto; /* 超出高度时显示滚动条 */
+  word-wrap: break-word; /* 自动换行 */
+  word-break: break-all; /* 在必要时进行换行 */
+}
+.custom-span-title {
+  display: block; /* 确保元素是块级元素 */
+  height: 45px; /* 设置固定高度 */
+  overflow-y: auto; /* 超出高度时显示滚动条 */
+  word-wrap: break-word; /* 自动换行 */
+  word-break: break-all; /* 在必要时进行换行 */
+}
+
+/* CSS 样式 */
+.tag-container {
+  display: flex;
+  flex-wrap: wrap; /* 超出宽度时自动换行 */
+  gap: 8px; /* 设置标签之间的间距 */
+}
+.name-background {
+  display: inline-block;
+  background-color: #abece6; /* 背景颜色 */
+  padding: 4px 8px; /* 调整内边距,让背景包裹文字 */
+  border-radius: 4px; /* 可选:设置圆角 */
+}
+.tag-box {
+  padding: 8px 12px;
+  border: 1px solid #989797;
+  border-radius: 4px;
+  cursor: pointer;
+  display: inline-block;
+}
+
+.tag-selected {
+  background-color: #00bc98;
+  color: #fff;
+  border-color: #00bc98;
+}
+
+.el-tag + .el-tag {
+  margin-left: 10px;
+}
+.button-new-tag {
+  margin-left: 10px;
+  height: 32px;
+  line-height: 30px;
+  padding-top: 0;
+  padding-bottom: 0;
+}
+.input-new-tag {
+  width: 90px;
+  margin-left: 10px;
+  vertical-align: bottom;
+}
+.text-container {
+  max-height: 5em; /* 设置最大高度为6行,根据字体大小调整 */
+  overflow-y: auto; /* 内容超出时显示滚动条 */
+  line-height: 1.5em; /* 行高设置,确保每行高度一致 */
+}
 </style>

+ 73 - 6
src/views/qw/sop/sop.vue

@@ -125,7 +125,7 @@
       </el-col>
       <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
     </el-row>
-    <el-table v-loading="loading" border :data="sopList" @selection-change="handleSelectionChange" @row-click="handleCellClick">
+    <el-table v-loading="loading" border :data="sopList" @selection-change="handleSelectionChange">
       <el-table-column type="selection" width="55" align="center" />
       <el-table-column label="规则编号" align="center" prop="id"  width="160"/>
       <el-table-column label="规则名称" align="center" prop="name" width="150" />
@@ -162,7 +162,7 @@
 <!--        </template>-->
 <!--      </el-table-column>-->
 
-      <!--      <el-table-column label="修改规则状态" align="center" prop="editableStatus" width="140">-->
+<!--        <el-table-column label="修改规则状态" align="center" prop="editableStatus" width="140">-->
 <!--        <template slot-scope="scope">-->
 <!--            <div v-if="scope.row.status !== 1">-->
 <!--              <el-switch-->
@@ -290,6 +290,14 @@
             @click="handleUpdate(scope.row,2)"
             v-hasPermi="['qw:sop:edit']"
           >查看规则</el-button>
+          <el-button
+            v-if="scope.row.status==2 || scope.row.status==0 || scope.row.status == 3 || scope.row.status == 4  "
+            size="mini"
+            type="text"
+            style="color: green;"
+            @click="handleQueryDetails(scope.row)"
+            v-hasPermi="['qw:sop:list']"
+          >查看模板</el-button>
 <!--          <el-button-->
 <!--            size="mini"-->
 <!--            type="text"-->
@@ -304,7 +312,7 @@
             size="mini"
             type="text"
             @click="handleUpdateOutTime(scope.row)"
-          >修改时间</el-button>
+          >修改时间/评级/大小转</el-button>
           <el-button
             v-if="scope.row.status == 1  "
             size="mini"
@@ -454,12 +462,43 @@
     </el-dialog>
 
     <!-- 单独的修改时间   -->
-    <el-dialog :title="outTimeOpen.title" :visible.sync="outTimeOpen.open" width="300px">
+    <el-dialog :title="outTimeOpen.title" :visible.sync="outTimeOpen.open" width="500px">
+
        <el-col>
+
          <el-row>
+           <span>过期时间:</span>
            <el-input-number  v-model="outTimeOpen.expiryTime"  :min="1" :max="100" ></el-input-number>
            (小时)
          </el-row>
+
+         <el-row  style="margin-top: 3%">
+           <span>是否开启客户评级:</span>
+             <el-switch
+               v-model="outTimeOpen.isRating"
+               active-color="#13ce66"
+               inactive-color="#ff4949"
+               :active-value="1"
+               :inactive-value="2">
+             </el-switch>
+             <span v-if="outTimeOpen.isRating == '1'" style="margin-left: 10px;color: #13ce66">已开启</span>
+             <span v-if="outTimeOpen.isRating == '2'" style="margin-left: 10px;color: #ff4949">已关闭</span>
+         </el-row>
+         <el-row>
+           <span>小转天数:</span>
+           <el-input-number  v-model="outTimeOpen.minConversionDay"></el-input-number>
+           (天)
+         </el-row>
+         <el-row>
+           <span>大转天数:</span>
+           <el-input-number  v-model="outTimeOpen.maxConversionDay"></el-input-number>
+           (天)
+         </el-row>
+         <el-row>
+           <span>发课开始天数:</span>
+           <el-input-number  v-model="outTimeOpen.courseDay"  :min="1" :max="100" ></el-input-number>
+           (天)
+         </el-row>
          <el-row style="margin-top: 3%">
 
            <el-button type="primary" icon="el-icon-search" size="mini" @click="handleUpdateExpiryTime">确定修改</el-button>
@@ -667,7 +706,9 @@ export default {
         title:'',
         open:false,
         id:null,
+        tempId:null,
         expiryTime:null,
+        isRating:null,
       },
       voiceForm:{
         list:[],
@@ -828,10 +869,23 @@ export default {
     },
 
     switchSopStatusChange(row,checked){
+
+      let loadingRock = this.$loading({
+        lock: true,
+        text: '暂停中-请勿刷新页面-重复点击',
+        spinner: 'el-icon-loading',
+        background: 'rgba(0, 0, 0, 0.7)'
+      });
+
+
       updateSopStatus({id:row,status:checked}).then(response => {
         this.$message.success("修改成功");
         this.getList();
+      }).finally(res=>{
+        loadingRock.close();
       })
+
+
     },
 
     //新客户自动创建sop
@@ -899,14 +953,16 @@ export default {
 
     //修改过期时间
     handleUpdateOutTime(val){
-        this.outTimeOpen.title="修改过期时间";
+        this.outTimeOpen.title="修改过期时间/评级";
         this.outTimeOpen.id=val.id;
+        this.outTimeOpen.tempId=val.tempId;
         this.outTimeOpen.expiryTime=val.expiryTime;
+        this.outTimeOpen.isRating=val.isRating;
         this.outTimeOpen.open=true;
     },
 
     handleUpdateExpiryTime(){
-      updateSop({id:this.outTimeOpen.id,expiryTime:this.outTimeOpen.expiryTime}).then(response => {
+      updateSop({id:this.outTimeOpen.id,tempId:this.outTimeOpen.tempId,expiryTime:this.outTimeOpen.expiryTime,isRating: this.outTimeOpen.isRating,minConversionDay: this.outTimeOpen.minConversionDay,maxConversionDay: this.outTimeOpen.maxConversionDay,courseDay: this.outTimeOpen.courseDay}).then(response => {
         this.msgSuccess("修改成功");
         this.resetUpdateExpiryTime()
         this.getList();
@@ -918,6 +974,10 @@ export default {
           open:false,
           id:null,
           expiryTime:null,
+        courseDay:null,
+          minConversionDay:null,
+          maxConversionDay:null,
+          isRating:null,
       }
     },
     addContent(index){
@@ -1083,6 +1143,13 @@ export default {
         this.sopLogsDialog.open=true;
         this.sopLogsDialog.sopLogsForm=row;
     },
+
+    /**
+    * 查看模板
+    */
+    handleQueryDetails(row){
+      this.$router.push(`/qw/sopTemp/updateSopTemp/${row.tempId}/3`)
+    },
     /** 提交按钮 */
     submitForm() {
       this.$refs["form"].validate(valid => {

+ 65 - 18
src/views/qw/sopTemp/addSopTemp.vue

@@ -5,7 +5,7 @@
     <div style="margin: 30px;" v-if="this.form.sendType == 2 "> sop规则【AI插件】模板</div>
 
     <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
         <el-form-item label="名称" prop="name">
           <el-input v-model="form.name" placeholder="请输入模板标题" />
         </el-form-item>
@@ -108,9 +108,9 @@
                                 </el-form-item>
                                 <el-form-item label="Ai触达"  v-if="content.type == 4 ">
                                   <el-select  v-model="content.aiTouch" placeholder="请选择Ai触达类型" size="mini" style=" margin-right: 10px;" v-if="content.type == 4 ">
-									<el-option label="非首次交流" value="非首次交流"></el-option>
-									<el-option label="首次交流1" value="首次交流1"></el-option>
-									<el-option label="首次交流2" value="首次交流2"></el-option>
+                                    <el-option label="非首次交流" value="非首次交流"></el-option>
+                                    <el-option label="首次交流1" value="首次交流1"></el-option>
+                                    <el-option label="首次交流2" value="首次交流2"></el-option>
                                     <el-option label="交流状态1" value="交流状态1"></el-option>
                                     <el-option label="交流状态2" value="交流状态2"></el-option>
                                     <el-option label="交流状态3" value="交流状态3"></el-option>
@@ -177,6 +177,24 @@
                                               </div>
                                             </el-card>
                                           </div>
+
+                                          <div v-if="setList.contentType == 4">
+                                            <el-card class="box-card">
+                                              <el-form-item label="标题" prop="miniprogramTitle">
+                                                <el-input v-model="setList.miniprogramTitle" placeholder="请输入小程序消息标题,最长为64字"  />
+                                              </el-form-item>
+                                              <el-form-item label="封面" prop="miniprogramPicUrl">
+                                                <ImageUpload v-model="setList.miniprogramPicUrl"  type="image" :num="10" :width="150" :height="150" />
+                                              </el-form-item>
+                                              <el-form-item label="appid" prop="miniprogramAppid" v-show="false" >
+                                                <el-input v-model="setList.miniprogramAppid='wx73f85f8d62769119' " disabled />
+                                              </el-form-item>
+                                              <el-form-item label="page路径" prop="miniprogramPage" v-show="false" label-width="100px" style="margin-left: -30px">
+                                                <el-input v-model="setList.miniprogramPage" placeholder="小程序消息打开后的路径" disabled />
+                                              </el-form-item>
+                                            </el-card>
+                                          </div>
+
                                           <div v-if="setList.contentType == 5 ">
 
                                             <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
@@ -667,13 +685,19 @@ export default {
 
       for (let i = 0; i < content.setting.length; i++) {
         //如果是链接的才上
-        if (content.setting[i].contentType == 3 && content.type == 2 && content.courseId != null) {
+        if (selectedCourse && content.type == 2 && content.courseId != null) {
 
-          //响应式直接给链接的标题/封面上值
-          if (selectedCourse) {
-            this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
-            this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+          if (content.setting[i].contentType == 3){
+            //响应式直接给链接的标题/封面上值
+              this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+              this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
           }
+
+          if (content.setting[i].contentType == 4){
+            this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
+
+
         }
       }
       videoList(content.courseId).then(response => {
@@ -690,9 +714,16 @@ export default {
         const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
         for (let i = 0; i < content.setting.length; i++) {
           //响应式直接给链接的标题/封面上值
-          if (selectedCourse && content.setting[i].contentType == 3 && content.type == 2 && content.courseId != null) {
-            this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
-            this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+          if (selectedCourse  && content.type == 2 && content.courseId != null) {
+
+            if (content.setting[i].contentType == 3){
+              this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+              this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+            }
+            if (this.setting[i].contentType == 4){
+              this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+            }
+
           }
 
         }
@@ -704,8 +735,13 @@ export default {
 
         for (let i = 0; i < content.setting.length; i++) {
           //响应式直接给链接的描述上值
-          if (selectedVideo && content.setting[i].contentType == 3 && content.type == 2 && content.videoId != null) {
-            this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          if (selectedVideo  && content.type == 2 && content.videoId != null) {
+            if (content.setting[i].contentType == 3){
+              this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+            }
+            if (this.setting[i].contentType == 4){
+              this.$set(content.setting[i], 'miniprogramTitle', selectedVideo.dictLabel);
+            }
           }
         }
       }
@@ -726,12 +762,16 @@ export default {
           this.$set(content.setting[i], 'isBindUrl', '1');
         }
         //如果是链接的才上
-        if (content.setting[i].contentType == 3 && content.type == 2 && content.videoId != null) {
-
+        if (selectedVideo && content.type == 2 && content.videoId != null) {
           //响应式直接给链接的描述上值
-          if (selectedVideo) {
-            this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          if (content.setting[i].contentType == 3 ){
+
+              this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          }
+          if (content.setting[i].contentType == 4){
+              this.$set(content.setting[i], 'miniprogramTitle', selectedVideo.dictLabel);
           }
+
         }
       }
 
@@ -839,6 +879,13 @@ export default {
                   if (this.setting[i].content[j].setting[k].contentType == 3 && this.setting[i].content[j].setting[k].type == 1 && (this.setting[i].content[j].setting[k].linkUrl == null || this.setting[i].content[j].setting[k].linkUrl == "")) {
                     return this.$message.error("链接地址不能为空")
                   }
+
+                  if (this.setting[i].content[j].setting[k].contentType == 4 && (this.setting[i].content[j].setting[k].miniprogramTitle == null || this.setting[i].content[j].setting[k].miniprogramTitle == "")) {
+                    return this.$message.error("小程序消息标题不能为空")
+                  }
+                  if (this.setting[i].content[j].setting[k].contentType == 4 && (this.setting[i].content[j].setting[k].miniprogramPicUrl == null || this.setting[i].content[j].setting[k].miniprogramPicUrl == "")) {
+                    return this.$message.error("小程序封面地址不能为空")
+                  }
                   if (this.setting[i].content[j].setting[k].contentType == 5 && (this.setting[i].content[j].setting[k].fileUrl == null || this.setting[i].content[j].setting[k].fileUrl == "")) {
                     return this.$message.error("文件不能为空")
                   }

+ 257 - 37
src/views/qw/sopTemp/updateSopTemp.vue

@@ -4,12 +4,13 @@
     <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==1 "> sop规则【修改企微接口】模板</div>
     <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==2 "> sop规则【复制企微接口】模板</div>
     <div style="margin: 30px;" v-if="this.form.sendType == 1 && formType==3 "> sop规则【查看企微接口】模板</div>
-    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==1 "> sop规则【修改AI插件】模板</div>
-    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==2 "> sop规则【复制AI插件】模板</div>
-    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==3 "> sop规则【查看AI插件】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==1 "> sop规则【修改群发助手】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==2 "> sop规则【复制群发助手】模板</div>
+    <div style="margin: 30px;" v-if="this.form.sendType == 2 && formType==3 "> sop规则【查看群发助手】模板</div>
+    <div style="margin: 30px;">模板编号:【{{this.form.id}}】</div>
 
     <div style="margin-top: 10px;margin-left: 50px;margin-right: 100px;margin-bottom: 60px;">
-      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
         <el-form-item label="名称" prop="name">
           {{ form.name }}
         </el-form-item>
@@ -47,7 +48,7 @@
                     style="background-color: #fbfbfb;padding: 15px;  border: 1px solid #e6e6e6; margin-bottom: 20px;">
                     <el-form :model="item" label-width="80px">
                       <el-form-item v-if="form.sendType != 4" label="内容名称" style="height: 50px;">
-                        <el-input :disabled="formType == 3" @change="updateChange" v-model="item.name"
+                        <el-input :disabled="formType == 3"  v-model="item.name"
                                   placeholder="内容名称,仅内部可见"/>
                       </el-form-item>
                       <el-form-item label="规则">
@@ -83,20 +84,29 @@
                                       </el-time-picker>
                                     </el-form-item>
                                   </div>
+                                  <el-form-item label="官方群发" v-if="contentIndex==0 && content.type==2">
+                                      <el-switch
+                                        v-model="content.isOfficial"
+                                        active-color="#13ce66"
+                                        inactive-color="#DCDFE6"
+                                        active-value="1"
+                                        inactive-value="0">
+                                      </el-switch>
+                                  </el-form-item>
 
                                   <el-form-item label="消息类别" v-if="form.sendType != 4">
-                                    <el-radio-group v-model="content.type" :disabled="formType == 3"
+                                    <el-radio-group v-model="content.type" :disabled="formType == 3 || content.isOfficial === '1'"
                                                     @change="updateHtml(() => content.contentType = '1')">
                                       <el-radio :label="1">普通</el-radio>
                                       <el-radio :label="2">课程</el-radio>
                                       <el-radio :label="3">订单</el-radio>
                                       <el-radio :label="4">AI触达</el-radio>
-									  <el-radio :label="5">打标签</el-radio>
+                                      <el-radio :label="5">打标签</el-radio>
                                     </el-radio-group>
                                   </el-form-item>
                                   <el-form-item label="课程" v-if="content.type == 2 " required>
                                     <el-select :disabled="formType == 3" v-model="content.courseId"
-                                               placeholder="请选择课程" style=" margin-right: 10px;" size="mini"
+                                               placeholder="请选择课程" style=" margin-right: 10px;" size="mini" remote  filterable
                                                @change="courseChangeUpdate(content,index,contentIndex)">
                                       <el-option
                                         v-for="dict in courseList"
@@ -106,7 +116,7 @@
                                       />
                                     </el-select>
                                     <el-select :disabled="formType == 3" v-model="content.videoId"
-                                               placeholder="请选择小节" size="mini" style=" margin-right: 10px;"
+                                               placeholder="请选择小节" size="mini" style=" margin-right: 10px;" remote  filterable
                                                @change="videoIdChange(content,index,contentIndex)">
                                       <el-option
                                         v-for="dict in videoList[contentIndex]"
@@ -117,7 +127,7 @@
                                     </el-select>
                                     <el-select :disabled="formType == 3" v-model="content.courseType"
                                                placeholder="请选择消息类型" size="mini"
-                                               style=" margin-right: 10px;" v-if="content.type != 4 " @change="update">
+                                               style=" margin-right: 10px;" v-if="content.type != 4 ">
                                       <el-option
                                         v-for="dict in sysFsSopWatchStatus"
                                         :key="dict.dictValue"
@@ -202,7 +212,7 @@
                                                   v-for="item in sysQwSopContentType"
                                                   :key="item.dictValue"
                                                   :label="item.dictValue"
-                                                  :disabled="item.dictValue === '1' && content.setting.some(s => s.contentType == '1')">
+                                                  :disabled="item.dictValue === '1' && content.setting.some(s => s.contentType == '1') ">
                                                   {{ item.dictLabel }}
                                                 </el-radio>
                                               </el-radio-group>
@@ -214,6 +224,7 @@
                                                 <el-radio
                                                   :key="item.dictValue"
                                                   :label="item.dictValue"
+                                                  :disabled="(content.type!=2 && item.dictValue === '9') || (content.isOfficial==1 && ['5','6','7','8','9'].includes(item.dictValue))"
                                                   v-for="item in sysQwSopAiContentType">{{ item.dictLabel }}
                                                 </el-radio>
                                               </el-radio-group>
@@ -223,12 +234,26 @@
                                             <el-input :disabled="formType == 3" v-if="setList.contentType == 1 "
                                                       v-model="setList.value"
                                                       type="textarea" :rows="3" placeholder="内容"
-                                                      @change="updateChange"
-                                                      style="width: 90%;margin-top: 10px;"/>
+                                                      style="width: 90%;margin-top: 10px;"
+                                                      @keydown.native="handleKeydown($event, index, contentIndex, setIndex)"
+                                                      :ref="`textarea-${index}-${contentIndex}-${setIndex}`"
+                                            />
+
+                                            <!-- 修改按钮部分 -->
+                                            <el-link
+                                                v-if="setList.contentType == 1"
+                                                type="primary"
+                                                @click="toggleSalesCall(index, contentIndex, setIndex)"
+                                                style="margin-top: 10px;"
+                                                >
+                                            {{ setList.isSalesCallAdded ? '移除#销售称呼#' : '添加#销售称呼#' }}
+                                            </el-link>
+
                                             <ImageUpload :disabled="formType == 3" v-if="setList.contentType == 2 "
                                                          v-model="setList.imgUrl"
                                                          type="image" :num="1" :width="150" :height="150"/>
-                                            <div v-if="setList.contentType == 3 ">
+
+                                            <div v-if="setList.contentType == 3  || (setList.contentType == 9 && content.type==2 )">
                                               <el-card class="box-card">
                                                 <el-form-item label="链接标题:" label-width="100px" required>
                                                   <el-input :disabled="formType == 3" v-model="setList.linkTitle"
@@ -263,6 +288,25 @@
                                                 </div>
                                               </el-card>
                                             </div>
+
+                                            <div v-if="setList.contentType == 4">
+                                              <el-card class="box-card">
+                                                <el-form-item label="标题" prop="miniprogramTitle">
+                                                  <el-input v-model="setList.miniprogramTitle" placeholder="请输入小程序消息标题,最长为64字节" :rows="2" maxlength="64"  type="textarea" @input="checkByteLength(content,setList.contentType,content.isOfficial)"   />
+                                                </el-form-item>
+                                                <el-form-item label="封面" prop="miniprogramPicUrl">
+                                                  <ImageUpload v-if="content.isOfficial !== '1'" v-model="setList.miniprogramPicUrl" type="image" :num="10" :width="150" :height="150" />
+                                                </el-form-item>
+                                                <el-form-item label="appid" prop="miniprogramAppid" v-show="false">
+                                                  <el-input v-model="setList.miniprogramAppid='wx73f85f8d62769119' " disabled />
+                                                </el-form-item>
+                                                <el-form-item label="page路径" prop="miniprogramPage"  v-show="false" label-width="100px" style="margin-left: -30px">
+                                                  <el-input v-model="setList.miniprogramPage" placeholder="小程序消息打开后的路径"  disabled />
+                                                </el-form-item>
+                                              </el-card>
+                                            </div>
+
+
                                             <div v-if="setList.contentType == 5 ">
 
                                             <el-form-item label="上传文件:" prop="fileUrl" label-width="100px">
@@ -395,7 +439,7 @@
                                             v-model="setList.intervalTime"
                                             :min="1"
                                             :max="1440"
-                                            style="width:100;margin-top: 10px;"
+                                            style="width:100px;margin-top: 10px;"
                                           >
                                           </el-input-number>
                                           <span class="tip">单位:分钟,最大1440分钟(24小时)</span>
@@ -698,6 +742,47 @@ export default {
 	  this.addTag[index].inputValue = '';
 	},
 
+    // 检查字节长度
+    checkByteLength(content,type,isOfficial) {
+
+    if (type == 4 && isOfficial=='1')
+      for (let i = 0; i < content.setting.length; i++) {
+        const text = content.setting[i].miniprogramTitle;
+        const byteLength = this.getByteLength(text); // 获取当前字节数
+        // 如果字节数超过64,截断输入内容
+        if (byteLength > 64) {
+          this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(text, 60));
+        }
+
+      }
+
+    },
+
+    // 计算字符串的字节数
+    getByteLength(text) {
+      return new Blob([text]).size; // 使用 Blob 计算字节数
+    },
+
+    // 根据字节数截断字符串
+    truncateTextByByteLength(text, maxByteLength) {
+      let byteLength = 0;
+      let result = "";
+
+      for (let i = 0; i < text.length; i++) {
+        const char = text[i];
+        const charByteLength = this.getByteLength(char); // 获取当前字符的字节数
+
+        // 如果加上当前字符的字节数后不超过限制,则添加到结果中
+        if (byteLength + charByteLength <= maxByteLength) {
+          result += char;
+          byteLength += charByteLength;
+        } else {
+          break; // 超过限制时停止
+        }
+      }
+
+      return result;
+    },
 
 	handleCloseDel(index,tag,content) {
 	  content.delTag.splice(content.delTag.indexOf(tag), 1);
@@ -709,7 +794,7 @@ export default {
 	handleInputConfirmDel(index,content) {
 
 	  let delTagValue = this.addTag[index].delTagValue;
-	  console.log(delTagValue)
+
 	  if (delTagValue) {
 		if (!content.hasOwnProperty('delTag')) {
 		   this.$set(content, 'delTag', []);
@@ -789,7 +874,7 @@ export default {
             });
 
             for (let j = 0; j < this.setting[index].content.length; j++) {
-              console.log(this.setting[index].content[j])
+
               if (this.setting[index].content[j].addTag != null) {
                 this.setting[index].content[j].addTag = JSON.parse(this.setting[index].content[j].addTag)
               }
@@ -822,6 +907,16 @@ export default {
       if (!check) {
         return;
       }
+      for (let j = 0; j < data.content.length; j++) {
+        if (data.content[j].type != 4 && data.content[j].type != 5) {
+          for (let k = 0; k < data.content[j].setting.length; k++) {
+            if (data.content[j].setting[k].contentType == 4 && (data.content[j].setting[k].miniprogramTitle != null || data.content[j].setting[k].miniprogramTitle != "")) {
+              data.content[j].setting[k].miniprogramTitle=this.truncateTextByByteLength(data.content[j].setting[k].miniprogramTitle,60)
+            }
+          }
+        }
+      }
+
       let index = 0;
       const dataList = data.content.map(e => {
         e.name = data.name;
@@ -864,7 +959,7 @@ export default {
     },
     checkData(data) {
       if (this.form.sendType != 4) {
-      console.log("data",data)
+
         for (let j = 0; j < data.content.length; j++) {
           if (data.name == null || data.name == "") {
             this.$message.error("内容名称不能为空")
@@ -899,15 +994,15 @@ export default {
                 this.$message.error("图片不能为空")
                 return false;
               }
-              if (data.content[j].setting[k].contentType == 3 && (data.content[j].setting[k].linkTitle == null || data.content[j].setting[k].linkTitle == "")) {
+              if ((data.content[j].setting[k].contentType == 3 ||data.content[j].setting[k].contentType == 9) && (data.content[j].setting[k].linkTitle == null || data.content[j].setting[k].linkTitle == "")) {
                 this.$message.error("链接标题不能为空")
                 return false;
               }
-              if (data.content[j].setting[k].contentType == 3 && (data.content[j].setting[k].linkDescribe == null || data.content[j].setting[k].linkDescribe == "")) {
+              if ((data.content[j].setting[k].contentType == 3 ||data.content[j].setting[k].contentType == 9 ) && (data.content[j].setting[k].linkDescribe == null || data.content[j].setting[k].linkDescribe == "")) {
                 this.$message.error("链接描述不能为空")
                 return false;
               }
-              if (data.content[j].setting[k].contentType == 3 && (data.content[j].setting[k].linkImageUrl == null || data.content[j].setting[k].linkImageUrl == "")) {
+              if ((data.content[j].setting[k].contentType == 3 ||data.content[j].setting[k].contentType == 9 ) && (data.content[j].setting[k].linkImageUrl == null || data.content[j].setting[k].linkImageUrl == "")) {
                 this.$message.error("链接图片不能为空")
                 return false;
               }
@@ -915,6 +1010,16 @@ export default {
                 this.$message.error("链接地址不能为空")
                 return false;
               }
+
+              if (data.content[j].setting[k].contentType == 4 && (data.content[j].setting[k].miniprogramTitle == null || data.content[j].setting[k].miniprogramTitle == "")) {
+                this.$message.error("小程序消息标题不能为空")
+                return false;
+              }
+              if (data.content[j].setting[k].contentType == 4 && data.content[j].isOfficial !== '1' && (data.content[j].setting[k].miniprogramPicUrl == null || data.content[j].setting[k].miniprogramPicUrl == "")) {
+                this.$message.error("小程序封面地址不能为空")
+                return false;
+              }
+
               if (data.content[j].setting[k].contentType == 5 && (data.content[j].setting[k].fileUrl == null || data.content[j].setting[k].fileUrl == "")) {
                 this.$message.error("文件不能为空")
                 return false;
@@ -1101,22 +1206,32 @@ export default {
 
     //修改
     courseChangeUpdate(content, index, countIndex) {
+
       this.$set(content, 'videoId', null);
       // 查找选中的课程对应的 label
       const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
-      for (let i = 0; i < content.setting; i++) {
+      for (let i = 0; i < content.setting.length; i++) {
+
+
+
         this.$set(content.setting[i], 'linkTitle', null);
         this.$set(content.setting[i], 'linkDescribe', null);
         this.$set(content.setting[i], 'linkImageUrl', null);
 
-        //如果是链接的才上
-        if (content.setting[i].contentType == 3 && content.type == 2 && content.courseId != null) {
 
+        //如果是链接的才上
+        if (selectedCourse && content.type == 2 && content.courseId != null) {
           //响应式直接给链接的标题/封面上值
-          if (selectedCourse) {
+
+          if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9 ){
             this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
             this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
           }
+          if (content.setting[i].contentType == 4){
+            this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+          }
+
+
         }
       }
 
@@ -1141,15 +1256,94 @@ export default {
       });
 
     },
+    toggleSalesCall(itemIndex, contentIndex, setIndex) {
+      // 获取目标对象
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      const salesCall = '#销售称呼#';
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}`;
+      const textarea = this.$refs[refKey][0]?.$refs?.textarea;
+
+      if (!textarea) return;
+
+      // 获取光标位置
+      const cursorPosition = textarea.selectionStart;
+
+      if (setItem.isSalesCallAdded) {
+        // 移除所有标签
+        setItem.value = setItem.value.replace(new RegExp(salesCall, 'g'), '');
+      } else {
+        // 插入到光标位置
+        setItem.value =
+          setItem.value.slice(0, cursorPosition) +
+          salesCall +
+          setItem.value.slice(cursorPosition);
+      }
+
+      // 切换状态
+      setItem.isSalesCallAdded = !setItem.isSalesCallAdded;
+
+      // 保持光标位置
+      this.$nextTick(() => {
+        const newCursorPos = cursorPosition + (setItem.isSalesCallAdded ? salesCall.length : 0);
+        textarea.setSelectionRange(newCursorPos, newCursorPos);
+      });
+    },
+    handleKeydown(event, itemIndex, contentIndex, setIndex) {
+
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}`;
+      const textarea = this.$refs[refKey][0]?.$refs?.textarea;
+
+      if (!textarea) return;
+
+      const tags = ['#销售称呼#', '#客户称呼#'];
+      const key = event.key;
+      const value = setItem.value;
+      const cursorPosition = textarea.selectionStart;
+
+      if (key === 'Backspace' || key === 'Delete') {
+        tags.forEach(tag => {
+          let start, end;
+
+          // Backspace 处理
+          if (key === 'Backspace' && cursorPosition >= tag.length) {
+            start = cursorPosition - tag.length;
+            if (value.slice(start, cursorPosition) === tag) {
+              event.preventDefault();
+              setItem.value = value.slice(0, start) + value.slice(cursorPosition);
+              if (tag === '#销售称呼#') setItem.isSalesCallAdded = false;
+              this.$nextTick(() => textarea.setSelectionRange(start, start));
+            }
+          }
+
+          // Delete 处理
+          if (key === 'Delete') {
+            end = cursorPosition + tag.length;
+            if (value.slice(cursorPosition, end) === tag) {
+              event.preventDefault();
+              setItem.value = value.slice(0, cursorPosition) + value.slice(end);
+              if (tag === '#销售称呼#') setItem.isSalesCallAdded = false;
+            }
+          }
+        });
+      }
+    },
     handleContentTypeChange(content, index, countIndex) {
       //如果是链接的才上
       if (content.courseId != null && content.type == 2) {
         const selectedCourse = this.courseList.find(course => parseInt(course.dictValue) === content.courseId);
         for (let i = 0; i < content.setting.length; i++) {
           //响应式直接给链接的标题/封面上值
-          if (selectedCourse && content.setting[i].contentType == 3 && content.type == 2 && content.courseId != null) {
-            this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
-            this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+          if (selectedCourse  && content.type == 2 && content.courseId != null) {
+            if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9){
+              this.$set(content.setting[i], 'linkTitle', selectedCourse.dictLabel);
+              this.$set(content.setting[i], 'linkImageUrl', selectedCourse.dictImgUrl);
+            }
+            if (content.setting[i].contentType == 4 && (content.isOfficial == '0' || content.isOfficial == null)){
+              console.log(content.isOfficial);
+              this.$set(content.setting[i], 'miniprogramPicUrl', selectedCourse.dictImgUrl);
+            }
+
           }
 
         }
@@ -1161,8 +1355,14 @@ export default {
 
         for (let i = 0; i < content.setting.length; i++) {
           //响应式直接给链接的描述上值
-          if (selectedVideo && content.setting[i].contentType == 3 && content.type == 2 && content.videoId != null) {
-            this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+          if (selectedVideo  && content.type == 2 && content.videoId != null) {
+
+            if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9){
+              this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
+            }
+            if (content.setting[i].contentType == 4){
+              this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(selectedVideo.dictLabel,60));
+            }
           }
         }
       }
@@ -1182,12 +1382,16 @@ export default {
           this.$set(content.setting[i], 'isBindUrl', '1');
         }
         //如果是链接的才上
-        if (content.setting[i].contentType == 3 && content.type == 2 && content.videoId != null) {
+        if (selectedVideo && content.type == 2 && content.videoId != null) {
 
-          //响应式直接给链接的描述上值
-          if (selectedVideo) {
+          if (content.setting[i].contentType == 3 || content.setting[i].contentType == 9 ){
             this.$set(content.setting[i], 'linkDescribe', selectedVideo.dictLabel);
           }
+          if (content.setting[i].contentType == 4){
+            this.$set(content.setting[i], 'miniprogramTitle', this.truncateTextByByteLength(selectedVideo.dictLabel,60));
+          }
+
+
         }
       }
 
@@ -1340,7 +1544,23 @@ export default {
       getSopTemp(id).then(response => {
         this.videoList = []
         this.form = response.data;
-        this.setting = this.form.list;
+        this.setting = this.form.list || [];
+
+        if (Array.isArray(this.setting)) {
+          this.setting.forEach(item => {
+            if (item && Array.isArray(item.content)) {
+              item.content.forEach(content => {
+                if (content && Array.isArray(content.setting)) {
+                  content.setting.forEach(setList => {
+                    if (setList && !Object.hasOwn(setList, 'isSalesCallAdded')) {
+                      this.$set(setList, 'isSalesCallAdded', false);
+                    }
+                  });
+                }
+              });
+            }
+          });
+        }
         this.form.list.forEach(e => e.newDay = e.dayNum)
         this.dayList = JSON.parse(JSON.stringify(this.form.list));
         this.videoList.push([]);
@@ -1395,9 +1615,9 @@ export default {
       val || val();
 
     },
-    updateChange(val) {
-      console.log(val)
-    }
+    // updateChange(val) {
+    //   console.log(val)
+    // }
   }
 };
 </script>