xw преди 1 месец
родител
ревизия
06004aab4c
променени са 3 файла, в които са добавени 713 реда и са изтрити 93 реда
  1. 283 31
      src/views/qw/sopTemp/updateSopTemp.vue
  2. 215 31
      src/views/qw/sopUserLogsInfo/sendMsgOpenTool.vue
  3. 215 31
      src/views/qw/sopUserLogsInfo/sopUserLogsInfoDetails.vue

+ 283 - 31
src/views/qw/sopTemp/updateSopTemp.vue

@@ -335,31 +335,106 @@
                                           </div>
                                         </el-form-item>
                                         <el-form-item label="内容">
-                                          <el-input :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" v-if="setList.contentType == 1 || setList.contentType == 15"
-                                                    v-model="setList.value"
-                                                    type="textarea" :rows="3" placeholder="内容"
-                                                    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 || setList.contentType == 15) && roles.includes('edit_sop_temp_content')"
-                                            type="primary"
-                                            @click="toggleSalesCall(index, contentIndex, setIndex)"
-                                            style="margin-top: 10px;"
-                                          >
-                                            {{ setList.isSalesCallAdded ? '移除#销售称呼#' : '添加#销售称呼#' }}
-                                          </el-link>
-                                          <el-link
-                                            v-if="(setList.contentType == 1 || setList.contentType == 15) && roles.includes('edit_sop_temp_content')"
-                                            type="primary"
-                                            @click="toggleUserNameCall(index, contentIndex, setIndex)"
-                                            style="margin-top: 10px; margin-left: 20px"
-                                          >
-                                            {{ setList.isUserNameCallAdded ? '移除#客户称呼#' : '添加#客户称呼#' }}
-                                          </el-link>
+                                          <div v-if="setList.contentType == 1 || setList.contentType == 15">
+                                            <div style="display: flex; align-items: center; margin-bottom: 10px;">
+                                              <span style="margin-right: 10px;">随机发送</span>
+                                              <el-switch
+                                                v-model="setList.isRandomSend"
+                                                :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                active-color="#13ce66"
+                                                inactive-color="#DCDFE6"
+                                                :active-value="1"
+                                                :inactive-value="0"
+                                                @change="handleRandomSendChange(setList)"
+                                              />
+                                              <span style="color: #999; font-size: 12px; margin-left: 10px;">
+                                                开启后可为同一内容配置多条文案,每个客户随机发送其中一条
+                                              </span>
+                                            </div>
+
+                                            <div v-if="setList.isRandomSend == 1">
+                                              <div
+                                                v-for="(randomValue, randomIndex) in setList.randomValueList"
+                                                :key="randomIndex"
+                                                style="margin-bottom: 12px; padding: 10px; border: 1px dashed #dcdfe6; border-radius: 4px;"
+                                              >
+                                                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
+                                                  <span style="color: #606266; font-size: 13px;">文案 {{ randomIndex + 1 }}</span>
+                                                  <el-button
+                                                    v-if="setList.randomValueList.length > 2 && roles.includes('edit_sop_temp_content') && formType != 3"
+                                                    type="text"
+                                                    icon="el-icon-delete"
+                                                    style="color: #f56c6c;"
+                                                    @click="removeRandomValue(setList, randomIndex)"
+                                                  />
+                                                </div>
+                                                <el-input
+                                                  :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                  v-model="setList.randomValueList[randomIndex]"
+                                                  type="textarea"
+                                                  :rows="3"
+                                                  placeholder="请输入文案内容"
+                                                  style="width: 90%;"
+                                                  @keydown.native="handleRandomKeydown($event, index, contentIndex, setIndex, randomIndex)"
+                                                  :ref="`textarea-${index}-${contentIndex}-${setIndex}-${randomIndex}`"
+                                                />
+                                                <div style="margin-top: 8px;">
+                                                  <el-link
+                                                    v-if="roles.includes('edit_sop_temp_content')"
+                                                    type="primary"
+                                                    @click="toggleRandomSalesCall(index, contentIndex, setIndex, randomIndex)"
+                                                  >
+                                                    {{ setList.randomSalesCallAdded && setList.randomSalesCallAdded[randomIndex] ? '移除#销售称呼#' : '添加#销售称呼#' }}
+                                                  </el-link>
+                                                  <el-link
+                                                    v-if="roles.includes('edit_sop_temp_content')"
+                                                    type="primary"
+                                                    style="margin-left: 20px;"
+                                                    @click="toggleRandomUserNameCall(index, contentIndex, setIndex, randomIndex)"
+                                                  >
+                                                    {{ setList.randomUserNameCallAdded && setList.randomUserNameCallAdded[randomIndex] ? '移除#客户称呼#' : '添加#客户称呼#' }}
+                                                  </el-link>
+                                                </div>
+                                              </div>
+                                              <el-link
+                                                v-if="roles.includes('edit_sop_temp_content') && formType != 3"
+                                                type="primary"
+                                                icon="el-icon-plus"
+                                                :underline="false"
+                                                @click="addRandomValue(setList)"
+                                              >
+                                                添加文案
+                                              </el-link>
+                                            </div>
+
+                                            <template v-else>
+                                              <el-input :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')"
+                                                        v-model="setList.value"
+                                                        type="textarea" :rows="3" placeholder="内容"
+                                                        style="width: 90%;margin-top: 10px;"
+                                                        @keydown.native="handleKeydown($event, index, contentIndex, setIndex)"
+                                                        :ref="`textarea-${index}-${contentIndex}-${setIndex}`"
+                                              />
+
+                                              <!-- 修改按钮部分 -->
+                                              <el-link
+                                                v-if="roles.includes('edit_sop_temp_content')"
+                                                type="primary"
+                                                @click="toggleSalesCall(index, contentIndex, setIndex)"
+                                                style="margin-top: 10px;"
+                                              >
+                                                {{ setList.isSalesCallAdded ? '移除#销售称呼#' : '添加#销售称呼#' }}
+                                              </el-link>
+                                              <el-link
+                                                v-if="roles.includes('edit_sop_temp_content')"
+                                                type="primary"
+                                                @click="toggleUserNameCall(index, contentIndex, setIndex)"
+                                                style="margin-top: 10px; margin-left: 20px"
+                                              >
+                                                {{ setList.isUserNameCallAdded ? '移除#客户称呼#' : '添加#客户称呼#' }}
+                                              </el-link>
+                                            </template>
+                                          </div>
 
                                           <ImageUpload :disabled="formType == 3 || !roles.includes('edit_sop_temp_content')" v-if="setList.contentType == 2 "
                                                        v-model="setList.imgUrl"
@@ -1324,6 +1399,9 @@ export default {
               this.courseChange(this.setting[index].content[j], index, j);
             }
           }
+          if (this.setting[index].content[j].setting) {
+            this.setting[index].content[j].setting.forEach(setList => this.normalizeSettingRandomFields(setList));
+          }
         }
       });
     },
@@ -1360,6 +1438,9 @@ export default {
               this.courseChange(this.setting[index].content[j], index, j);
             }
           }
+          if (this.setting[index].content[j].setting) {
+            this.setting[index].content[j].setting.forEach(setList => this.normalizeSettingRandomFields(setList));
+          }
         }
       });
     },
@@ -1432,6 +1513,7 @@ export default {
         e.settingList = e.setting;
         // 在序列化之前,清理 null 值,避免后端接收到字符串 "null"
         e.settingList.forEach(setting => {
+          this.prepareSettingForSave(setting);
           // 将所有值为 null 的字段删除或设置为 undefined
           Object.keys(setting).forEach(key => {
             if (setting[key] === null) {
@@ -1521,9 +1603,18 @@ export default {
 
           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 == 1 && (data.content[j].setting[k].value == null || data.content[j].setting[k].value == "")) {
-                this.$message.error("内容不能为空")
-                return false;
+              if (data.content[j].setting[k].contentType == 1) {
+                const settingItem = data.content[j].setting[k];
+                if (settingItem.isRandomSend == 1) {
+                  const validRandomValues = (settingItem.randomValueList || []).filter(item => item != null && item.trim() !== '');
+                  if (validRandomValues.length < 2) {
+                    this.$message.error("开启随机发送后,至少需要填写2条文案")
+                    return false;
+                  }
+                } else if (settingItem.value == null || settingItem.value == "") {
+                  this.$message.error("内容不能为空")
+                  return false;
+                }
               }
               if (data.content[j].setting[k].contentType == 2 && (data.content[j].setting[k].imgUrl == null || data.content[j].setting[k].imgUrl == "")) {
                 this.$message.error("图片不能为空")
@@ -1592,9 +1683,17 @@ export default {
                 this.$message.error("客服时间不能为空");
                 return false;
               }
-              if (set.contentType == 1 && (set.value == null || set.value == "")) {
-                this.$message.error("内容不能为空")
-                return false;
+              if (set.contentType == 1) {
+                if (set.isRandomSend == 1) {
+                  const validRandomValues = (set.randomValueList || []).filter(item => item != null && item.trim() !== '');
+                  if (validRandomValues.length < 2) {
+                    this.$message.error("开启随机发送后,至少需要填写2条文案")
+                    return false;
+                  }
+                } else if (set.value == null || set.value == "") {
+                  this.$message.error("内容不能为空")
+                  return false;
+                }
               }
               if (set.contentType == 2 && (set.imgUrl == null || set.imgUrl == "")) {
                 this.$message.error("图片不能为空")
@@ -1975,6 +2074,158 @@ export default {
         textarea.setSelectionRange(newCursorPos, newCursorPos);
       });
     },
+    initRandomSendFields(setList) {
+      if (!setList) {
+        return;
+      }
+      if (setList.isRandomSend === undefined || setList.isRandomSend === null) {
+        this.$set(setList, 'isRandomSend', 0);
+      }
+      if (!Array.isArray(setList.randomValueList)) {
+        const initialValue = setList.value || '';
+        this.$set(setList, 'randomValueList', initialValue ? [initialValue] : ['', '']);
+      }
+      if (!Array.isArray(setList.randomSalesCallAdded)) {
+        this.$set(setList, 'randomSalesCallAdded', setList.randomValueList.map(() => false));
+      }
+      if (!Array.isArray(setList.randomUserNameCallAdded)) {
+        this.$set(setList, 'randomUserNameCallAdded', setList.randomValueList.map(() => false));
+      }
+    },
+    handleRandomSendChange(setList) {
+      this.initRandomSendFields(setList);
+      if (setList.isRandomSend == 1) {
+        if (!setList.value && setList.randomValueList.every(item => !item)) {
+          this.$set(setList, 'randomValueList', ['', '']);
+          this.$set(setList, 'randomSalesCallAdded', [false, false]);
+          this.$set(setList, 'randomUserNameCallAdded', [false, false]);
+        } else if (setList.value && setList.randomValueList.every(item => !item)) {
+          this.$set(setList, 'randomValueList', [setList.value, '']);
+          this.$set(setList, 'randomSalesCallAdded', [false, false]);
+          this.$set(setList, 'randomUserNameCallAdded', [false, false]);
+        } else if (setList.randomValueList.length < 2) {
+          setList.randomValueList.push('');
+          setList.randomSalesCallAdded.push(false);
+          setList.randomUserNameCallAdded.push(false);
+        }
+      } else if (setList.randomValueList && setList.randomValueList.length > 0) {
+        const firstValue = setList.randomValueList.find(item => item && item.trim()) || setList.randomValueList[0] || '';
+        this.$set(setList, 'value', firstValue);
+      }
+    },
+    addRandomValue(setList) {
+      this.initRandomSendFields(setList);
+      setList.randomValueList.push('');
+      setList.randomSalesCallAdded.push(false);
+      setList.randomUserNameCallAdded.push(false);
+    },
+    removeRandomValue(setList, randomIndex) {
+      if (!setList.randomValueList || setList.randomValueList.length <= 2) {
+        return;
+      }
+      setList.randomValueList.splice(randomIndex, 1);
+      setList.randomSalesCallAdded.splice(randomIndex, 1);
+      setList.randomUserNameCallAdded.splice(randomIndex, 1);
+    },
+    toggleRandomSalesCall(itemIndex, contentIndex, setIndex, randomIndex) {
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      this.initRandomSendFields(setItem);
+      const salesCall = '#销售称呼#';
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}-${randomIndex}`;
+      const textarea = this.$refs[refKey] && this.$refs[refKey][0]?.$refs?.textarea;
+      const cursorPosition = textarea ? textarea.selectionStart : (setItem.randomValueList[randomIndex] || '').length;
+
+      if (setItem.randomSalesCallAdded[randomIndex]) {
+        this.$set(setItem.randomValueList, randomIndex, (setItem.randomValueList[randomIndex] || '').replace(new RegExp(salesCall, 'g'), ''));
+        this.$set(setItem.randomSalesCallAdded, randomIndex, false);
+      } else {
+        const currentValue = setItem.randomValueList[randomIndex] || '';
+        this.$set(
+          setItem.randomValueList,
+          randomIndex,
+          currentValue.slice(0, cursorPosition) + salesCall + currentValue.slice(cursorPosition)
+        );
+        this.$set(setItem.randomSalesCallAdded, randomIndex, true);
+      }
+    },
+    toggleRandomUserNameCall(itemIndex, contentIndex, setIndex, randomIndex) {
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      this.initRandomSendFields(setItem);
+      const userCall = '#客户称呼#';
+      const refKey = `textarea-${itemIndex}-${contentIndex}-${setIndex}-${randomIndex}`;
+      const textarea = this.$refs[refKey] && this.$refs[refKey][0]?.$refs?.textarea;
+      const cursorPosition = textarea ? textarea.selectionStart : (setItem.randomValueList[randomIndex] || '').length;
+
+      if (setItem.randomUserNameCallAdded[randomIndex]) {
+        this.$set(setItem.randomValueList, randomIndex, (setItem.randomValueList[randomIndex] || '').replace(new RegExp(userCall, 'g'), ''));
+        this.$set(setItem.randomUserNameCallAdded, randomIndex, false);
+      } else {
+        const currentValue = setItem.randomValueList[randomIndex] || '';
+        this.$set(
+          setItem.randomValueList,
+          randomIndex,
+          currentValue.slice(0, cursorPosition) + userCall + currentValue.slice(cursorPosition)
+        );
+        this.$set(setItem.randomUserNameCallAdded, randomIndex, true);
+      }
+    },
+    handleRandomKeydown(event, itemIndex, contentIndex, setIndex, randomIndex) {
+      if (event.key !== 'Backspace' && event.key !== 'Delete') {
+        return;
+      }
+      const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
+      this.initRandomSendFields(setItem);
+      const textarea = event.target;
+      const value = setItem.randomValueList[randomIndex] || '';
+      const cursorPosition = textarea.selectionStart;
+      const tags = ['#销售称呼#', '#客户称呼#'];
+
+      tags.forEach(tag => {
+        const tagIndex = value.indexOf(tag);
+        if (tagIndex !== -1) {
+          const tagEnd = tagIndex + tag.length;
+          if ((event.key === 'Backspace' && cursorPosition > tagIndex && cursorPosition <= tagEnd) ||
+            (event.key === 'Delete' && cursorPosition >= tagIndex && cursorPosition < tagEnd)) {
+            event.preventDefault();
+            this.$set(setItem.randomValueList, randomIndex, value.replace(tag, ''));
+            if (tag === '#销售称呼#') {
+              this.$set(setItem.randomSalesCallAdded, randomIndex, false);
+            }
+            if (tag === '#客户称呼#') {
+              this.$set(setItem.randomUserNameCallAdded, randomIndex, false);
+            }
+          }
+        }
+      });
+    },
+    normalizeSettingRandomFields(setting) {
+      if (!setting || (setting.contentType != 1 && setting.contentType != '1' && setting.contentType != 15 && setting.contentType != '15')) {
+        return;
+      }
+      this.initRandomSendFields(setting);
+      if (setting.isRandomSend == 1 && setting.randomValueList.length < 2) {
+        while (setting.randomValueList.length < 2) {
+          setting.randomValueList.push('');
+          setting.randomSalesCallAdded.push(false);
+          setting.randomUserNameCallAdded.push(false);
+        }
+      }
+    },
+    prepareSettingForSave(setting) {
+      if (!setting) {
+        return;
+      }
+      if (setting.isRandomSend == 1 && Array.isArray(setting.randomValueList)) {
+        setting.randomValueList = setting.randomValueList
+          .map(item => (item == null ? '' : item.trim()))
+          .filter(item => item !== '');
+        if (setting.randomValueList.length > 0) {
+          setting.value = setting.randomValueList[0];
+        }
+      }
+      delete setting.randomSalesCallAdded;
+      delete setting.randomUserNameCallAdded;
+    },
     handleKeydown(event, itemIndex, contentIndex, setIndex) {
 
       const setItem = this.setting[itemIndex].content[contentIndex].setting[setIndex];
@@ -2308,6 +2559,7 @@ export default {
                 }
                 if (content && Array.isArray(content.setting)) {
                   content.setting.forEach(setList => {
+                    this.normalizeSettingRandomFields(setList);
                     if (setList && !Object.hasOwn(setList, 'isSalesCallAdded')) {
                       this.$set(setList, 'isSalesCallAdded', false);
                     }

+ 215 - 31
src/views/qw/sopUserLogsInfo/sendMsgOpenTool.vue

@@ -132,33 +132,79 @@
                         </el-form-item>
                       </el-card>
                     </div>
-                    <el-input
-                      v-if="item.contentType == 1 || item.contentType == 15"
-                      v-model="item.value"
-                      type="textarea"
-                      :rows="3"
-                      placeholder="内容"
-                      style="width: 90%; margin-top: 10px;"
-                      @keydown.native="handleKeydown($event, index)"
-                      :ref="`textarea-${index}`"
-                    >
-                    </el-input>
-                    <el-link
-                      v-if="item.contentType == 1 || item.contentType == 15"
-                      type="primary"
-                      @click="toggleSalesCall(index)"
-                      style="margin-top: 10px;"
-                    >
-                      {{ item.isSalesCallAdded ? '移除#销售称呼#' : '添加#销售称呼#' }}
-                    </el-link>
-                    <el-link
-                      v-if="item.contentType == 1 || item.contentType == 15"
-                      type="primary"
-                      @click="toggleSalesCallCustomer(index)"
-                      style="margin-top: 10px;margin-left: 2%"
-                    >
-                      {{ item.isSalesCallCustomerAdded ? '移除#客户称呼#' : '添加#客户称呼#' }}
-                    </el-link>
+                    <div v-if="item.contentType == 1 || item.contentType == 15">
+                      <div style="display: flex; align-items: center; margin-bottom: 10px;">
+                        <span style="margin-right: 10px;">随机发送</span>
+                        <el-switch
+                          v-model="item.isRandomSend"
+                          active-color="#13ce66"
+                          inactive-color="#DCDFE6"
+                          :active-value="1"
+                          :inactive-value="0"
+                          @change="handleRandomSendChange(item)"
+                        />
+                        <span style="color: #999; font-size: 12px; margin-left: 10px;">
+                          开启后可为同一内容配置多条文案,每个客户随机发送其中一条
+                        </span>
+                      </div>
+
+                      <div v-if="item.isRandomSend == 1">
+                        <div
+                          v-for="(randomValue, randomIndex) in item.randomValueList"
+                          :key="randomIndex"
+                          style="margin-bottom: 12px; padding: 10px; border: 1px dashed #dcdfe6; border-radius: 4px;"
+                        >
+                          <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
+                            <span style="color: #606266; font-size: 13px;">文案 {{ randomIndex + 1 }}</span>
+                            <el-button
+                              v-if="item.randomValueList.length > 2"
+                              type="text"
+                              icon="el-icon-delete"
+                              style="color: #f56c6c;"
+                              @click="removeRandomValue(item, randomIndex)"
+                            />
+                          </div>
+                          <el-input
+                            v-model="item.randomValueList[randomIndex]"
+                            type="textarea"
+                            :rows="3"
+                            placeholder="请输入文案内容"
+                            style="width: 90%;"
+                            @keydown.native="handleRandomKeydown($event, index, randomIndex)"
+                            :ref="`textarea-${index}-${randomIndex}`"
+                          />
+                          <div style="margin-top: 8px;">
+                            <el-link type="primary" @click="toggleRandomSalesCall(index, randomIndex)">
+                              {{ item.randomSalesCallAdded && item.randomSalesCallAdded[randomIndex] ? '移除#销售称呼#' : '添加#销售称呼#' }}
+                            </el-link>
+                            <el-link type="primary" style="margin-left: 20px;" @click="toggleRandomUserNameCall(index, randomIndex)">
+                              {{ item.randomUserNameCallAdded && item.randomUserNameCallAdded[randomIndex] ? '移除#客户称呼#' : '添加#客户称呼#' }}
+                            </el-link>
+                          </div>
+                        </div>
+                        <el-link type="primary" icon="el-icon-plus" :underline="false" @click="addRandomValue(item)">
+                          添加文案
+                        </el-link>
+                      </div>
+
+                      <template v-else>
+                        <el-input
+                          v-model="item.value"
+                          type="textarea"
+                          :rows="3"
+                          placeholder="内容"
+                          style="width: 90%; margin-top: 10px;"
+                          @keydown.native="handleKeydown($event, index)"
+                          :ref="`textarea-${index}`"
+                        />
+                        <el-link type="primary" @click="toggleSalesCall(index)" style="margin-top: 10px;">
+                          {{ item.isSalesCallAdded ? '移除#销售称呼#' : '添加#销售称呼#' }}
+                        </el-link>
+                        <el-link type="primary" @click="toggleSalesCallCustomer(index)" style="margin-top: 10px;margin-left: 2%">
+                          {{ item.isSalesCallCustomerAdded ? '移除#客户称呼#' : '添加#客户称呼#' }}
+                        </el-link>
+                      </template>
+                    </div>
 
 
                     <ImageUpload v-if="item.contentType == 2 " v-model="item.imgUrl" type="image" :num="1"  :width="150" :height="150" />
@@ -500,7 +546,7 @@ export default {
       },
       // 表单参数
       form: {},
-      setting:[{contentType:'1', value: '',}],
+      setting:[{contentType:'1', value: '', isRandomSend: 0}],
       msgForm:{
         liveId:null,
         videoId:null,
@@ -727,6 +773,7 @@ export default {
       const newSetting = {
         contentType:'1',
         value: '',
+        isRandomSend: 0,
       };
       // 将新设置项添加到 content.setting 数组中
       this.setting.push(newSetting);
@@ -853,6 +900,134 @@ export default {
         textarea.setSelectionRange(cursorPosition, cursorPosition);
       });
     },
+    initRandomSendFields(item) {
+      if (!item) {
+        return;
+      }
+      if (item.isRandomSend === undefined || item.isRandomSend === null) {
+        this.$set(item, 'isRandomSend', 0);
+      }
+      if (!Array.isArray(item.randomValueList)) {
+        const initialValue = item.value || '';
+        this.$set(item, 'randomValueList', initialValue ? [initialValue] : ['', '']);
+      }
+      if (!Array.isArray(item.randomSalesCallAdded)) {
+        this.$set(item, 'randomSalesCallAdded', item.randomValueList.map(() => false));
+      }
+      if (!Array.isArray(item.randomUserNameCallAdded)) {
+        this.$set(item, 'randomUserNameCallAdded', item.randomValueList.map(() => false));
+      }
+    },
+    handleRandomSendChange(item) {
+      this.initRandomSendFields(item);
+      if (item.isRandomSend == 1) {
+        if (!item.value && item.randomValueList.every(v => !v)) {
+          this.$set(item, 'randomValueList', ['', '']);
+          this.$set(item, 'randomSalesCallAdded', [false, false]);
+          this.$set(item, 'randomUserNameCallAdded', [false, false]);
+        } else if (item.value && item.randomValueList.every(v => !v)) {
+          this.$set(item, 'randomValueList', [item.value, '']);
+          this.$set(item, 'randomSalesCallAdded', [false, false]);
+          this.$set(item, 'randomUserNameCallAdded', [false, false]);
+        } else if (item.randomValueList.length < 2) {
+          item.randomValueList.push('');
+          item.randomSalesCallAdded.push(false);
+          item.randomUserNameCallAdded.push(false);
+        }
+      } else if (item.randomValueList && item.randomValueList.length > 0) {
+        const firstValue = item.randomValueList.find(v => v && v.trim()) || item.randomValueList[0] || '';
+        this.$set(item, 'value', firstValue);
+      }
+    },
+    addRandomValue(item) {
+      this.initRandomSendFields(item);
+      item.randomValueList.push('');
+      item.randomSalesCallAdded.push(false);
+      item.randomUserNameCallAdded.push(false);
+    },
+    removeRandomValue(item, randomIndex) {
+      if (!item.randomValueList || item.randomValueList.length <= 2) {
+        return;
+      }
+      item.randomValueList.splice(randomIndex, 1);
+      item.randomSalesCallAdded.splice(randomIndex, 1);
+      item.randomUserNameCallAdded.splice(randomIndex, 1);
+    },
+    toggleRandomSalesCall(index, randomIndex) {
+      const item = this.setting[index];
+      this.initRandomSendFields(item);
+      const salesCall = '#销售称呼#';
+      const textarea = this.$refs[`textarea-${index}-${randomIndex}`] && this.$refs[`textarea-${index}-${randomIndex}`][0]?.$refs?.textarea;
+      const cursorPosition = textarea ? textarea.selectionStart : (item.randomValueList[randomIndex] || '').length;
+
+      if (item.randomSalesCallAdded[randomIndex]) {
+        this.$set(item.randomValueList, randomIndex, (item.randomValueList[randomIndex] || '').replace(new RegExp(salesCall, 'g'), ''));
+        this.$set(item.randomSalesCallAdded, randomIndex, false);
+      } else {
+        const currentValue = item.randomValueList[randomIndex] || '';
+        this.$set(item.randomValueList, randomIndex, currentValue.slice(0, cursorPosition) + salesCall + currentValue.slice(cursorPosition));
+        this.$set(item.randomSalesCallAdded, randomIndex, true);
+      }
+    },
+    toggleRandomUserNameCall(index, randomIndex) {
+      const item = this.setting[index];
+      this.initRandomSendFields(item);
+      const userCall = '#客户称呼#';
+      const textarea = this.$refs[`textarea-${index}-${randomIndex}`] && this.$refs[`textarea-${index}-${randomIndex}`][0]?.$refs?.textarea;
+      const cursorPosition = textarea ? textarea.selectionStart : (item.randomValueList[randomIndex] || '').length;
+
+      if (item.randomUserNameCallAdded[randomIndex]) {
+        this.$set(item.randomValueList, randomIndex, (item.randomValueList[randomIndex] || '').replace(new RegExp(userCall, 'g'), ''));
+        this.$set(item.randomUserNameCallAdded, randomIndex, false);
+      } else {
+        const currentValue = item.randomValueList[randomIndex] || '';
+        this.$set(item.randomValueList, randomIndex, currentValue.slice(0, cursorPosition) + userCall + currentValue.slice(cursorPosition));
+        this.$set(item.randomUserNameCallAdded, randomIndex, true);
+      }
+    },
+    handleRandomKeydown(event, index, randomIndex) {
+      if (event.key !== 'Backspace' && event.key !== 'Delete') {
+        return;
+      }
+      const item = this.setting[index];
+      this.initRandomSendFields(item);
+      const value = item.randomValueList[randomIndex] || '';
+      const cursorPosition = event.target.selectionStart;
+      const tags = ['#销售称呼#', '#客户称呼#'];
+
+      tags.forEach(tag => {
+        const tagIndex = value.indexOf(tag);
+        if (tagIndex !== -1) {
+          const tagEnd = tagIndex + tag.length;
+          if ((event.key === 'Backspace' && cursorPosition > tagIndex && cursorPosition <= tagEnd) ||
+            (event.key === 'Delete' && cursorPosition >= tagIndex && cursorPosition < tagEnd)) {
+            event.preventDefault();
+            this.$set(item.randomValueList, randomIndex, value.replace(tag, ''));
+            if (tag === '#销售称呼#') {
+              this.$set(item.randomSalesCallAdded, randomIndex, false);
+            }
+            if (tag === '#客户称呼#') {
+              this.$set(item.randomUserNameCallAdded, randomIndex, false);
+            }
+          }
+        }
+      });
+    },
+    prepareSettingForSave(item) {
+      if (!item) {
+        return;
+      }
+      if (item.isRandomSend == 1 && Array.isArray(item.randomValueList)) {
+        item.randomValueList = item.randomValueList
+          .map(v => (v == null ? '' : v.trim()))
+          .filter(v => v !== '');
+        if (item.randomValueList.length > 0) {
+          item.value = item.randomValueList[0];
+        }
+      }
+      delete item.randomSalesCallAdded;
+      delete item.randomUserNameCallAdded;
+    },
 
     handleContentTypeChange() {
       //如果是链接的才上
@@ -934,6 +1109,7 @@ export default {
 
 
           if (this.msgForm.draftStrategy==1){
+            this.setting.forEach(item => this.prepareSettingForSave(item));
             this.msgForm.setting=JSON.stringify(this.setting)
 
         let jsonSetting = JSON.parse(this.msgForm.setting);
@@ -969,8 +1145,16 @@ export default {
             }
 
             for (let i = 0; i < this.setting.length; i++) {
-              if (this.setting[i].contentType == 1 && (this.setting[i].value == null || this.setting[i].value == "")) {
-                return this.$message.error("内容不能为空")
+              if (this.setting[i].contentType == 1) {
+                this.initRandomSendFields(this.setting[i]);
+                if (this.setting[i].isRandomSend == 1) {
+                  const validRandomValues = (this.setting[i].randomValueList || []).filter(v => v != null && v.trim() !== '');
+                  if (validRandomValues.length < 2) {
+                    return this.$message.error("开启随机发送后,至少需要填写2条文案");
+                  }
+                } else if (this.setting[i].value == null || this.setting[i].value == "") {
+                  return this.$message.error("内容不能为空");
+                }
               }
               if (this.setting[i].contentType == 2 && (this.setting[i].imgUrl == null || this.setting[i].imgUrl == "")) {
                 return this.$message.error("图片不能为空")
@@ -1041,7 +1225,7 @@ export default {
           sendMsgSopType(this.msgForm).then(response => {
             this.msgSuccess("一键群发成功");
             loading.close();
-            this.setting=[];
+            this.setting=[{contentType:'1', value: '', isRandomSend: 0}];
             this.resetSendMsgSop();
           }).finally(()=>{
             loading.close();

+ 215 - 31
src/views/qw/sopUserLogsInfo/sopUserLogsInfoDetails.vue

@@ -328,33 +328,79 @@
                     </el-radio-group>
                   </el-form-item>
                   <el-form-item label="内容" style="margin-bottom: 2%" >
-                    <el-input
-                      v-if="item.contentType == 1 || item.contentType == 15"
-                      v-model="item.value"
-                      type="textarea"
-                      :rows="3"
-                      placeholder="内容"
-                      style="width: 90%; margin-top: 10px;"
-                      @keydown.native="handleKeydown($event, index)"
-                      :ref="`textarea-${index}`"
-                    >
-                    </el-input>
-                    <el-link
-                      v-if="item.contentType == 1 || item.contentType == 15"
-                      type="primary"
-                      @click="toggleSalesCall(index)"
-                      style="margin-top: 10px;"
-                    >
-                      {{ item.isSalesCallAdded ? '移除#客服称呼#' : '添加#客服称呼#' }}
-                    </el-link>
-                    <el-link
-                      v-if="item.contentType == 1 || item.contentType == 15"
-                      type="primary"
-                      @click="toggleSalesCallCustomer(index)"
-                      style="margin-top: 10px;margin-left: 2%"
-                    >
-                      {{ item.isSalesCallCustomerAdded ? '移除#客户称呼#' : '添加#客户称呼#' }}
-                    </el-link>
+                    <div v-if="item.contentType == 1 || item.contentType == 15">
+                      <div style="display: flex; align-items: center; margin-bottom: 10px;">
+                        <span style="margin-right: 10px;">随机发送</span>
+                        <el-switch
+                          v-model="item.isRandomSend"
+                          active-color="#13ce66"
+                          inactive-color="#DCDFE6"
+                          :active-value="1"
+                          :inactive-value="0"
+                          @change="handleRandomSendChange(item)"
+                        />
+                        <span style="color: #999; font-size: 12px; margin-left: 10px;">
+                          开启后可为同一内容配置多条文案,每个客户随机发送其中一条
+                        </span>
+                      </div>
+
+                      <div v-if="item.isRandomSend == 1">
+                        <div
+                          v-for="(randomValue, randomIndex) in item.randomValueList"
+                          :key="randomIndex"
+                          style="margin-bottom: 12px; padding: 10px; border: 1px dashed #dcdfe6; border-radius: 4px;"
+                        >
+                          <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px;">
+                            <span style="color: #606266; font-size: 13px;">文案 {{ randomIndex + 1 }}</span>
+                            <el-button
+                              v-if="item.randomValueList.length > 2"
+                              type="text"
+                              icon="el-icon-delete"
+                              style="color: #f56c6c;"
+                              @click="removeRandomValue(item, randomIndex)"
+                            />
+                          </div>
+                          <el-input
+                            v-model="item.randomValueList[randomIndex]"
+                            type="textarea"
+                            :rows="3"
+                            placeholder="请输入文案内容"
+                            style="width: 90%;"
+                            @keydown.native="handleRandomKeydown($event, index, randomIndex)"
+                            :ref="`textarea-${index}-${randomIndex}`"
+                          />
+                          <div style="margin-top: 8px;">
+                            <el-link type="primary" @click="toggleRandomSalesCall(index, randomIndex)">
+                              {{ item.randomSalesCallAdded && item.randomSalesCallAdded[randomIndex] ? '移除#客服称呼#' : '添加#客服称呼#' }}
+                            </el-link>
+                            <el-link type="primary" style="margin-left: 20px;" @click="toggleRandomUserNameCall(index, randomIndex)">
+                              {{ item.randomUserNameCallAdded && item.randomUserNameCallAdded[randomIndex] ? '移除#客户称呼#' : '添加#客户称呼#' }}
+                            </el-link>
+                          </div>
+                        </div>
+                        <el-link type="primary" icon="el-icon-plus" :underline="false" @click="addRandomValue(item)">
+                          添加文案
+                        </el-link>
+                      </div>
+
+                      <template v-else>
+                        <el-input
+                          v-model="item.value"
+                          type="textarea"
+                          :rows="3"
+                          placeholder="内容"
+                          style="width: 90%; margin-top: 10px;"
+                          @keydown.native="handleKeydown($event, index)"
+                          :ref="`textarea-${index}`"
+                        />
+                        <el-link type="primary" @click="toggleSalesCall(index)" style="margin-top: 10px;">
+                          {{ item.isSalesCallAdded ? '移除#客服称呼#' : '添加#客服称呼#' }}
+                        </el-link>
+                        <el-link type="primary" @click="toggleSalesCallCustomer(index)" style="margin-top: 10px;margin-left: 2%">
+                          {{ item.isSalesCallCustomerAdded ? '移除#客户称呼#' : '添加#客户称呼#' }}
+                        </el-link>
+                      </template>
+                    </div>
 
 
                     <ImageUpload v-if="item.contentType == 2 " v-model="item.imgUrl" type="image" :num="1"  :width="150" :height="150" />
@@ -814,7 +860,7 @@ export default {
       // 表单参数
       form: {},
       updateLogsInfoFrom:{},
-      setting:[{contentType:'1', value: '',}],
+      setting:[{contentType:'1', value: '', isRandomSend: 0}],
       msgForm:{
         liveId:null,
         // liveMsgType:null,
@@ -1299,6 +1345,7 @@ export default {
       const newSetting = {
         contentType:'1',
         value: '',
+        isRandomSend: 0,
       };
       // 将新设置项添加到 content.setting 数组中
       this.setting.push(newSetting);
@@ -1425,6 +1472,134 @@ export default {
         textarea.setSelectionRange(cursorPosition, cursorPosition);
       });
     },
+    initRandomSendFields(item) {
+      if (!item) {
+        return;
+      }
+      if (item.isRandomSend === undefined || item.isRandomSend === null) {
+        this.$set(item, 'isRandomSend', 0);
+      }
+      if (!Array.isArray(item.randomValueList)) {
+        const initialValue = item.value || '';
+        this.$set(item, 'randomValueList', initialValue ? [initialValue] : ['', '']);
+      }
+      if (!Array.isArray(item.randomSalesCallAdded)) {
+        this.$set(item, 'randomSalesCallAdded', item.randomValueList.map(() => false));
+      }
+      if (!Array.isArray(item.randomUserNameCallAdded)) {
+        this.$set(item, 'randomUserNameCallAdded', item.randomValueList.map(() => false));
+      }
+    },
+    handleRandomSendChange(item) {
+      this.initRandomSendFields(item);
+      if (item.isRandomSend == 1) {
+        if (!item.value && item.randomValueList.every(v => !v)) {
+          this.$set(item, 'randomValueList', ['', '']);
+          this.$set(item, 'randomSalesCallAdded', [false, false]);
+          this.$set(item, 'randomUserNameCallAdded', [false, false]);
+        } else if (item.value && item.randomValueList.every(v => !v)) {
+          this.$set(item, 'randomValueList', [item.value, '']);
+          this.$set(item, 'randomSalesCallAdded', [false, false]);
+          this.$set(item, 'randomUserNameCallAdded', [false, false]);
+        } else if (item.randomValueList.length < 2) {
+          item.randomValueList.push('');
+          item.randomSalesCallAdded.push(false);
+          item.randomUserNameCallAdded.push(false);
+        }
+      } else if (item.randomValueList && item.randomValueList.length > 0) {
+        const firstValue = item.randomValueList.find(v => v && v.trim()) || item.randomValueList[0] || '';
+        this.$set(item, 'value', firstValue);
+      }
+    },
+    addRandomValue(item) {
+      this.initRandomSendFields(item);
+      item.randomValueList.push('');
+      item.randomSalesCallAdded.push(false);
+      item.randomUserNameCallAdded.push(false);
+    },
+    removeRandomValue(item, randomIndex) {
+      if (!item.randomValueList || item.randomValueList.length <= 2) {
+        return;
+      }
+      item.randomValueList.splice(randomIndex, 1);
+      item.randomSalesCallAdded.splice(randomIndex, 1);
+      item.randomUserNameCallAdded.splice(randomIndex, 1);
+    },
+    toggleRandomSalesCall(index, randomIndex) {
+      const item = this.setting[index];
+      this.initRandomSendFields(item);
+      const salesCall = '#客服称呼#';
+      const textarea = this.$refs[`textarea-${index}-${randomIndex}`] && this.$refs[`textarea-${index}-${randomIndex}`][0]?.$refs?.textarea;
+      const cursorPosition = textarea ? textarea.selectionStart : (item.randomValueList[randomIndex] || '').length;
+
+      if (item.randomSalesCallAdded[randomIndex]) {
+        this.$set(item.randomValueList, randomIndex, (item.randomValueList[randomIndex] || '').replace(new RegExp(salesCall, 'g'), ''));
+        this.$set(item.randomSalesCallAdded, randomIndex, false);
+      } else {
+        const currentValue = item.randomValueList[randomIndex] || '';
+        this.$set(item.randomValueList, randomIndex, currentValue.slice(0, cursorPosition) + salesCall + currentValue.slice(cursorPosition));
+        this.$set(item.randomSalesCallAdded, randomIndex, true);
+      }
+    },
+    toggleRandomUserNameCall(index, randomIndex) {
+      const item = this.setting[index];
+      this.initRandomSendFields(item);
+      const userCall = '#客户称呼#';
+      const textarea = this.$refs[`textarea-${index}-${randomIndex}`] && this.$refs[`textarea-${index}-${randomIndex}`][0]?.$refs?.textarea;
+      const cursorPosition = textarea ? textarea.selectionStart : (item.randomValueList[randomIndex] || '').length;
+
+      if (item.randomUserNameCallAdded[randomIndex]) {
+        this.$set(item.randomValueList, randomIndex, (item.randomValueList[randomIndex] || '').replace(new RegExp(userCall, 'g'), ''));
+        this.$set(item.randomUserNameCallAdded, randomIndex, false);
+      } else {
+        const currentValue = item.randomValueList[randomIndex] || '';
+        this.$set(item.randomValueList, randomIndex, currentValue.slice(0, cursorPosition) + userCall + currentValue.slice(cursorPosition));
+        this.$set(item.randomUserNameCallAdded, randomIndex, true);
+      }
+    },
+    handleRandomKeydown(event, index, randomIndex) {
+      if (event.key !== 'Backspace' && event.key !== 'Delete') {
+        return;
+      }
+      const item = this.setting[index];
+      this.initRandomSendFields(item);
+      const value = item.randomValueList[randomIndex] || '';
+      const cursorPosition = event.target.selectionStart;
+      const tags = ['#客服称呼#', '#客户称呼#'];
+
+      tags.forEach(tag => {
+        const tagIndex = value.indexOf(tag);
+        if (tagIndex !== -1) {
+          const tagEnd = tagIndex + tag.length;
+          if ((event.key === 'Backspace' && cursorPosition > tagIndex && cursorPosition <= tagEnd) ||
+            (event.key === 'Delete' && cursorPosition >= tagIndex && cursorPosition < tagEnd)) {
+            event.preventDefault();
+            this.$set(item.randomValueList, randomIndex, value.replace(tag, ''));
+            if (tag === '#客服称呼#') {
+              this.$set(item.randomSalesCallAdded, randomIndex, false);
+            }
+            if (tag === '#客户称呼#') {
+              this.$set(item.randomUserNameCallAdded, randomIndex, false);
+            }
+          }
+        }
+      });
+    },
+    prepareSettingForSave(item) {
+      if (!item) {
+        return;
+      }
+      if (item.isRandomSend == 1 && Array.isArray(item.randomValueList)) {
+        item.randomValueList = item.randomValueList
+          .map(v => (v == null ? '' : v.trim()))
+          .filter(v => v !== '');
+        if (item.randomValueList.length > 0) {
+          item.value = item.randomValueList[0];
+        }
+      }
+      delete item.randomSalesCallAdded;
+      delete item.randomUserNameCallAdded;
+    },
 
     handleContentTypeChange() {
 
@@ -1613,6 +1788,7 @@ export default {
       this.$refs["msgForm"].validate(valid => {
         if (valid) {
 
+          this.setting.forEach(item => this.prepareSettingForSave(item));
           this.msgForm.setting=JSON.stringify(this.setting)
           this.msgForm.ids=this.ids;
           this.msgForm.sopId=this.queryParams.sopId;
@@ -1654,8 +1830,16 @@ export default {
           }
 
           for (let i = 0; i < this.setting.length; i++) {
-            if (this.setting[i].contentType == 1 && (this.setting[i].value == null || this.setting[i].value == "")) {
-              return this.$message.error("内容不能为空")
+            if (this.setting[i].contentType == 1) {
+              this.initRandomSendFields(this.setting[i]);
+              if (this.setting[i].isRandomSend == 1) {
+                const validRandomValues = (this.setting[i].randomValueList || []).filter(v => v != null && v.trim() !== '');
+                if (validRandomValues.length < 2) {
+                  return this.$message.error("开启随机发送后,至少需要填写2条文案");
+                }
+              } else if (this.setting[i].value == null || this.setting[i].value == "") {
+                return this.$message.error("内容不能为空");
+              }
             }
             if (this.setting[i].contentType == 2 && (this.setting[i].imgUrl == null || this.setting[i].imgUrl == "")) {
               return this.$message.error("图片不能为空")
@@ -1702,7 +1886,7 @@ export default {
           sendMsgSop(this.msgForm).then(response => {
             this.msgSuccess("一键群发成功");
             loading.close();
-            this.setting=[];
+            this.setting=[{contentType:'1', value: '', isRandomSend: 0}];
             this.msgForm = {
               videoId:null,
               courseId:null,