Просмотр исходного кода

Merge branch 'master' of http://1.14.104.71:10880/root/ylrz_his_scrm_companyUI

hulin 13 часов назад
Родитель
Сommit
ed4c745bce

+ 1 - 1
.editorconfig

@@ -8,7 +8,7 @@ charset = utf-8
 # 缩进风格,可选space、tab
 # 缩进风格,可选space、tab
 indent_style = space
 indent_style = space
 # 缩进的空格数
 # 缩进的空格数
-indent_size = 2
+indent_size = 4
 # 结尾换行符,可选lf、cr、crlf
 # 结尾换行符,可选lf、cr、crlf
 end_of_line = lf
 end_of_line = lf
 # 在文件结尾插入新行
 # 在文件结尾插入新行

+ 78 - 0
src/api/crm/customerProperty.js

@@ -0,0 +1,78 @@
+import request from '@/utils/request'
+
+export function listByCustomerId(customerId) {
+  return request({
+    url: '/crm/customerProperty/list/' + customerId,
+    method: 'get'
+  })
+}
+
+export function getById(id) {
+  return request({
+    url: '/crm/customerProperty/' + id,
+    method: 'get'
+  })
+}
+
+export function add(data) {
+  return request({
+    url: '/crm/customerProperty/add',
+    method: 'post',
+    data: data
+  })
+}
+
+export function addOrUpdate(data) {
+  return request({
+    url: '/crm/customerProperty/addOrUpdate',
+    method: 'post',
+    data: data
+  })
+}
+
+export function addByTemplateId(customerId, templateId, propertyValue) {
+  return request({
+    url: '/crm/customerProperty/addByTemplateId',
+    method: 'post',
+    params: { customerId, templateId, propertyValue }
+  })
+}
+
+export function addOrUpdateByTemplateId(customerId, templateId, propertyValue) {
+  return request({
+    url: '/crm/customerProperty/addOrUpdateByTemplateId',
+    method: 'post',
+    params: { customerId, templateId, propertyValue }
+  })
+}
+
+export function batchAddByTemplateIds(customerId, propertyMap) {
+  return request({
+    url: '/crm/customerProperty/batchAddByTemplateIds/' + customerId,
+    method: 'post',
+    data: propertyMap
+  })
+}
+
+export function update(data) {
+  return request({
+    url: '/crm/customerProperty',
+    method: 'put',
+    data: data
+  })
+}
+
+export function del(ids) {
+  return request({
+    url: '/crm/customerProperty/' + ids,
+    method: 'delete'
+  })
+}
+
+export function deleteByPropertyId(customerId, propertyId) {
+  return request({
+    url: '/crm/customerProperty/deleteByPropertyId',
+    method: 'delete',
+    params: { customerId, propertyId }
+  })
+}

+ 47 - 0
src/api/crm/propertyTemplate.js

@@ -0,0 +1,47 @@
+import request from '@/utils/request'
+
+export function listPropertyTemplate(query) {
+  return request({
+    url: '/crm/customerPropertyTemplate/list',
+    method: 'get',
+    params: query
+  })
+}
+
+export function getPropertyTemplate(id) {
+  return request({
+    url: '/crm/customerPropertyTemplate/' + id,
+    method: 'get'
+  })
+}
+
+export function addPropertyTemplate(data) {
+  return request({
+    url: '/crm/customerPropertyTemplate',
+    method: 'post',
+    data: data
+  })
+}
+
+export function updatePropertyTemplate(data) {
+  return request({
+    url: '/crm/customerPropertyTemplate',
+    method: 'put',
+    data: data
+  })
+}
+
+export function delPropertyTemplate(id) {
+  return request({
+    url: '/crm/customerPropertyTemplate/' + id,
+    method: 'delete'
+  })
+}
+
+export function exportPropertyTemplate(query) {
+  return request({
+    url: '/crm/customerPropertyTemplate/export',
+    method: 'get',
+    params: query
+  })
+}

+ 93 - 0
src/views/company/companyConfig/index.vue

@@ -1,6 +1,54 @@
 <template>
 <template>
   <div class="app-container">
   <div class="app-container">
       <el-tabs v-model="activeName"    >
       <el-tabs v-model="activeName"    >
+        <el-tab-pane label="CID配置" name="cidConfig">
+          <el-form ref="cidConfigForm" :model="cidConfig" label-width="200px">
+            <el-form-item label="是否开启手机号配置" prop="enablePhoneConfig">
+              <el-switch v-model="cidConfig.enablePhoneConfig"></el-switch>
+            </el-form-item>
+
+            <template v-if="cidConfig.enablePhoneConfig">
+              <el-form-item label="生成条数" prop="generateCount">
+                <el-input-number
+                  v-model="cidConfig.generateCount"
+                  :min="1"
+                  :step="1"
+                  :precision="0"
+                  placeholder="请输入生成条数"
+                ></el-input-number>
+              </el-form-item>
+
+              <el-form-item label="开始位置" prop="startIndex">
+                <el-input-number
+                  v-model="cidConfig.startIndex"
+                  :min="1"
+                  :max="11"
+                  :step="1"
+                  :precision="0"
+                  placeholder="例如: 1"
+                ></el-input-number>
+                <span class="tip-text">(从第几位开始生成)</span>
+              </el-form-item>
+
+              <el-form-item label="结束位置" prop="endIndex">
+                <el-input-number
+                  v-model="cidConfig.endIndex"
+                  :min="1"
+                  :max="11"
+                  :step="1"
+                  :precision="0"
+                  placeholder="例如: 11"
+                ></el-input-number>
+                <span class="tip-text">(到第几位结束)</span>
+              </el-form-item>
+            </template>
+
+            <div class="line"></div>
+            <div style="float:right;margin-right:20px">
+              <el-button type="primary" @click="onSubmitCidConfig">提交</el-button>
+            </div>
+          </el-form>
+        </el-tab-pane>
           <el-tab-pane label="客户管理配置" name="sysConfig">
           <el-tab-pane label="客户管理配置" name="sysConfig">
           <el-form ref="sysConfig" :model="sysConfig" label-width="120px">
           <el-form ref="sysConfig" :model="sysConfig" label-width="120px">
             <el-form-item label="公海回收规则">
             <el-form-item label="公海回收规则">
@@ -262,6 +310,12 @@ export default {
   },
   },
   data() {
   data() {
     return {
     return {
+      cidConfig: {
+        enablePhoneConfig: false,
+        generateCount: 0,
+        startIndex: 1,
+        endIndex: 5
+      },
       adminIsShow: false,
       adminIsShow: false,
       company:null,
       company:null,
       statusOptions:[],
       statusOptions:[],
@@ -319,6 +373,7 @@ export default {
     this.getConfigKey("sys:AiKf:config");
     this.getConfigKey("sys:AiKf:config");
     this.getConfigKey("company:admin:show");
     this.getConfigKey("company:admin:show");
     this.getConfigKey("redPacket:config");
     this.getConfigKey("redPacket:config");
+    this.getConfigKey("cId.config");
     this.getDicts("sys_company_status").then((response) => {
     this.getDicts("sys_company_status").then((response) => {
       this.statusOptions = response.data;
       this.statusOptions = response.data;
     });
     });
@@ -464,6 +519,11 @@ export default {
               if(response.data.configValue!=null){
               if(response.data.configValue!=null){
                 this.redPacketConfig=JSON.parse(response.data.configValue);
                 this.redPacketConfig=JSON.parse(response.data.configValue);
               }
               }
+            }else if(key == "cId.config"){
+              this.cidConfigForm = response.data;
+              if(response.data.configValue != null){
+                this.cidConfig = JSON.parse(response.data.configValue);
+              }
             }
             }
         });
         });
     },
     },
@@ -540,6 +600,39 @@ export default {
         }
         }
       })
       })
     },
     },
+    onSubmitCidConfig() {
+      if (this.cidConfig.enablePhoneConfig) {
+        if (!this.cidConfig.generateCount || this.cidConfig.generateCount < 1) {
+          this.msgError('生成条数不能为空且不能小于1');
+          return;
+        }
+        if (!this.cidConfig.startIndex || this.cidConfig.startIndex < 1 || this.cidConfig.startIndex > 11) {
+          this.msgError('开始位置不能为空,且必须在1到11之间');
+          return;
+        }
+        if (!this.cidConfig.endIndex || this.cidConfig.endIndex < 1 || this.cidConfig.endIndex > 11) {
+          this.msgError('结束位置不能为空,且必须在1到11之间');
+          return;
+        }
+        if (this.cidConfig.startIndex > this.cidConfig.endIndex) {
+          this.msgError('开始位置不能大于结束位置');
+          return;
+        }
+
+        if (this.cidConfig.endIndex - this.cidConfig.startIndex < 4) {
+          this.msgError('开始和结束位置之间的位数不能少于4位');
+          return;
+        }
+      }
+
+      this.cidConfigForm.configValue = JSON.stringify(this.cidConfig);
+      updateConfig(this.cidConfigForm).then(response => {
+        if (response.code === 200) {
+          this.msgSuccess("修改成功");
+          this.getConfigKey("cId.config");
+        }
+      });
+    },
   }
   }
 };
 };
 </script>
 </script>

+ 132 - 21
src/views/company/companyVoiceRobotic/index.vue

@@ -10,14 +10,14 @@
           @keyup.enter.native="handleQuery"
           @keyup.enter.native="handleQuery"
         />
         />
       </el-form-item>
       </el-form-item>
-      <el-form-item label="机器人" prop="robot">
+      <!-- <el-form-item label="机器人" prop="robot">
         <el-select v-model="queryParams.robot" filterable clearable>
         <el-select v-model="queryParams.robot" filterable clearable>
           <el-option v-for="item in robotList" :label="item.name + '('+item.num+')'" :value="item.id"/>
           <el-option v-for="item in robotList" :label="item.name + '('+item.num+')'" :value="item.id"/>
         </el-select>
         </el-select>
-      </el-form-item>
-      <el-form-item label="话术" prop="dialogId">
-        <el-select v-model="queryParams.dialogId" filterable clearable>
-          <el-option v-for="item in dialogList" :label="item.name" :value="item.id"/>
+      </el-form-item> -->
+      <el-form-item label="任务类型" prop="taskType">
+        <el-select v-model="queryParams.taskType" filterable clearable>
+          <el-option v-for="item in taskTypeList" :key="item.id" :label="item.name" :value="item.id"/>
         </el-select>
         </el-select>
       </el-form-item>
       </el-form-item>
       <el-form-item>
       <el-form-item>
@@ -87,9 +87,15 @@
       </el-table-column>
       </el-table-column>
       <el-table-column label="添加类型" align="center" prop="isWeCom">
       <el-table-column label="添加类型" align="center" prop="isWeCom">
         <template slot-scope="scope">
         <template slot-scope="scope">
-          <el-tag v-if="scope.row.isWeCom == 1">个微</el-tag>
+          <!-- <el-tag v-if="scope.row.isWeCom == 1">个微</el-tag> -->
           <el-tag v-if="scope.row.isWeCom == 2">企微</el-tag>
           <el-tag v-if="scope.row.isWeCom == 2">企微</el-tag>
         </template>
         </template>
+      </el-table-column>
+       <el-table-column label="任务类型" align="center" prop="taskType">
+        <template slot-scope="scope">
+          <el-tag v-if="scope.row.taskType == 1">普通任务</el-tag>
+          <el-tag v-if="scope.row.taskType == 2">场景任务</el-tag>
+        </template>
       </el-table-column>
       </el-table-column>
       <el-table-column label="任务状态" align="center">
       <el-table-column label="任务状态" align="center">
         <template slot-scope="scope">
         <template slot-scope="scope">
@@ -139,13 +145,13 @@
             v-if="scope.row.taskStatus == 0"
             v-if="scope.row.taskStatus == 0"
             @click="taskRunFun(scope.row.id)"
             @click="taskRunFun(scope.row.id)"
           >启动任务</el-button>
           >启动任务</el-button>
-          <el-button
+          <!-- <el-button
             size="mini"
             size="mini"
             type="text"
             type="text"
             v-if="statusObj.hasOwnProperty(scope.row.taskId) && (statusObj[scope.row.taskId].runningStatus == 0 || statusObj[scope.row.taskId].runningStatus == 3)"
             v-if="statusObj.hasOwnProperty(scope.row.taskId) && (statusObj[scope.row.taskId].runningStatus == 0 || statusObj[scope.row.taskId].runningStatus == 3)"
             @click="startRoboticFun(scope.row.taskId)"
             @click="startRoboticFun(scope.row.taskId)"
             v-hasPermi="['system:companyVoiceRobotic:list']"
             v-hasPermi="['system:companyVoiceRobotic:list']"
-          >开启外呼任务</el-button>
+          >开启外呼任务</el-button> -->
           <el-button
           <el-button
             size="mini"
             size="mini"
             type="text"
             type="text"
@@ -177,7 +183,19 @@
     <el-drawer size="45%" :title="title" :visible.sync="open" width="500px" append-to-body class="task-form-drawer">
     <el-drawer size="45%" :title="title" :visible.sync="open" width="500px" append-to-body class="task-form-drawer">
       <div class="drawer-content">
       <div class="drawer-content">
         <el-form ref="form" :model="form" :rules="rules" label-width="90px" class="task-form">
         <el-form ref="form" :model="form" :rules="rules" label-width="90px" class="task-form">
-          <div class="form-section">
+            <div class="form-section" >
+               <div class="section-title">
+                <i class="el-icon-document"></i>
+                <span>任务类型</span>
+              </div>
+             <el-form-item label="任务类型" prop="taskType">
+              <el-select v-model="form.taskType" filterable placeholder="请选择任务类型" @change="taskTypeChange()">
+                <el-option v-for="item in taskTypeList" :key="item.id" :label="item.name" :value="item.id"/>
+              </el-select>
+            </el-form-item>
+             </div>
+         
+          <div class="form-section" v-if="form.taskType === 1" >
             <div class="section-title">
             <div class="section-title">
               <i class="el-icon-document"></i>
               <i class="el-icon-document"></i>
               <span>基本信息</span>
               <span>基本信息</span>
@@ -205,10 +223,11 @@
                   placeholder="任务运行开始时间"
                   placeholder="任务运行开始时间"
                    style="width: 100%;"
                    style="width: 100%;"
                   v-model="form.runtimeRangeStart"
                   v-model="form.runtimeRangeStart"
+                  key="runtimeRangeStart"
                   :picker-options="{
                   :picker-options="{
-                    start: '07:30',
-                    step: '00:10',
-                    end: '20:00'
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00'
                   }">
                   }">
                 </el-time-select>
                 </el-time-select>
                   </el-form-item>
                   </el-form-item>
@@ -220,10 +239,11 @@
                       style="width: 100%;"
                       style="width: 100%;"
                   placeholder="任务运行结束时间"
                   placeholder="任务运行结束时间"
                   v-model="form.runtimeRangeEnd"
                   v-model="form.runtimeRangeEnd"
+                  key="runtimeRangeEnd"
                   :picker-options="{
                   :picker-options="{
-                    start: '08:30',
-                    step: '00:10',
-                    end: '21:00',
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00',
                     minTime: form.runtimeRangeStart
                     minTime: form.runtimeRangeStart
                   }">
                   }">
                 </el-time-select>
                 </el-time-select>
@@ -231,6 +251,60 @@
                 </el-col>
                 </el-col>
               </el-form-item>
               </el-form-item>
           </div>
           </div>
+          <!-- 场景任务 -->
+          <div class="form-section" v-if="form.taskType === 2" >
+            <div class="section-title">
+              <i class="el-icon-document"></i>
+              <span>基本信息</span>
+            </div>
+            <el-form-item label="任务名称" prop="name">
+              <el-input v-model="form.name" placeholder="请输入任务名称" clearable/>
+            </el-form-item>
+            <el-form-item label="任务流程" prop="companyAiWorkflowId">
+              <el-select v-model="form.companyAiWorkflowId" filterable placeholder="请选择任务流程">
+                <el-option v-for="item in workflowList" :key="item.value" :label="item.label" :value="item.value"/>
+              </el-select>
+            </el-form-item>
+             <el-form-item label="场景类型" prop="sceneType">
+              <el-select v-model="form.sceneType" filterable placeholder="请选择场景类型">
+                <el-option v-for="opt in sceneList" :key="opt.dictValue" :label="opt.dictLabel" :value="opt.dictValue"/>
+              </el-select>
+            </el-form-item>
+            <el-form-item label="适用时间" required>
+                <el-col :span="11">
+                  <el-form-item prop="availableStartTime">
+                     <el-time-select
+                  placeholder="场景适用开始时间"
+                   style="width: 100%;"
+                  v-model="form.availableStartTime"
+                  key="availableStartTime"
+                  :picker-options="{
+                    start: '00:00',
+                    step: '00:30',
+                    end: '24:00'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+                <el-col class="line" :span="2" style="text-align: center">-</el-col>
+                <el-col :span="11">
+                  <el-form-item prop="availableEndTime">
+                   <el-time-select
+                  style="width: 100%;"
+                  placeholder="场景适用结束时间"
+                  v-model="form.availableEndTime"
+                  key="availableEndTime"
+                  :picker-options="{
+                    start: '00:00',
+                    step: '00:30',
+                    end: '24:00'
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+              </el-form-item>
+          
+          </div>
 
 
           <div class="form-section">
           <div class="form-section">
             <div class="section-title">
             <div class="section-title">
@@ -258,10 +332,10 @@
             </div>
             </div>
             <el-form-item label="添加类型" prop="isWeCom">
             <el-form-item label="添加类型" prop="isWeCom">
               <el-radio-group v-model="form.isWeCom">
               <el-radio-group v-model="form.isWeCom">
-                <el-radio :label="1" border>
+                <!-- <el-radio :label="1" border>
                   <i class="el-icon-pie-chart"></i>
                   <i class="el-icon-pie-chart"></i>
                   个微
                   个微
-                </el-radio>
+                </el-radio> -->
                 <el-radio :label="2" border>
                 <el-radio :label="2" border>
                   <i class="el-icon-star-on"></i>
                   <i class="el-icon-star-on"></i>
                   企微
                   企微
@@ -592,6 +666,8 @@ export default {
   components: { draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo},
   components: { draggable, customerDetails, customerSelect, qwUserSelect,qwUserSelectTwo},
   data() {
   data() {
     return {
     return {
+      taskType:1,
+      taskTypeList:[{id:1,name:"普通任务"},{id:2,name:"场景任务"}],
       currentCompanyId:null,
       currentCompanyId:null,
       // 遮罩层
       // 遮罩层
       loading: true,
       loading: true,
@@ -644,10 +720,13 @@ export default {
         weekDay2: null,
         weekDay2: null,
         startTime2: null,
         startTime2: null,
         endTime2: null,
         endTime2: null,
-        createUser: null
+        createUser: null,
+        taskType:null
       },
       },
       // 表单参数
       // 表单参数
-      form: {},
+      form: {
+        taskType:1
+      },
       taskFlowList: [{key: "cellPhone", value: "外呼"}, {key: "qwAddWx", value: "企微加微"}, {key: "sendMsg", value: "发短信"}, {key: "addWx", value: "加微"}],
       taskFlowList: [{key: "cellPhone", value: "外呼"}, {key: "qwAddWx", value: "企微加微"}, {key: "sendMsg", value: "发短信"}, {key: "addWx", value: "加微"}],
       taskFlowMap: {cellPhone: "外呼", sendMsg: "发短信", addWx: "加微", qwAddWx: "企微加微"},
       taskFlowMap: {cellPhone: "外呼", sendMsg: "发短信", addWx: "加微", qwAddWx: "企微加微"},
       statusObj: {},
       statusObj: {},
@@ -709,6 +788,9 @@ export default {
       },
       },
       // 表单校验
       // 表单校验
       rules: {
       rules: {
+        taskType:[
+           { required: true, message: '请选择任务类型', trigger: 'change' },
+        ],
         name: [
         name: [
             { required: true, message: '请输入任务名称', trigger: 'blur' },
             { required: true, message: '请输入任务名称', trigger: 'blur' },
           ],
           ],
@@ -722,7 +804,8 @@ export default {
              { required: true, message: '请选择任务运行结束时间', trigger: 'change' }
              { required: true, message: '请选择任务运行结束时间', trigger: 'change' }
           ]
           ]
       },
       },
-      smsTempList:[]
+      smsTempList:[],
+      sceneList:[]
     };
     };
   },
   },
   created() {
   created() {
@@ -747,6 +830,10 @@ export default {
     getDicts("customer_intention_level").then(e => {
     getDicts("customer_intention_level").then(e => {
       this.levelList = e.data;
       this.levelList = e.data;
     })
     })
+    getDicts("task_scene_type").then(e => {
+      console.log(e);
+      this.sceneList = e.data;
+    })
     this.getList();
     this.getList();
     this.getSmsTempDropList();
     this.getSmsTempDropList();
 
 
@@ -855,6 +942,9 @@ export default {
         runtimeRangeStart:null,
         runtimeRangeStart:null,
         runtimeRangeEnd:null,
         runtimeRangeEnd:null,
         isWeCom: 1,
         isWeCom: 1,
+        taskType:1,
+        availableStartTime :null,
+        availableEndTime: null
       };
       };
       this.resetForm("form");
       this.resetForm("form");
     },
     },
@@ -877,8 +967,11 @@ export default {
     /** 新增按钮操作 */
     /** 新增按钮操作 */
     handleAdd() {
     handleAdd() {
       this.reset();
       this.reset();
+      optionList().then(e => {
+      this.workflowList = e.data;
+      })
       this.open = true;
       this.open = true;
-      this.title = "添加机器人外呼任务";
+      this.title = "添加任务";
     },
     },
     /** 修改按钮操作 */
     /** 修改按钮操作 */
     handleUpdate(row) {
     handleUpdate(row) {
@@ -1185,6 +1278,24 @@ export default {
     getAvatarColor(index) {
     getAvatarColor(index) {
       const colors = ['#1890ff', '#52c41a', '#faad14', '#722ed1', '#eb2f96'];
       const colors = ['#1890ff', '#52c41a', '#faad14', '#722ed1', '#eb2f96'];
       return colors[index % colors.length];
       return colors[index % colors.length];
+    },
+    /**
+     * 选择任务类型
+     */
+    taskTypeChange(){
+      console.log(this.form.taskType);
+      this.form.runtimeRangeStart=null;
+      this.form.runtimeRangeEnd= null;
+      this.form.userIds= [];
+      this.form.userNames= [];
+      this.form.userTableList= [];
+      this.form.companyAiWorkflowId = null;
+      this.form.name = null;
+      this.form.availableStartTime = null;
+      this.form.availableEndTime = null;
+      this.$nextTick(() => {
+      this.$refs.form.clearValidate();
+      })
     }
     }
   }
   }
 };
 };

+ 38 - 1
src/views/company/companyWorkflow/design.vue

@@ -350,8 +350,24 @@
               <div class="section-title">
               <div class="section-title">
                 <i class="el-icon-phone"></i>外呼配置
                 <i class="el-icon-phone"></i>外呼配置
               </div>
               </div>
+              <el-form-item label="外呼模式">
+                <el-select
+                  v-model="selectedNode.nodeConfig.callMode"
+                  filterable
+                  placeholder="请选择外呼模式"
+                  @change="checkCallMode()"
+                >
+                  <el-option
+                    v-for="item in callMode"
+                    :key="item.id"
+                    :label="item.name"
+                    :value="item.id"
+                  />
+                </el-select>
+              </el-form-item>
               <el-form-item label="外呼网关">
               <el-form-item label="外呼网关">
                 <el-select
                 <el-select
+                  :disabled="editAiDisable"
                   v-model="selectedNode.nodeConfig.gatewayId"
                   v-model="selectedNode.nodeConfig.gatewayId"
                   filterable
                   filterable
                   placeholder="请选择外呼网关"
                   placeholder="请选择外呼网关"
@@ -367,6 +383,7 @@
               </el-form-item>
               </el-form-item>
               <el-form-item label="大模型">
               <el-form-item label="大模型">
                 <el-select
                 <el-select
+                  :disabled="editAiDisable"
                   v-model="selectedNode.nodeConfig.llmAccountId"
                   v-model="selectedNode.nodeConfig.llmAccountId"
                   filterable
                   filterable
                   placeholder="请选择大模型"
                   placeholder="请选择大模型"
@@ -382,6 +399,7 @@
               </el-form-item>
               </el-form-item>
               <el-form-item label="音色">
               <el-form-item label="音色">
                 <el-select
                 <el-select
+                  :disabled="editAiDisable"
                   v-model="selectedNode.nodeConfig.voiceCode"
                   v-model="selectedNode.nodeConfig.voiceCode"
                   filterable
                   filterable
                   placeholder="请选择音色"
                   placeholder="请选择音色"
@@ -598,7 +616,14 @@ export default {
   name: 'WorkflowDesign',
   name: 'WorkflowDesign',
   data() {
   data() {
     return {
     return {
-
+      callMode:[{
+        id:1,
+        name:"人工外呼"
+      },{
+        id:2,
+        name:"ai自动外呼"
+      }],
+      editAiDisable:true,
       wxDialogList: [],
       wxDialogList: [],
       // 工作流ID
       // 工作流ID
       workflowId: null,
       workflowId: null,
@@ -726,6 +751,12 @@ export default {
       // 强制触发Vue响应式更新
       // 强制触发Vue响应式更新
       this.$forceUpdate()
       this.$forceUpdate()
     },
     },
+    //选择外呼模式
+    checkCallMode(){
+      // callMode: 1=人工外呼, 2=AI自动外呼
+      // 人工外呼时禁用AI相关配置,AI外呼时启用
+      this.editAiDisable = this.selectedNode.nodeConfig.callMode !== 2;
+    },
 
 
     // 点击画布时聚焦
     // 点击画布时聚焦
     onCanvasClick() {
     onCanvasClick() {
@@ -765,6 +796,12 @@ export default {
         if (!this.selectedNode.nodeConfig.recallTimes && this.selectedNode.nodeConfig.recallTimes !== 0) {
         if (!this.selectedNode.nodeConfig.recallTimes && this.selectedNode.nodeConfig.recallTimes !== 0) {
           this.$set(this.selectedNode.nodeConfig, 'recallTimes', 0)
           this.$set(this.selectedNode.nodeConfig, 'recallTimes', 0)
         }
         }
+        // 初始化外呼模式,默认为人工外呼(1)
+        if (!this.selectedNode.nodeConfig.callMode) {
+          this.$set(this.selectedNode.nodeConfig, 'callMode', 1)
+        }
+        // 根据当前外呼模式设置AI配置的禁用状态
+        this.editAiDisable = this.selectedNode.nodeConfig.callMode !== 2;
 
 
         getTypes().then(e => {
         getTypes().then(e => {
           this.robotList = e.robot;
           this.robotList = e.robot;

+ 2 - 1
src/views/components/QwUserSelectTwo.vue

@@ -64,7 +64,8 @@
       </el-card>
       </el-card>
 
 
       <!-- 底部按钮 -->
       <!-- 底部按钮 -->
-      <div class="footer-actions">
+      <div class="footer-actions" style="    text-align: right;
+    margin: 20px;">
         <el-button @click="shows = false">取 消</el-button>
         <el-button @click="shows = false">取 消</el-button>
         <el-button type="primary" @click="submitForm">确定选择</el-button>
         <el-button type="primary" @click="submitForm">确定选择</el-button>
       </div>
       </div>

+ 466 - 0
src/views/crm/components/AiTagPanel.vue

@@ -0,0 +1,466 @@
+<template>
+  <div class="info-section">
+    <div class="section-header">
+      <span class="section-title"><i class="el-icon-magic-sticker"></i> AI 标签</span>
+      <div class="header-actions">
+        <el-button type="primary" size="mini" icon="el-icon-plus" @click="handleAddTag">添加维度</el-button>
+      </div>
+    </div>
+    <div class="section-content" v-loading="loading">
+      <div v-if="tagList.length === 0" class="empty-tips">
+        <span>暂无标签,点击"添加维度"开始设置</span>
+      </div>
+
+      <el-row :gutter="24" v-else>
+        <el-col :span="6" v-for="tag in tagList" :key="tag.id" class="tag-col">
+          <div class="info-item">
+            <span class="info-label">
+              {{ tag.propertyName }}
+              <el-button type="text" icon="el-icon-delete" size="mini" @click="handleDeleteTag(tag)" style="color: #f56c6c; margin-left: 4px;"></el-button>
+            </span>
+            <span class="info-value">
+              <el-tag
+                v-if="tag.propertyValue"
+                size="mini"
+                @click="handleEditTag(tag)"
+                style="cursor: pointer;"
+                :type="getIntentionTagType(tag.intention)"
+              >
+                <span v-if="Array.isArray(tag.propertyValue)">{{ tag.propertyValue.join('、') }}</span>
+                <span v-else>{{ tag.propertyValue }}</span>
+              </el-tag>
+              <span v-else class="no-value">
+                未设置
+                <el-button type="text" size="mini" icon="el-icon-edit" @click="handleEditTag(tag)" style="margin-left: 4px;">批注</el-button>
+              </span>
+            </span>
+            <div class="tag-extra-info">
+              <el-tag v-if="tag.intention" size="mini" effect="plain">
+                <i class="el-icon-star-on"></i> {{ getIntentionText(tag.intention) }}
+              </el-tag>
+              <el-tag v-if="tag.likeRatio !== null && tag.likeRatio !== undefined" size="mini" type="success" effect="plain">
+                <i class="el-icon-trend-chart"></i> {{ tag.likeRatio }}%
+              </el-tag>
+            </div>
+          </div>
+        </el-col>
+      </el-row>
+    </div>
+
+    <!-- 添加/编辑标签弹窗 -->
+    <el-dialog
+      :title="dialogTitle"
+      :visible.sync="dialogVisible"
+      width="600px"
+      append-to-body
+      :close-on-click-modal="false"
+    >
+      <el-form ref="form" :model="form" :rules="rules" label-width="100px">
+        <el-form-item label="标签维度" prop="propertyId">
+          <el-select
+            v-model="form.propertyId"
+            placeholder="请选择标签维度"
+            style="width: 100%"
+            :disabled="isEdit"
+            @change="handlePropertyChange"
+          >
+            <el-option
+              v-for="item in propertyTemplateList"
+              :key="item.id"
+              :label="item.name"
+              :value="item.id"
+            >
+              <span style="float: left">{{ item.name }}</span>
+              <span style="float: right; color: #8492a6; font-size: 13px">{{ getValueTypeText(item.valueType) }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+        <el-form-item label="标签值" prop="propertyValue">
+          <el-input
+            v-if="currentValueType === 'text'"
+            v-model="form.propertyValue"
+            placeholder="请输入标签值"
+            maxlength="100"
+            show-word-limit
+          />
+          <el-select
+            v-else-if="currentValueType === 'select'"
+            v-model="form.propertyValue"
+            placeholder="请选择标签值"
+            style="width: 100%"
+            clearable
+          >
+            <el-option label="是" value="是" />
+            <el-option label="否" value="否" />
+          </el-select>
+          <el-select
+            v-else-if="currentValueType === 'multiSelect'"
+            v-model="form.propertyValue"
+            placeholder="请选择标签值(可多选)"
+            style="width: 100%"
+            multiple
+            collapse-tags
+            clearable
+          >
+            <el-option label="喜欢" value="喜欢" />
+            <el-option label="不喜欢" value="不喜欢" />
+            <el-option label="一般" value="一般" />
+            <el-option label="观望" value="观望" />
+            <el-option label="有意向" value="有意向" />
+            <el-option label="无意向" value="无意向" />
+          </el-select>
+          <el-input
+            v-else-if="currentValueType === 'number'"
+            v-model="form.propertyValue"
+            placeholder="请输入数字"
+            maxlength="20"
+          />
+          <el-input
+            v-else-if="currentValueType === 'date'"
+            v-model="form.propertyValue"
+            type="date"
+            placeholder="选择日期"
+            style="width: 100%"
+          />
+          <span v-else class="unknown-type">未知的属性类型</span>
+        </el-form-item>
+        <el-form-item label="意向登记" prop="intention">
+          <el-select v-model="form.intention" placeholder="请选择意向程度" style="width: 100%" clearable>
+            <el-option label="高意向(80-100%)" value="high" />
+            <el-option label="中意向(50-79%)" value="medium" />
+            <el-option label="低意向(20-49%)" value="low" />
+            <el-option label="无意向(0-19%)" value="none" />
+          </el-select>
+        </el-form-item>
+        <el-form-item label="喜欢占比" prop="likeRatio">
+          <el-slider v-model="form.likeRatio" :min="0" :max="100" show-input input-size="small" />
+        </el-form-item>
+        <el-form-item label="备注" prop="remark">
+          <el-input
+            v-model="form.remark"
+            type="textarea"
+            :rows="2"
+            placeholder="请输入备注"
+            maxlength="200"
+            show-word-limit
+          />
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="dialogVisible = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import { listPropertyTemplate } from "@/api/crm/propertyTemplate";
+import { listByCustomerId, addOrUpdateByTemplateId, deleteByPropertyId, addOrUpdate } from "@/api/crm/customerProperty";
+
+export default {
+  name: "AiTagPanel",
+  props: {
+    customerId: {
+      type: Number,
+      required: true,
+      default: null
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      tagList: [],
+      propertyTemplateList: [],
+      dialogVisible: false,
+      dialogTitle: "",
+      isEdit: false,
+      currentValueType: "text",
+      form: {
+        customerId: null,
+        propertyId: null,
+        propertyName: null,
+        propertyValue: null,
+        propertyValueType: null,
+        tradeType: null,
+        intention: null,
+        likeRatio: null,
+        remark: null
+      },
+      rules: {
+        propertyId: [
+          { required: true, message: "请选择标签维度", trigger: "change" }
+        ],
+        propertyValue: [
+          { required: true, message: "请输入标签值", trigger: "blur" }
+        ]
+      }
+    };
+  },
+  created() {
+    if (this.customerId) {
+      this.loadTags();
+    }
+  },
+  watch: {
+    customerId(newVal) {
+      if (newVal) {
+        this.loadTags();
+      }
+    },
+    'form.likeRatio'(newVal) {
+      if (newVal !== null && newVal !== undefined) {
+        this.autoSetIntention(newVal);
+      }
+    }
+  },
+  methods: {
+    autoSetIntention(likeRatio) {
+      let intention = null;
+      if (likeRatio >= 80) {
+        intention = 'high';
+      } else if (likeRatio >= 50) {
+        intention = 'medium';
+      } else if (likeRatio >= 20) {
+        intention = 'low';
+      } else {
+        intention = 'none';
+      }
+      this.form.intention = intention;
+    },
+    loadTags() {
+      this.loading = true;
+      listByCustomerId(this.customerId).then(response => {
+        let tagList = response.data || [];
+        console.log('加载的标签数据:', tagList);
+        tagList.forEach(tag => {
+          if (tag.propertyValueType === 'multiSelect' && tag.propertyValue && typeof tag.propertyValue === 'string') {
+            tag.propertyValue = tag.propertyValue.split(',');
+          }
+        });
+        this.tagList = tagList;
+        this.loading = false;
+      }).catch(() => {
+        this.loading = false;
+      });
+    },
+    loadPropertyTemplates() {
+      listPropertyTemplate({ pageNum: 1, pageSize: 100 }).then(response => {
+        this.propertyTemplateList = response.rows || [];
+      });
+    },
+
+    handleAddTag() {
+      this.loadPropertyTemplates();
+      this.reset();
+      this.dialogTitle = "添加标签";
+      this.isEdit = false;
+      this.dialogVisible = true;
+    },
+    handleEditTag(tag) {
+      this.loadPropertyTemplates();
+      let propertyValue = tag.propertyValue;
+      if (tag.propertyValueType === 'multiSelect' && propertyValue && typeof propertyValue === 'string') {
+        propertyValue = propertyValue.split(',');
+      }
+      this.form = {
+        customerId: tag.customerId,
+        propertyId: tag.propertyId,
+        propertyName: tag.propertyName,
+        propertyValue: propertyValue,
+        propertyValueType: tag.propertyValueType,
+        tradeType: tag.tradeType,
+        intention: tag.intention || null,
+        likeRatio: tag.likeRatio || null,
+        remark: tag.remark
+      };
+      this.currentValueType = tag.propertyValueType || "text";
+      this.dialogTitle = "编辑标签";
+      this.isEdit = true;
+      this.dialogVisible = true;
+    },
+    handleDeleteTag(tag) {
+      this.$confirm(`确认删除标签"${tag.propertyName}"吗?`, "警告", {
+        confirmButtonText: "确定",
+        cancelButtonText: "取消",
+        type: "warning"
+      }).then(() => {
+        return deleteByPropertyId(this.customerId, tag.propertyId);
+      }).then(() => {
+        this.$message.success("删除成功");
+        this.loadTags();
+        this.$emit("tag-change");
+      }).catch(() => {});
+    },
+    handlePropertyChange(propertyId) {
+      const template = this.propertyTemplateList.find(item => item.id === propertyId);
+      if (template) {
+        this.form.propertyName = template.name;
+        this.form.propertyValueType = template.valueType;
+        this.form.tradeType = template.tradeType;
+        this.currentValueType = template.valueType;
+      }
+    },
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (valid) {
+          this.form.customerId = this.customerId;
+          const requestData = {
+            customerId: this.customerId,
+            propertyId: this.form.propertyId,
+            propertyName: this.form.propertyName,
+            propertyValue: Array.isArray(this.form.propertyValue) ? this.form.propertyValue.join(',') : this.form.propertyValue,
+            propertyValueType: this.form.propertyValueType,
+            tradeType: this.form.tradeType,
+            intention: this.form.intention,
+            likeRatio: this.form.likeRatio,
+            remark: this.form.remark
+          };
+          
+          addOrUpdate(requestData).then(response => {
+            if (response.code === 200) {
+              this.$message.success("保存成功");
+              this.dialogVisible = false;
+              this.loadTags();
+              this.$emit("tag-change");
+            }
+          });
+        }
+      });
+    },
+    reset() {
+      this.form = {
+        customerId: this.customerId,
+        propertyId: null,
+        propertyName: null,
+        propertyValue: null,
+        propertyValueType: null,
+        tradeType: null,
+        intention: null,
+        likeRatio: null,
+        remark: null
+      };
+      this.currentValueType = "text";
+      this.isEdit = false;
+      if (this.$refs.form) {
+        this.$refs.form.clearValidate();
+      }
+    },
+    getValueTypeText(valueType) {
+      const typeMap = {
+        text: "文本",
+        number: "数字",
+        date: "日期",
+        select: "单选",
+        multiSelect: "多选"
+      };
+      return typeMap[valueType] || valueType;
+    },
+    getIntentionText(intention) {
+      const intentionMap = {
+        high: "高意向",
+        medium: "中意向",
+        low: "低意向",
+        none: "无意向"
+      };
+      return intentionMap[intention] || intention;
+    },
+    getIntentionTagType(intention) {
+      const typeMap = {
+        high: "success",
+        medium: "warning",
+        low: "info",
+        none: "danger"
+      };
+      return typeMap[intention] || "";
+    }
+  }
+};
+</script>
+
+<style lang="scss" scoped>
+.info-section {
+  background: #fff;
+  border-radius: 8px;
+  margin-bottom: 16px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+
+  .section-header {
+    padding: 16px 20px;
+    border-bottom: 1px solid #ebeef5;
+    background: #fafbfc;
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+
+    .section-title {
+      font-size: 15px;
+      font-weight: 600;
+      color: #303133;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      i {
+        color: #409eff;
+      }
+    }
+
+    .header-actions {
+      display: flex;
+      align-items: center;
+    }
+  }
+
+  .section-content {
+    padding: 20px;
+    min-height: 80px;
+
+    .empty-tips {
+      text-align: center;
+      color: #909399;
+      padding: 20px 0;
+      font-size: 14px;
+    }
+
+    .tag-col {
+      margin-bottom: 0;
+    }
+  }
+}
+
+.info-item {
+  margin-bottom: 16px;
+
+  .info-label {
+    display: block;
+    font-size: 12px;
+    color: #909399;
+    margin-bottom: 6px;
+    display: flex;
+    align-items: center;
+  }
+
+  .info-value {
+    font-size: 14px;
+    color: #303133;
+    display: flex;
+    align-items: center;
+
+    .no-value {
+      color: #c0c4cc;
+      display: flex;
+      align-items: center;
+      gap: 8px;
+    }
+  }
+
+  .tag-extra-info {
+    margin-top: 6px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+}
+</style>

+ 1 - 0
src/views/crm/components/CustomerSelect.vue

@@ -456,6 +456,7 @@ export default {
           this.getList();
           this.getList();
     },
     },
     submitForm(){
     submitForm(){
+      console.log(this.ids);
       this.$nextTick(() => {
       this.$nextTick(() => {
         this.$emit("success", {ids: this.ids, names: this.names, rows: this.rows})
         this.$emit("success", {ids: this.ids, names: this.names, rows: this.rows})
         this.shows = false;
         this.shows = false;

+ 326 - 224
src/views/crm/components/addOrEditCustomer.vue

@@ -1,65 +1,107 @@
 <template>
 <template>
-    <div>
-        <el-form ref="form" :model="form" :rules="rules" label-width="120px">
-                <!-- <el-form-item label="客户编码" prop="customerCode">
-                    <el-input v-model="form.customerCode" :disabled="form.customerId!=null" placeholder="请输入客户编码" />
-                </el-form-item> -->
-                <el-form-item label="客户名称" prop="customerName">
-                    <el-input v-model="form.customerName" placeholder="请输入客户名称" />
-                </el-form-item>
-                <el-form-item label="手机" prop="mobile">
-                    <el-input  maxlength="11" v-model="form.mobile" placeholder="请输入手机" />
-                </el-form-item>
-                <el-form-item label="性别" prop="sex">
-                    <el-radio-group v-model="form.sex">
-                        <el-radio :label="item.dictValue" v-for="item in sexOptions" >{{item.dictLabel}}</el-radio>
-                    </el-radio-group>
-                </el-form-item>
-                <el-form-item label="微信号" prop="weixin">
-                    <el-input v-model="form.weixin" placeholder="请输入微信号" />
-                </el-form-item>
-                <el-form-item label="所在地区" prop="address">
-                    <el-cascader
-                    ref="citySelect"
-                    v-model="cityIds"
-                    :options="citys"
-                    @change="handleCityChange"></el-cascader>
-                </el-form-item>
-                <el-form-item label="详细地址" prop="detailAddress">
-                    <el-input v-model="form.detailAddress" placeholder="请输入详细地址" />
-                </el-form-item>
-                <el-form-item label="客户类型" prop="customerType">
-                    <el-select v-model="form.customerType" placeholder="请选择客户类型" clearable size="small">
-                        <el-option
-                            v-for="item in typeOptions"
-                            :key="item.dictValue"
-                            :label="item.dictLabel"
-                            :value="item.dictValue"
+    <div class="customer-dialog-content">
+        <div class="dialog-header">
+            <i class="el-icon-user header-icon"></i>
+            <span class="header-title">{{ form.customerId ? '编辑客户' : '新增客户' }}</span>
+        </div>
+        
+        <el-form ref="form" :model="form" :rules="rules" label-width="120px" class="customer-form">
+            <el-divider content-position="left">
+                <span class="divider-title">
+                    <i class="el-icon-info"></i> 基本信息
+                </span>
+            </el-divider>
+            
+            <el-row :gutter="20">
+                <el-col :span="12">
+                    <el-form-item label="客户名称" prop="customerName">
+                        <el-input v-model="form.customerName" placeholder="请输入客户名称" />
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="手机" prop="mobile">
+                        <el-input maxlength="11" v-model="form.mobile" placeholder="请输入手机" />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            
+            <el-row :gutter="20">
+                <el-col :span="12">
+                    <el-form-item label="性别" prop="sex">
+                        <el-radio-group v-model="form.sex">
+                            <el-radio :label="item.dictValue" v-for="item in sexOptions" :key="item.dictValue">{{item.dictLabel}}</el-radio>
+                        </el-radio-group>
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="微信号" prop="weixin">
+                        <el-input v-model="form.weixin" placeholder="请输入微信号" />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            
+            <el-row :gutter="20">
+                <el-col :span="12">
+                    <el-form-item label="客户类型" prop="customerType">
+                        <el-select v-model="form.customerType" placeholder="请选择客户类型" clearable style="width: 100%;">
+                            <el-option
+                                v-for="item in typeOptions"
+                                :key="item.dictValue"
+                                :label="item.dictLabel"
+                                :value="item.dictValue"
                             />
                             />
-                    </el-select>
-                </el-form-item>
-                <!-- <el-form-item label="客户状态">
-                    <el-radio-group v-model="form.status">
-                        <el-radio :label="item.dictValue" v-for="item in statusOptions">{{item.dictLabel}}</el-radio>
-                    </el-radio-group>
-                </el-form-item> -->
-                <el-form-item label="客户来源" prop="source">
-                <el-select v-model="form.source" placeholder="请选择客户来源" clearable size="small">
-                    <el-option
-                        v-for="item in sourceOptions"
-                        :key="item.dictValue"
-                        :label="item.dictLabel"
-                        :value="item.dictValue"
-                        />
-                </el-select>
-                </el-form-item>
-                <el-form-item label="标签" prop="tags">
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="客户来源" prop="source">
+                        <el-select v-model="form.source" placeholder="请选择客户来源" clearable style="width: 100%;">
+                            <el-option
+                                v-for="item in sourceOptions"
+                                :key="item.dictValue"
+                                :label="item.dictLabel"
+                                :value="item.dictValue"
+                            />
+                        </el-select>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            
+            <el-row :gutter="20">
+                <el-col :span="12">
+                    <el-form-item label="所在地区" prop="address">
+                        <el-cascader
+                            ref="citySelect"
+                            v-model="cityIds"
+                            :options="citys"
+                            @change="handleCityChange"
+                            style="width: 100%;"
+                        ></el-cascader>
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="详细地址" prop="detailAddress">
+                        <el-input v-model="form.detailAddress" placeholder="请输入详细地址" />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            
+            <el-divider content-position="left">
+                <span class="divider-title">
+                    <i class="el-icon-price-tag"></i> 标签信息
+                </span>
+            </el-divider>
+            
+            <el-form-item label="标签" prop="tags">
+                <div class="tags-container">
                     <el-tag
                     <el-tag
                         :key="tag"
                         :key="tag"
                         v-for="tag in tags"
                         v-for="tag in tags"
                         closable
                         closable
                         :disable-transitions="false"
                         :disable-transitions="false"
-                        @close="handleClose(tag)">
+                        class="selected-tag"
+                        @close="handleClose(tag)"
+                    >
                         {{tag}}
                         {{tag}}
                     </el-tag>
                     </el-tag>
                     <el-input
                     <el-input
@@ -68,110 +110,147 @@
                         v-model="inputValue"
                         v-model="inputValue"
                         ref="saveTagInput"
                         ref="saveTagInput"
                         size="small"
                         size="small"
+                        placeholder="输入新标签"
                         @keyup.enter.native="handleInputConfirm"
                         @keyup.enter.native="handleInputConfirm"
                         @blur="handleInputConfirm"
                         @blur="handleInputConfirm"
                     >
                     >
                     </el-input>
                     </el-input>
-                    <el-button v-else class="button-new-tag" size="small" @click="showInput">添加标签+</el-button>
-                    <el-select v-model="tagId" @change="tagsChange" style="width:140px;margin-left: 5px;" placeholder="请选择客户标签" clearable size="small">
+                    <el-button v-else class="button-new-tag" size="small" icon="el-icon-plus" @click="showInput">自定义标签</el-button>
+                    <el-select v-model="tagId" @change="tagsChange" style="width:140px;margin-left: 10px;" placeholder="选择标签" clearable size="small">
                         <el-option
                         <el-option
                             v-for="item in tagsOptions"
                             v-for="item in tagsOptions"
                             :key="item.dictValue"
                             :key="item.dictValue"
                             :label="item.dictLabel"
                             :label="item.dictLabel"
                             :value="item.dictValue"
                             :value="item.dictValue"
-                            />
+                        />
                     </el-select>
                     </el-select>
-                </el-form-item>
-                <el-form-item label="进线日期" prop="registerDate">
-                <el-date-picker
-                value-format="yyyy-MM-dd"
-                    v-model="form.registerDate"
-                    type="date"
-                    placeholder="请选择进线日期">
-                </el-date-picker>
-                </el-form-item>
-                <el-form-item label="进线链接" prop="registerLinkUrl">
-                <el-input v-model="form.registerLinkUrl" placeholder="请输入进线链接" />
-                </el-form-item>
-                <el-form-item label="进线客户详情" prop="registerDesc">
-                    <el-input v-model="form.registerDesc" placeholder="请输入进线客户详情" />
-                </el-form-item>
-                <el-form-item label="进线填写时间" prop="registerSubmitTime">
-                <el-date-picker
-                    value-format="yyyy-MM-dd HH:mm:ss"
-                    v-model="form.registerSubmitTime"
-                    type="datetime"
-                    placeholder="请选择进线填写时间">
-                </el-date-picker>
-                </el-form-item>
-                <el-form-item label="进线方式" prop="registerType">
-                    <el-input v-model="form.registerType" placeholder="请输入进线方式" />
-                </el-form-item>
-                <el-form-item label="来源渠道编码" prop="sourceCode">
-                    <el-input v-model="form.sourceCode" placeholder="请输入来源渠道编码" />
-                </el-form-item>
-                <el-form-item label="推线时间" prop="pushTime">
-                    <el-input v-model="form.pushTime" placeholder="请输入推线时间" />
-                </el-form-item>
-                <el-form-item label="推线编码" prop="pushCode">
-                    <el-input v-model="form.pushCode" placeholder="请输入推线编码" />
-                </el-form-item>
-                <el-form-item :label="ext.name" v-for="ext  in exts"  >
-                    <el-input v-model="ext.value"  />
-                </el-form-item>
-
-                <el-form-item label="备注" prop="remark">
-                    <el-input :rows="2" v-model="form.remark" type="textarea" placeholder="请输入内容" />
-                </el-form-item>
-            </el-form>
-            <div  class="footer">
-                <el-button type="primary" @click="submitForm">确 定</el-button>
-            </div>
+                </div>
+            </el-form-item>
+            
+            <el-divider content-position="left">
+                <span class="divider-title">
+                    <i class="el-icon-connection"></i> 进线信息
+                </span>
+            </el-divider>
+            
+            <el-row :gutter="20">
+                <el-col :span="12">
+                    <el-form-item label="进线日期" prop="registerDate">
+                        <el-date-picker
+                            value-format="yyyy-MM-dd"
+                            v-model="form.registerDate"
+                            type="date"
+                            placeholder="请选择进线日期"
+                            style="width: 100%;"
+                        ></el-date-picker>
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="进线填写时间" prop="registerSubmitTime">
+                        <el-date-picker
+                            value-format="yyyy-MM-dd HH:mm:ss"
+                            v-model="form.registerSubmitTime"
+                            type="datetime"
+                            placeholder="请选择进线填写时间"
+                            style="width: 100%;"
+                        ></el-date-picker>
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            
+            <el-row :gutter="20">
+                <el-col :span="12">
+                    <el-form-item label="进线链接" prop="registerLinkUrl">
+                        <el-input v-model="form.registerLinkUrl" placeholder="请输入进线链接" />
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="进线方式" prop="registerType">
+                        <el-input v-model="form.registerType" placeholder="请输入进线方式" />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            
+            <el-form-item label="进线客户详情" prop="registerDesc">
+                <el-input v-model="form.registerDesc" placeholder="请输入进线客户详情" />
+            </el-form-item>
+            
+            <el-row :gutter="20">
+                <el-col :span="12">
+                    <el-form-item label="来源渠道编码" prop="sourceCode">
+                        <el-input v-model="form.sourceCode" placeholder="请输入来源渠道编码" />
+                    </el-form-item>
+                </el-col>
+                <el-col :span="12">
+                    <el-form-item label="推线时间" prop="pushTime">
+                        <el-input v-model="form.pushTime" placeholder="请输入推线时间" />
+                    </el-form-item>
+                </el-col>
+            </el-row>
+            
+            <el-form-item label="推线编码" prop="pushCode">
+                <el-input v-model="form.pushCode" placeholder="请输入推线编码" />
+            </el-form-item>
+            
+            <el-divider content-position="left" v-if="exts.length > 0">
+                <span class="divider-title">
+                    <i class="el-icon-s-operation"></i> 扩展信息
+                </span>
+            </el-divider>
+            
+            <el-form-item :label="ext.name" v-for="ext in exts" :key="ext.extId">
+                <el-input v-model="ext.value" placeholder="请输入内容" />
+            </el-form-item>
+            
+            <el-divider content-position="left">
+                <span class="divider-title">
+                    <i class="el-icon-document"></i> 备注
+                </span>
+            </el-divider>
+            
+            <el-form-item label="备注" prop="remark">
+                <el-input :rows="3" v-model="form.remark" type="textarea" placeholder="请输入备注内容" maxlength="500" show-word-limit />
+            </el-form-item>
+        </el-form>
     </div>
     </div>
 </template>
 </template>
   
   
 <script>
 <script>
     import { listCustomerExt } from "@/api/crm/customerExt";
     import { listCustomerExt } from "@/api/crm/customerExt";
-    import { updateCustomer,addCustomer,addMyCustomer,getCustomerDetails   } from "@/api/crm/customer";
-    import {getCitys} from "@/api/store/city";
+    import { updateCustomer, addCustomer, addMyCustomer, getCustomerDetails } from "@/api/crm/customer";
+    import { getCitys } from "@/api/store/city";
+    
     export default {
     export default {
-        name: "remark",
+        name: "AddOrEditCustomer",
         data() {
         data() {
             return {
             return {
-                addType:1,//添加类型 1线索客户 2我的客户
-                tagId:null,
-                tagsOptions:[],
-                cityIds:[],
-                citys:[],
-                tags:[],
+                addType: 1,
+                tagId: null,
+                tagsOptions: [],
+                cityIds: [],
+                citys: [],
+                tags: [],
                 inputVisible: false,
                 inputVisible: false,
                 inputValue: '',
                 inputValue: '',
-                statusOptions:[],
-                typeOptions:[],
-                sourceOptions:[],
-                sexOptions:[],
-                customerExts:[],
-                // 表单参数
+                statusOptions: [],
+                typeOptions: [],
+                sourceOptions: [],
+                sexOptions: [],
+                customerExts: [],
                 form: {
                 form: {
-                    province:null,
-                    city:null,
-                    district:null,
-                    
+                    province: null,
+                    city: null,
+                    district: null,
                 },
                 },
-                exts:[],
-                // 表单校验
+                exts: [],
                 rules: {
                 rules: {
-                    // customerName: [
-                    // { required: true, message: "客户名称不能为空", trigger: "blur" }
-                    // ],
                     mobile: [
                     mobile: [
-                    { required: true, message: "手机号不能为空", trigger: "blur" }
+                        { required: true, message: "手机号不能为空", trigger: "blur" }
                     ],
                     ],
                     source: [
                     source: [
-                    { required: true, message: "客户来源不能为空", trigger: "blur" }
+                        { required: true, message: "客户来源不能为空", trigger: "blur" }
                     ],
                     ],
                 }
                 }
-                 
             };
             };
         },
         },
         created() {
         created() {
@@ -190,44 +269,32 @@
             this.getDicts("crm_customer_type").then((response) => {
             this.getDicts("crm_customer_type").then((response) => {
                 this.typeOptions = response.data;
                 this.typeOptions = response.data;
             });
             });
-            
-            this.getCitys()
+            this.getCitys();
         },
         },
         methods: {
         methods: {
-            tagsChange(e){
-                var item=this.tagsOptions.find(val => val.dictValue === e);
-                console.log(item);
-                this.tags.push(item.dictLabel);
-                this.form.tags=this.tags.toString();
-            },
-            closeTag(){
-                this.addTag.open=false;
-                this.getDetails(this.customerId)
-            },
-            handleAddTag(){
-                this.addTag.open=true;
-                var that=this;
-                setTimeout(() => {
-                    that.$refs.tag.reset(this.item);
-                }, 500);
-                
+            tagsChange(e) {
+                var item = this.tagsOptions.find(val => val.dictValue === e);
+                if (item && !this.tags.includes(item.dictLabel)) {
+                    this.tags.push(item.dictLabel);
+                    this.form.tags = this.tags.toString();
+                }
+                this.tagId = null;
             },
             },
             handleCityChange(value) {
             handleCityChange(value) {
-                console.log(value);
-                var nodes=this.$refs.citySelect.getCheckedNodes();
-                this.form.address=nodes[0].pathLabels[0]+"-"+nodes[0].pathLabels[1]+"-"+nodes[0].pathLabels[2];
-                this.form.cityIds=value.toString();
-                console.log(this.form.address);
+                var nodes = this.$refs.citySelect.getCheckedNodes();
+                if (nodes && nodes.length > 0) {
+                    this.form.address = nodes[0].pathLabels.join("-");
+                    this.form.cityIds = value.toString();
+                }
             },
             },
-            getCitys(){
+            getCitys() {
                 getCitys().then(res => {
                 getCitys().then(res => {
-                this.loading = false;
-                this.citys=res.data;
-                })
+                    this.citys = res.data;
+                });
             },
             },
             handleClose(tag) {
             handleClose(tag) {
                 this.tags.splice(this.tags.indexOf(tag), 1);
                 this.tags.splice(this.tags.indexOf(tag), 1);
-                this.form.tags=this.tags.toString();
+                this.form.tags = this.tags.toString();
             },
             },
             showInput() {
             showInput() {
                 this.inputVisible = true;
                 this.inputVisible = true;
@@ -236,15 +303,14 @@
                 });
                 });
             },
             },
             handleInputConfirm() {
             handleInputConfirm() {
-                let inputValue = this.inputValue;
-                if (inputValue) {
+                let inputValue = this.inputValue.trim();
+                if (inputValue && !this.tags.includes(inputValue)) {
                     this.tags.push(inputValue);
                     this.tags.push(inputValue);
+                    this.form.tags = this.tags.toString();
                 }
                 }
                 this.inputVisible = false;
                 this.inputVisible = false;
                 this.inputValue = '';
                 this.inputValue = '';
-                this.form.tags=this.tags.toString();
             },
             },
-            // 表单重置
             reset() {
             reset() {
                 this.form = {
                 this.form = {
                     customerId: null,
                     customerId: null,
@@ -276,113 +342,100 @@
                     isLine: null,
                     isLine: null,
                     source: null,
                     source: null,
                     tags: null,
                     tags: null,
-                    
                 };
                 };
-                this.exts=[];
-                this.tags=[];
-                this.cityIds=[];
+                this.exts = [];
+                this.tags = [];
+                this.cityIds = [];
                 this.resetForm("form");
                 this.resetForm("form");
             },
             },
             handleAdd(addType) {
             handleAdd(addType) {
-                this.addType=addType;
+                this.addType = addType;
                 this.reset();
                 this.reset();
-                console.log(this.customerExts)
-                var data={status:1}
+                var data = { status: 1 };
                 listCustomerExt(data).then(response => {
                 listCustomerExt(data).then(response => {
                     this.customerExts = response.data;
                     this.customerExts = response.data;
                     this.customerExts.forEach(element => {
                     this.customerExts.forEach(element => {
-                        var data={extId:element.extId,name:element.name,value:""};
-                        this.exts.push(data)
+                        var data = { extId: element.extId, name: element.name, value: "" };
+                        this.exts.push(data);
                     });
                     });
-                
                 });
                 });
-                
-                console.log(this.exts)
             },
             },
-            /** 修改按钮操作 */
             handleUpdate(customerId) {
             handleUpdate(customerId) {
                 this.reset();
                 this.reset();
-                var that=this;
-                var data={customerId:customerId}
+                var that = this;
+                var data = { customerId: customerId };
                 getCustomerDetails(data).then(response => {
                 getCustomerDetails(data).then(response => {
                     this.form = response.customer;
                     this.form = response.customer;
-                    if(this.form.status!=null){
+                    if (this.form.status != null) {
                         this.form.status = this.form.status.toString();
                         this.form.status = this.form.status.toString();
                     }
                     }
-                    if(this.form.customerType!=null){
+                    if (this.form.customerType != null) {
                         this.form.customerType = this.form.customerType.toString();
                         this.form.customerType = this.form.customerType.toString();
                     }
                     }
-                    if(this.form.sex!=null){
-                     this.form.sex = this.form.sex.toString();
+                    if (this.form.sex != null) {
+                        this.form.sex = this.form.sex.toString();
                     }
                     }
-                    if(this.form.source!=null){
+                    if (this.form.source != null) {
                         this.form.source = this.form.source.toString();
                         this.form.source = this.form.source.toString();
                     }
                     }
-                    if(this.form.tags!=null){
-                        this.tags = this.form.tags.split(",")
+                    if (this.form.tags != null) {
+                        this.tags = this.form.tags.split(",");
                     }
                     }
-                    if(this.form.cityIds!=null){
-                        var ids=this.form.cityIds.split(",");
-                        this.cityIds=[];
+                    if (this.form.cityIds != null) {
+                        var ids = this.form.cityIds.split(",");
+                        this.cityIds = [];
                         ids.forEach(element => {
                         ids.forEach(element => {
-                            var id=parseInt(element);
-                            that.cityIds.push(id)
+                            var id = parseInt(element);
+                            that.cityIds.push(id);
                         });
                         });
                     }
                     }
                     listCustomerExt(data).then(response => {
                     listCustomerExt(data).then(response => {
                         this.customerExts = response.data;
                         this.customerExts = response.data;
                         this.customerExts.forEach(element => {
                         this.customerExts.forEach(element => {
-                            var data={extId:element.extId,name:element.name,value:""};
-                            this.exts.push(data)
+                            var data = { extId: element.extId, name: element.name, value: "" };
+                            this.exts.push(data);
                         });
                         });
-                        if(this.form.extJson!=null){
-                            var extList=JSON.parse(this.form.extJson);
+                        if (this.form.extJson != null) {
+                            var extList = JSON.parse(this.form.extJson);
                             that.exts.forEach(item => {
                             that.exts.forEach(item => {
                                 extList.forEach(element => {
                                 extList.forEach(element => {
-                                    if(item.extId==element.extId){
-                                        item.value=element.value
+                                    if (item.extId == element.extId) {
+                                        item.value = element.value;
                                     }
                                     }
                                 });
                                 });
-                            
                             });
                             });
                         }
                         }
-                    
                     });
                     });
-                    this.open = true;
-                    this.title = "修改客户";
                 });
                 });
             },
             },
-            /** 提交按钮 */
             submitForm() {
             submitForm() {
                 this.$refs["form"].validate(valid => {
                 this.$refs["form"].validate(valid => {
                     if (valid) {
                     if (valid) {
                         if (this.form.customerId != null) {
                         if (this.form.customerId != null) {
-                            this.form.extJson=JSON.stringify(this.exts);
+                            this.form.extJson = JSON.stringify(this.exts);
                             updateCustomer(this.form).then(response => {
                             updateCustomer(this.form).then(response => {
                                 if (response.code === 200) {
                                 if (response.code === 200) {
-                                    this.msgSuccess("操作成功");
+                                    this.$message.success("操作成功");
                                     this.$emit('close');
                                     this.$emit('close');
                                 }
                                 }
                             });
                             });
                         } else {
                         } else {
-                            this.form.extJson=JSON.stringify(this.exts);
-                            if(this.addType==1){
+                            this.form.extJson = JSON.stringify(this.exts);
+                            if (this.addType == 1) {
                                 addCustomer(this.form).then(response => {
                                 addCustomer(this.form).then(response => {
                                     if (response.code === 200) {
                                     if (response.code === 200) {
-                                        this.msgSuccess("操作成功");
+                                        this.$message.success("操作成功");
                                         this.$emit('close');
                                         this.$emit('close');
                                     }
                                     }
                                 });
                                 });
-                            }
-                            else if(this.addType==2){
+                            } else if (this.addType == 2) {
                                 addMyCustomer(this.form).then(response => {
                                 addMyCustomer(this.form).then(response => {
                                     if (response.code === 200) {
                                     if (response.code === 200) {
-                                        this.msgSuccess("操作成功");
+                                        this.$message.success("操作成功");
                                         this.$emit('close');
                                         this.$emit('close');
                                     }
                                     }
                                 });
                                 });
                             }
                             }
-                            
                         }
                         }
                     }
                     }
                 });
                 });
@@ -391,19 +444,68 @@
     };
     };
 </script>
 </script>
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-.contents{
-    height: 100%;
-    background-color: #fff;
-    padding: 20px;
+.customer-dialog-content {
+    padding: 10px 0;
+    
+    .dialog-header {
+        display: flex;
+        align-items: center;
+        margin-bottom: 24px;
         
         
-}
-.footer{
-    display: flex;
-    align-items: center;
-    justify-content: flex-end;
+        .header-icon {
+            font-size: 24px;
+            color: #409eff;
+            margin-right: 12px;
+        }
+        
+        .header-title {
+            font-size: 16px;
+            font-weight: 600;
+            color: #303133;
+        }
+    }
+    
+    .customer-form {
+        .el-divider {
+            margin: 20px 0;
+            
+            .divider-title {
+                font-size: 14px;
+                font-weight: 600;
+                color: #606266;
+                display: flex;
+                align-items: center;
+                gap: 6px;
+                
+                i {
+                    color: #909399;
+                }
+            }
+        }
+        
+        .tags-container {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+            align-items: center;
+            min-height: 40px;
+        }
+        
+        .selected-tag {
+            margin: 0;
+        }
+        
+        .button-new-tag {
+            background: #f5f7fa;
+            border: 1px dashed #d9d9d9;
+            color: #606266;
+            
+            &:hover {
+                background: #ecf5ff;
+                border-color: #409eff;
+                color: #409eff;
+            }
+        }
+    }
 }
 }
 </style>
 </style>
-
-
-
- 

+ 42 - 28
src/views/crm/components/addRemark.vue

@@ -1,20 +1,29 @@
 <template>
 <template>
-    <div>
-            <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-                <el-form-item label="备注" prop="remark">
-                    <el-input :rows="2" v-model="form.remark" type="textarea" placeholder="请输入内容" />
-                </el-form-item>
-            </el-form>
-            <div  class="footer">
-                <el-button type="primary" @click="submitForm">确 定</el-button>
-            </div>
+    <div class="remark-dialog-content">
+        <div class="dialog-header">
+            <i class="el-icon-document header-icon"></i>
+            <span class="header-title">添加客户备注</span>
+        </div>
+        
+        <el-form ref="form" :model="form" :rules="rules" label-width="80px" class="remark-form">
+            <el-form-item label="备注内容" prop="remark">
+                <el-input 
+                    :rows="5" 
+                    v-model="form.remark" 
+                    type="textarea" 
+                    placeholder="请输入备注内容..."
+                    maxlength="500"
+                    show-word-limit
+                />
+            </el-form-item>
+        </el-form>
     </div>
     </div>
 </template>
 </template>
   
   
 <script>
 <script>
     import { updateCustomer  } from "@/api/crm/customer";
     import { updateCustomer  } from "@/api/crm/customer";
     export default {
     export default {
-        name: "remark",
+        name: "AddRemark",
         data() {
         data() {
             return {
             return {
                 form: {
                 form: {
@@ -22,12 +31,12 @@
                     remark:null,
                     remark:null,
                  
                  
                 },
                 },
-                // 表单校验
                 rules: {
                 rules: {
                     remark: [
                     remark: [
-                        { required: true, message: "备注不能为空", trigger: "change" }
+                        { required: true, message: "请输入备注内容", trigger: "blur" },
+                        { min: 2, message: "备注内容至少2个字符", trigger: "blur" }
                     ],
                     ],
-         
+             
                 }
                 }
                  
                  
             };
             };
@@ -44,8 +53,7 @@
                     if (valid) {
                     if (valid) {
                         updateCustomer(this.form).then(response => {
                         updateCustomer(this.form).then(response => {
                             if (response.code === 200) {
                             if (response.code === 200) {
-                                this.msgSuccess("操作成功");
-                                
+                                this.$message.success("操作成功");
                                 this.$emit('close');
                                 this.$emit('close');
                             }
                             }
                         });
                         });
@@ -56,19 +64,25 @@
     };
     };
 </script>
 </script>
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-.contents{
-    height: 100%;
-    background-color: #fff;
-    padding: 20px;
+.remark-dialog-content {
+    padding: 10px 0;
+    
+    .dialog-header {
+        display: flex;
+        align-items: center;
+        margin-bottom: 24px;
         
         
-}
-.footer{
-    display: flex;
-    align-items: center;
-    justify-content: flex-end;
+        .header-icon {
+            font-size: 24px;
+            color: #909399;
+            margin-right: 12px;
+        }
+        
+        .header-title {
+            font-size: 16px;
+            font-weight: 600;
+            color: #303133;
+        }
+    }
 }
 }
 </style>
 </style>
-
-
-
- 

+ 95 - 48
src/views/crm/components/addTag.vue

@@ -1,22 +1,29 @@
 <template>
 <template>
-    <div>
-            <el-form ref="form" :model="form" :rules="rules" label-width="80px">
-                <el-form-item label="标签库" >
-                    <el-select @change="tagChange" v-model="tag" placeholder="请选择" clearable size="small">
-                        <el-option
-                                v-for="item in tagsOptions"
-                                :key="item.dictValue"
-                                :label="item.dictLabel"
-                                :value="item.dictValue"
-                            />
-                    </el-select>
-                </el-form-item>
-                <el-form-item label="标签" prop="tags">
+    <div class="tag-dialog-content">
+        <div class="dialog-header">
+            <i class="el-icon-price-tag header-icon"></i>
+            <span class="header-title">为客户添加标签</span>
+        </div>
+        
+        <el-form ref="form" :model="form" :rules="rules" label-width="80px" class="tag-form">
+            <el-form-item label="标签库">
+                <el-select @change="tagChange" v-model="tag" placeholder="从标签库选择" clearable size="medium" style="width: 100%;">
+                    <el-option
+                            v-for="item in tagsOptions"
+                            :key="item.dictValue"
+                            :label="item.dictLabel"
+                            :value="item.dictValue"
+                        />
+                </el-select>
+            </el-form-item>
+            <el-form-item label="已选标签" prop="tags">
+                <div class="tags-container">
                     <el-tag
                     <el-tag
                         :key="tag"
                         :key="tag"
                         v-for="tag in tags"
                         v-for="tag in tags"
                         closable
                         closable
                         :disable-transitions="false"
                         :disable-transitions="false"
+                        class="selected-tag"
                         @close="handleClose(tag)">
                         @close="handleClose(tag)">
                         {{tag}}
                         {{tag}}
                     </el-tag>
                     </el-tag>
@@ -26,23 +33,26 @@
                         v-model="inputValue"
                         v-model="inputValue"
                         ref="saveTagInput"
                         ref="saveTagInput"
                         size="small"
                         size="small"
+                        placeholder="输入新标签"
                         @keyup.enter.native="handleInputConfirm"
                         @keyup.enter.native="handleInputConfirm"
                         @blur="handleInputConfirm"
                         @blur="handleInputConfirm"
                     >
                     >
                     </el-input>
                     </el-input>
-                    <el-button v-else class="button-new-tag" size="small" @click="showInput">添加标签+</el-button>
-                </el-form-item>
-            </el-form>
-            <div  class="footer">
-                <el-button type="primary" @click="submitForm">确 定</el-button>
-            </div>
+                    <el-button v-else class="button-new-tag" size="small" icon="el-icon-plus" @click="showInput">自定义标签</el-button>
+                </div>
+                <div class="form-tip" v-if="tags.length === 0">
+                    <i class="el-icon-info"></i>
+                    <span>请从标签库选择或自定义添加标签</span>
+                </div>
+            </el-form-item>
+        </el-form>
     </div>
     </div>
 </template>
 </template>
   
   
 <script>
 <script>
     import { updateCustomer  } from "@/api/crm/customer";
     import { updateCustomer  } from "@/api/crm/customer";
     export default {
     export default {
-        name: "visit",
+        name: "AddTag",
         data() {
         data() {
             return {
             return {
                 tag:null,
                 tag:null,
@@ -55,12 +65,11 @@
                     tags:null,
                     tags:null,
                  
                  
                 },
                 },
-                // 表单校验
                 rules: {
                 rules: {
                     tags: [
                     tags: [
-                        { required: true, message: "标签不能为空", trigger: "change" }
+                        { required: true, message: "请至少选择一个标签", trigger: "change" }
                     ],
                     ],
-         
+             
                 }
                 }
                  
                  
             };
             };
@@ -69,17 +78,15 @@
             this.getDicts("crm_customer_tag").then((response) => {
             this.getDicts("crm_customer_tag").then((response) => {
                 this.tagsOptions = response.data;
                 this.tagsOptions = response.data;
             });
             });
-            
-            
         },
         },
         methods: {
         methods: {
             tagChange(e){
             tagChange(e){
-                console.log(e)
                 var v=this.tagsOptions.find(value=>value.dictValue==e);
                 var v=this.tagsOptions.find(value=>value.dictValue==e);
-                console.log(v)
-                this.tags.push(v.dictLabel);
-                this.form.tags=this.tags.toString();
-
+                if (v && !this.tags.includes(v.dictLabel)) {
+                    this.tags.push(v.dictLabel);
+                    this.form.tags=this.tags.toString();
+                }
+                this.tag = null;
             },
             },
             reset(customer) {
             reset(customer) {
                 this.tags= [];
                 this.tags= [];
@@ -100,21 +107,20 @@
                 });
                 });
             },
             },
             handleInputConfirm() {
             handleInputConfirm() {
-                let inputValue = this.inputValue;
-                if (inputValue) {
+                let inputValue = this.inputValue.trim();
+                if (inputValue && !this.tags.includes(inputValue)) {
                     this.tags.push(inputValue);
                     this.tags.push(inputValue);
+                    this.form.tags=this.tags.toString();
                 }
                 }
                 this.inputVisible = false;
                 this.inputVisible = false;
                 this.inputValue = '';
                 this.inputValue = '';
-                this.form.tags=this.tags.toString();
             },
             },
             submitForm() {
             submitForm() {
                 this.$refs["form"].validate(valid => {
                 this.$refs["form"].validate(valid => {
                     if (valid) {
                     if (valid) {
                         updateCustomer(this.form).then(response => {
                         updateCustomer(this.form).then(response => {
                             if (response.code === 200) {
                             if (response.code === 200) {
-                                this.msgSuccess("操作成功");
-                                
+                                this.$message.success("操作成功");
                                 this.$emit('close');
                                 this.$emit('close');
                             }
                             }
                         });
                         });
@@ -125,19 +131,60 @@
     };
     };
 </script>
 </script>
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-.contents{
-    height: 100%;
-    background-color: #fff;
-    padding: 20px;
+.tag-dialog-content {
+    padding: 10px 0;
+    
+    .dialog-header {
+        display: flex;
+        align-items: center;
+        margin-bottom: 24px;
         
         
-}
-.footer{
-    display: flex;
-    align-items: center;
-    justify-content: flex-end;
+        .header-icon {
+            font-size: 24px;
+            color: #e6a23c;
+            margin-right: 12px;
+        }
+        
+        .header-title {
+            font-size: 16px;
+            font-weight: 600;
+            color: #303133;
+        }
+    }
+    
+    .tag-form {
+        .tags-container {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px;
+            align-items: center;
+            min-height: 40px;
+        }
+        
+        .selected-tag {
+            margin: 0;
+        }
+        
+        .button-new-tag {
+            background: #f5f7fa;
+            border: 1px dashed #d9d9d9;
+            color: #606266;
+            
+            &:hover {
+                background: #ecf5ff;
+                border-color: #409eff;
+                color: #409eff;
+            }
+        }
+        
+        .form-tip {
+            margin-top: 8px;
+            font-size: 12px;
+            color: #909399;
+            display: flex;
+            align-items: center;
+            gap: 4px;
+        }
+    }
 }
 }
 </style>
 </style>
-
-
-
- 

+ 738 - 468
src/views/crm/components/customerDetails.vue

@@ -1,503 +1,773 @@
 <template>
 <template>
-    <div class="contents" v-if="item!=null">
-        <div class="customer-title"  >
-            <div class="customer-name">
-                {{ showDuplicate?item.customerName+"[从]":item.customerName}}
-                 <el-button size="mini"  v-if="showDuplicate"  v-hasPermi="['crm:customer:lookDuplicate']"  @click=" getDetails" >主客户</el-button>
+    <div class="customer-detail-container" v-if="item!=null">
+        <!-- 顶部客户信息卡片 -->
+        <div class="customer-header-card">
+            <div class="customer-avatar">
+                <el-avatar :size="64" icon="el-icon-user-solid"></el-avatar>
+            </div>
+            <div class="customer-info">
+                <div class="customer-main-info">
+                    <h2 class="customer-name-title">
+                        {{ showDuplicate ? item.customerName + '[从]' : item.customerName }}
+                        <el-tag v-if="item.status" :type="getStatusType(item.status)" size="medium"
+                                style="margin-left: 12px;"
+                        >
+                            {{ getStatusText(item.status) }}
+                        </el-tag>
+                    </h2>
+                    <div class="customer-meta">
+                        <span class="meta-item">
+                            <i class="el-icon-phone"></i>
+                            {{ item.mobile || '未填写' }}
+                            <el-button type="text" v-if="isReceive" size="mini"
+                                       @click="callNumber(item.customerId,null,null,null)" icon="el-icon-phone-outline"
+                            >拨打</el-button>
+                            <el-button type="text" v-if="isReceive" size="mini" @click="handleSms(item.mobile)"
+                                       icon="el-icon-message"
+                            >短信</el-button>
+                        </span>
+                        <span class="meta-item">
+                            <i class="el-icon-location"></i>
+                            {{ item.address || '未填写' }}
+                        </span>
+                        <span class="meta-item">
+                            <i class="el-icon-time"></i>
+                            创建于 {{ item.createTime }}
+                        </span>
+                    </div>
+                </div>
+                <div class="customer-actions">
+                    <el-button type="primary" size="small" icon="el-icon-edit" v-hasPermi="['crm:customer:edit']"
+                               @click="handleEdit()"
+                    >编辑客户
+                    </el-button>
+                    <el-button type="warning" size="small" icon="el-icon-price-tag" v-hasPermi="['crm:customer:addTag']"
+                               @click="handleAddTag()"
+                    >打标签
+                    </el-button>
+                    <el-button type="info" size="small" icon="el-icon-document" v-hasPermi="['crm:customer:addRemark']"
+                               @click="handleAddRemark()"
+                    >备注
+                    </el-button>
+                    <el-button size="small" v-if="showDuplicate" v-hasPermi="['crm:customer:lookDuplicate']"
+                               @click="getDetails"
+                    >
+                        主客户
+                    </el-button>
+                </div>
+            </div>
+        </div>
+
+        <!-- 标签展示区 -->
+        <div class="customer-tags-section" v-if="item && item.tags">
+            <span class="tags-label">客户标签:</span>
+            <el-tag v-for="tag in item.tags.split(',')" :key="tag" size="small" effect="plain"
+                    style="margin-right: 8px;"
+            >
+                {{ tag }}
+            </el-tag>
+        </div>
+
+        <!-- 信息分组展示 -->
+        <div class="info-sections">
+            <!-- 基本信息 -->
+            <div class="info-section">
+                <div class="section-header">
+                    <span class="section-title"><i class="el-icon-user"></i> 基本信息</span>
+                </div>
+                <div class="section-content">
+                    <el-row :gutter="24">
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">客户编号</span>
+                                <span class="info-value">{{ item.customerCode || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">客户名称</span>
+                                <span class="info-value">{{ item.customerName || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">性别</span>
+                                <span class="info-value">
+                                    <el-tag v-for="dict in sexOptions" :key="dict.dictValue"
+                                            v-if="item.sex==dict.dictValue" size="mini"
+                                    >{{ dict.dictLabel }}</el-tag>
+                                    <span v-if="!item.sex">-</span>
+                                </span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">微信号</span>
+                                <span class="info-value">{{ item.weixin || '-' }}</span>
+                            </div>
+                        </el-col>
+                    </el-row>
+                    <el-row :gutter="24">
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">客户来源</span>
+                                <span class="info-value">
+                                    <el-tag v-for="dict in sourceOptions" :key="dict.dictValue"
+                                            v-if="item.source==dict.dictValue" size="mini" type="info"
+                                    >{{ dict.dictLabel }}</el-tag>
+                                    <span v-if="!item.source">-</span>
+                                </span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">客户类型</span>
+                                <span class="info-value">
+                                    <el-tag v-for="dict in typeOptions" :key="dict.dictValue"
+                                            v-if="item.customerType==dict.dictValue" size="mini" type="warning"
+                                    >{{ dict.dictLabel }}</el-tag>
+                                    <span v-if="!item.customerType">-</span>
+                                </span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">最后一次跟进</span>
+                                <span class="info-value">{{ item.visitTime || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">入公海时间</span>
+                                <span class="info-value">{{ item.poolTime || '-' }}</span>
+                            </div>
+                        </el-col>
+                    </el-row>
+                </div>
+            </div>
+
+            <!-- 购买信息 -->
+            <div class="info-section">
+                <div class="section-header">
+                    <span class="section-title"><i class="el-icon-shopping-cart-2"></i> 购买信息</span>
+                </div>
+                <div class="section-content">
+                    <el-row :gutter="24">
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">购买渠道</span>
+                                <span class="info-value">{{ item.platformName || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">购买商品</span>
+                                <span class="info-value">{{ item.goodsName || '-' }} {{
+                                        item.goodsSpecification || ''
+                                    }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">购买店铺</span>
+                                <span class="info-value">{{ item.shopName || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">消费金额</span>
+                                <span class="info-value highlight">¥{{ item.payMoney || '0' }}</span>
+                            </div>
+                        </el-col>
+                    </el-row>
+                    <el-row :gutter="24">
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">购买次数</span>
+                                <span class="info-value">{{ item.buyCount || '0' }} 次</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">来源渠道编码</span>
+                                <span class="info-value">{{ item.sourceCode || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">推荐编码</span>
+                                <span class="info-value">{{ item.pushCode || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">推荐时间</span>
+                                <span class="info-value">{{ item.pushTime || '-' }}</span>
+                            </div>
+                        </el-col>
+                    </el-row>
+                </div>
+            </div>
+
+            <!-- 进线信息 -->
+            <div class="info-section">
+                <div class="section-header">
+                    <span class="section-title"><i class="el-icon-connection"></i> 进线信息</span>
+                </div>
+                <div class="section-content">
+                    <el-row :gutter="24">
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">进线日期</span>
+                                <span class="info-value">{{ item.registerDate || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">进线方式</span>
+                                <span class="info-value">{{ item.registerType || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">进线填写时间</span>
+                                <span class="info-value">{{ item.registerSubmitTime || '-' }}</span>
+                            </div>
+                        </el-col>
+                        <el-col :span="6">
+                            <div class="info-item">
+                                <span class="info-label">进线链接</span>
+                                <span class="info-value text-ellipsis" :title="item.registerLinkUrl">{{
+                                        item.registerLinkUrl || '-'
+                                    }}</span>
+                            </div>
+                        </el-col>
+                    </el-row>
+                    <el-row :gutter="24">
+                        <el-col :span="24">
+                            <div class="info-item">
+                                <span class="info-label">进线客户详情</span>
+                                <span class="info-value">{{ item.registerDesc || '-' }}</span>
+                            </div>
+                        </el-col>
+                    </el-row>
+                </div>
+            </div>
+
+            <!-- 扩展信息 -->
+            <div class="info-section" v-if="exts && exts.length > 0">
+                <div class="section-header">
+                    <span class="section-title"><i class="el-icon-tickets"></i> 扩展信息</span>
+                </div>
+                <div class="section-content">
+                    <el-row :gutter="24">
+                        <el-col :span="6" v-for="ext in exts" :key="ext.extId">
+                            <div class="info-item">
+                                <span class="info-label">{{ ext.name }}</span>
+                                <span class="info-value">{{ ext.value || '-' }}</span>
+                            </div>
+                        </el-col>
+                    </el-row>
+                </div>
+            </div>
+
+            <!-- 备注信息 -->
+            <div class="info-section" v-if="item.remark">
+                <div class="section-header">
+                    <span class="section-title"><i class="el-icon-document"></i> 备注信息</span>
+                </div>
+                <div class="section-content">
+                    <div class="remark-content">{{ item.remark }}</div>
+                </div>
             </div>
             </div>
-            <div>
-                <el-button size="mini"  v-hasPermi="['crm:customer:edit']"  @click=" handleEdit()">修改客户</el-button>
-                <el-button size="mini"  v-hasPermi="['crm:customer:addTag']"  @click=" handleAddTag()" >打标签</el-button>
-                <el-button size="mini"  v-hasPermi="['crm:customer:addRemark']"  @click=" handleAddRemark()" >修改备注</el-button>
 
 
+            <!-- AI标签 -->
+            <div class="info-section">
+                <div class="ai-tag-wrapper" v-if="item && item.customerId">
+                    <ai-tag-panel ref="aiTagPanel" :customer-id="item.customerId" @tag-change="handleTagChange"
+                    ></ai-tag-panel>
+                </div>
             </div>
             </div>
         </div>
         </div>
-        <el-descriptions title="" :column="3" border>
-            <el-descriptions-item label="客户编号"  >
-                <span v-if="item!=null">{{item.customerCode}}</span>
-            </el-descriptions-item>
-            <el-descriptions-item label="客户名称" >
-                <span v-if="item!=null">{{item.customerName}}</span>
-            </el-descriptions-item>
-            <el-descriptions-item label="手机号" >
-                <span v-if="item!=null">{{item.mobile}}</span>
-                 <el-button type="text"  v-if="isReceive" size="mini" @click="callNumber(item.customerId,null,null,null)">拨号</el-button>
-                 <el-button type="text" v-if="isReceive" size="mini" @click="handleSms(item.mobile)">短信</el-button>
-                 <el-button icon="el-icon-search" size="mini" @click="handleMobile" style="margin-left: 20px;" circle v-hasPermi="['crm:customer:query2']"></el-button>
-            </el-descriptions-item>
-            <el-descriptions-item label="性别" >
-                <span v-if="item!=null">
-                    <el-tag  v-for="(dict, index) in sexOptions"    v-if="item.sex==dict.dictValue">{{dict.dictLabel}}</el-tag>
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="微信号" >
-                <span v-if="item!=null">
-                    {{item.weixin}}
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="所在地">
-                <span v-if="item!=null">
-                    {{item.address}}
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="客户来源" >
-                <span v-if="item!=null">
-                    <el-tag  v-for="(dict, index) in sourceOptions"    v-if="item.source==dict.dictValue">{{dict.dictLabel}}</el-tag>
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="客户类型" >
-                <span v-if="item!=null">
-                    <el-tag  v-for="(dict, index) in typeOptions"    v-if="item.customerType==dict.dictValue">{{dict.dictLabel}}</el-tag>
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="客户状态" >
-                <span v-if="item!=null">
-                    <el-tag  v-for="(dict, index) in statusOptions"    v-if="item.status==dict.dictValue">{{dict.dictLabel}}</el-tag>
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="创建时间" >
-                <span v-if="item!=null">
-                    {{item.createTime}}
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="最后一次跟进时间" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.visitTime}}
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="入公海时间" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.poolTime}}
-                </span>
-            </el-descriptions-item>
-
-            <el-descriptions-item label="标签" label-class-name="my-label">
-                <span v-if="item!=null && item.tags != null">
-                    <el-tag v-for="tag in item.tags.split(',')" size="mini">{{ tag }}</el-tag>
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-
-            <el-descriptions-item label="进线日期" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.registerDate}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-            <el-descriptions-item label="进线链接" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.registerLinkUrl}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-            <el-descriptions-item label="进线客户详情" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.registerDesc}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-            <el-descriptions-item label="进线客户填写时间" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.registerSubmitTime}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-            <el-descriptions-item label="进线方式" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.registerType}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-          <el-descriptions-item label="购买渠道" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.platformName || ''}}
-                </span>
-
-          </el-descriptions-item>
-          <el-descriptions-item label="购买商品" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.goodsName || ''}} - {{item.goodsSpecification || ''}}
-                </span>
-
-          </el-descriptions-item>
-          <el-descriptions-item label="购买店铺" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.shopName || ''}}
-                </span>
-
-          </el-descriptions-item>
-            <el-descriptions-item label="消费金额" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.payMoney}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-            <el-descriptions-item label="购买次数" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.buyCount}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-            <el-descriptions-item label="来源渠道编码" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.sourceCode}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-            <el-descriptions-item label="推荐编码" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.pushCode}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-            <el-descriptions-item label="推荐时间" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.pushTime}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-
-            <el-descriptions-item :label="ext.name" v-for="ext in exts" label-class-name="my-label">
-                <span >
-                    {{ext.value}}
-                </span>
-            </el-descriptions-item>
-            <el-descriptions-item label="备注" label-class-name="my-label">
-                <span v-if="item!=null">
-                    {{item.remark}}
-                </span>
-                <!-- <el-button size="mini" icon="el-icon-edit"></el-button> -->
-            </el-descriptions-item>
-
-        </el-descriptions>
-
-        <el-tabs style="margin-top:15px;"  z-index = "99" type="border-card" v-model="activeName" @tab-click="handleClick">
-            <el-tab-pane label="跟进记录" name="visit">
-                <customer-visit-list ref="visit"></customer-visit-list>
-            </el-tab-pane>
-            <el-tab-pane label="联系人" name="contacts">
-                <customer-contacts ref="contacts"></customer-contacts>
-            </el-tab-pane>
-            <el-tab-pane label="订单记录" name="storeOrder">
-                <customer-store-order-list ref="storeOrder"></customer-store-order-list>
-            </el-tab-pane>
-
-            <el-tab-pane label="通话记录" name="voiceLogs">
-                <customer-voice-logs-list ref="voiceLogs"></customer-voice-logs-list>
-            </el-tab-pane>
-            <el-tab-pane label="短信记录" name="smsLogs">
-                <customer-sms-logs-list ref="smsLogs"></customer-sms-logs-list>
-            </el-tab-pane>
-            <el-tab-pane label="客户日志" name="logs">
-                <customer-logs-list ref="logs"></customer-logs-list>
-            </el-tab-pane>
-            <el-tab-pane label="历史订单" name="hisOrder">
-                <customer-his-order-list ref="hisOrder"></customer-his-order-list>
-            </el-tab-pane>
-            <el-tab-pane label="AI通话记录" name="aiVoiceLogs">
-                <ai-call-voice-log ref="aiVoiceRef"></ai-call-voice-log>
-            </el-tab-pane>
-             <el-tab-pane label="AI加微记录" name="aiAddWxLogs">
-                <ai-add-wx-log ref="aiAddWxRef"></ai-add-wx-log>
-            </el-tab-pane>
-             <el-tab-pane label="AI短信记录" name="aiSendMsgLogs">
-                <ai-send-msg-log ref="aiSendMsgRef"></ai-send-msg-log>
-            </el-tab-pane>
-        </el-tabs>
-
-        <el-dialog :title="addTag.title" :visible.sync="addTag.open" width="600px" append-to-body>
+
+        <!-- Tab 和 AI 标签区域 -->
+        <div class="content-area">
+            <el-tabs v-model="activeName" type="card" @tab-click="handleClick" class="customer-tabs">
+                <el-tab-pane label="跟进记录" name="visit">
+                    <customer-visit-list ref="visit"></customer-visit-list>
+                </el-tab-pane>
+                <el-tab-pane label="联系人" name="contacts">
+                    <customer-contacts ref="contacts"></customer-contacts>
+                </el-tab-pane>
+                <el-tab-pane label="订单记录" name="storeOrder">
+                    <customer-store-order-list ref="storeOrder"></customer-store-order-list>
+                </el-tab-pane>
+                <el-tab-pane label="通话记录" name="voiceLogs">
+                    <customer-voice-logs-list ref="voiceLogs"></customer-voice-logs-list>
+                </el-tab-pane>
+                <el-tab-pane label="短信记录" name="smsLogs">
+                    <customer-sms-logs-list ref="smsLogs"></customer-sms-logs-list>
+                </el-tab-pane>
+                <el-tab-pane label="客户日志" name="logs">
+                    <customer-logs-list ref="logs"></customer-logs-list>
+                </el-tab-pane>
+                <el-tab-pane label="历史订单" name="hisOrder">
+                    <customer-his-order-list ref="hisOrder"></customer-his-order-list>
+                </el-tab-pane>
+                <el-tab-pane label="AI 通话" name="aiVoiceLogs">
+                    <ai-call-voice-log ref="aiVoiceRef"></ai-call-voice-log>
+                </el-tab-pane>
+                <el-tab-pane label="AI 加微" name="aiAddWxLogs">
+                    <ai-add-wx-log ref="aiAddWxRef"></ai-add-wx-log>
+                </el-tab-pane>
+                <el-tab-pane label="AI 短信" name="aiSendMsgLogs">
+                    <ai-send-msg-log ref="aiSendMsgRef"></ai-send-msg-log>
+                </el-tab-pane>
+            </el-tabs>
+        </div>
+
+        <!-- 弹窗 -->
+        <el-dialog :title="addTag.title" :visible.sync="addTag.open" width="600px" append-to-body :show-close="true" class="customer-dialog">
             <add-tag ref="tag" @close="closeTag()"></add-tag>
             <add-tag ref="tag" @close="closeTag()"></add-tag>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="addTag.open = false">取 消</el-button>
+                <el-button type="primary" @click="$refs.tag.submitForm()">确 定</el-button>
+            </span>
         </el-dialog>
         </el-dialog>
-        <el-dialog :title="addRemark.title" :visible.sync="addRemark.open" width="600px" append-to-body>
+        <el-dialog :title="addRemark.title" :visible.sync="addRemark.open" width="600px" append-to-body :show-close="true" class="customer-dialog">
             <add-remark ref="remark" @close="closeRemark()"></add-remark>
             <add-remark ref="remark" @close="closeRemark()"></add-remark>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="addRemark.open = false">取 消</el-button>
+                <el-button type="primary" @click="$refs.remark.submitForm()">确 定</el-button>
+            </span>
         </el-dialog>
         </el-dialog>
-        <el-dialog :title="addSms.title" :visible.sync="addSms.open" width="800px" append-to-body>
+        <el-dialog :title="addSms.title" :visible.sync="addSms.open" width="800px" append-to-body class="customer-dialog">
             <add-sms ref="sms" @close="closeSms()"></add-sms>
             <add-sms ref="sms" @close="closeSms()"></add-sms>
         </el-dialog>
         </el-dialog>
-
-        <el-dialog :title="customer.title" :visible.sync="customer.open" width="1000px" append-to-body>
+        <el-dialog :title="customer.title" :visible.sync="customer.open" width="1000px" append-to-body :show-close="true" class="customer-dialog">
             <add-or-edit-customer ref="customer" @close="closeCustomer()"></add-or-edit-customer>
             <add-or-edit-customer ref="customer" @close="closeCustomer()"></add-or-edit-customer>
+            <span slot="footer" class="dialog-footer">
+                <el-button @click="customer.open = false">取 消</el-button>
+                <el-button type="primary" @click="$refs.customer.submitForm()">确 定</el-button>
+            </span>
         </el-dialog>
         </el-dialog>
-
-        <!-- <el-dialog :title="duplicate.title" :visible.sync="duplicate.open" width="800px" append-to-body>
-            <duplicate-customer ref="duplicateCustomer" @close="closeDuplicate()"></duplicate-customer>
-        </el-dialog> -->
-
-       <el-drawer size="75%" :modal="false" :title="duplicate.title" :visible.sync="duplicate.open">
-            <duplicate-customer  ref="duplicateCustomer" />
+        <el-drawer size="75%" :modal="false" :title="duplicate.title" :visible.sync="duplicate.open">
+            <duplicate-customer ref="duplicateCustomer"/>
         </el-drawer>
         </el-drawer>
-
     </div>
     </div>
 </template>
 </template>
 
 
 <script>
 <script>
-    import { listCustomerExt } from "@/api/crm/customerExt";
-    import customerVisitList from '../components/customerVisitList.vue';
-    import customerLogsList from '../components/customerLogsList.vue';
-    import customerSmsLogsList from '../components/customerSmsLogsList.vue';
-    import customerVoiceLogsList from '../components/customerVoiceLogsList.vue';
-    import customerStoreOrderList from '../components/customerStoreOrderList.vue';
-    import duplicateCustomer from '../components/duplicateCustomer.vue';
-    import customerContacts from './customerContacts.vue';
-    import customerHisOrderList from '../components/customerHisOrderList.vue';
-    import aiCallVoiceLog from './aiCallVoiceLog';
-    import aiAddWxLog from './aiAddWxLog';
-    import aiSendMsgLog from './aiSendMsgLog';
-    import { getCustomerDetails1,updateCustomer,getCustomer1 } from "@/api/crm/customer";
-    import addTag from './addTag.vue';
-    import addRemark from './addRemark.vue';
-    import addSms from './addSms.vue';
-    import addOrEditCustomer from '../components/addOrEditCustomer.vue';
-    export default {
-        name: "customer",
-        components: {customerHisOrderList,addOrEditCustomer,addSms,addTag,addRemark, customerContacts,customerVisitList,customerLogsList,
-        customerVoiceLogsList,customerStoreOrderList,customerSmsLogsList,duplicateCustomer,aiCallVoiceLog,aiAddWxLog,aiSendMsgLog },
-        data() {
-            return {
-                calleesId:null,
-                roboticId:null,
-                customer:{
-                    open:false,
-                    title:"修改客户"
-                },
-                isReceive:false,
-                tagId:null,
-                tagsOptions:[],
-                addSms:{
-                    open:false,
-                    title:"发短信"
-                },
-                addTag:{
-                    open:false,
-                    title:"打标签"
-                },
-                addRemark:{
-                    open:false,
-                    title:"客户备注"
-                },
-                duplicate:{
-                    open:false,
-                    title:"客户详情"
-                },
-                customerId:null,
-                 // 弹出层标题
-                title: "",
-                // 是否显示弹出层
-                open: false,
-                cityIds:[],
-                citys:[],
-                tags:[],
-                inputVisible: false,
-                inputValue: '',
-                receiveOptions:[],
-                statusOptions:[],
-                typeOptions:[],
-                sourceOptions:[],
-                sexOptions:[],
-                customerExts:[],
-                activeName:"",
-                item:null,
-                showDuplicate:false,
-                dCustomerId:null,
-
-            };
-        },
-        created() {
-            this.getDicts("crm_customer_source").then((response) => {
-                this.sourceOptions = response.data;
-            });
-            this.getDicts("sys_sex").then((response) => {
-                this.sexOptions = response.data;
-            });
-            this.getDicts("crm_customer_tag").then((response) => {
-                this.tagsOptions = response.data;
-            });
-            this.getDicts("crm_customer_status").then((response) => {
-                this.statusOptions = response.data;
-            });
-            this.getDicts("crm_customer_type").then((response) => {
-                this.typeOptions = response.data;
-            });
-            this.getDicts("crm_customer_is_receive").then((response) => {
-                this.receiveOptions = response.data;
-            });
+import { listCustomerExt } from '@/api/crm/customerExt'
+import customerVisitList from '../components/customerVisitList.vue'
+import customerLogsList from '../components/customerLogsList.vue'
+import customerSmsLogsList from '../components/customerSmsLogsList.vue'
+import customerVoiceLogsList from '../components/customerVoiceLogsList.vue'
+import customerStoreOrderList from '../components/customerStoreOrderList.vue'
+import duplicateCustomer from '../components/duplicateCustomer.vue'
+import customerContacts from './customerContacts.vue'
+import customerHisOrderList from '../components/customerHisOrderList.vue'
+import aiCallVoiceLog from './aiCallVoiceLog'
+import aiAddWxLog from './aiAddWxLog'
+import aiSendMsgLog from './aiSendMsgLog'
+import AiTagPanel from './AiTagPanel.vue'
+import { getCustomerDetails1, updateCustomer, getCustomer1 } from '@/api/crm/customer'
+import addTag from './addTag.vue'
+import addRemark from './addRemark.vue'
+import addSms from './addSms.vue'
+import addOrEditCustomer from '../components/addOrEditCustomer.vue'
 
 
+export default {
+    name: 'customer',
+    components: {
+        customerHisOrderList,
+        addOrEditCustomer,
+        addSms,
+        addTag,
+        addRemark,
+        customerContacts,
+        customerVisitList,
+        customerLogsList,
+        customerVoiceLogsList,
+        customerStoreOrderList,
+        customerSmsLogsList,
+        duplicateCustomer,
+        aiCallVoiceLog,
+        aiAddWxLog,
+        aiSendMsgLog,
+        AiTagPanel
+    },
+    data() {
+        return {
+            calleesId: null,
+            roboticId: null,
+            customer: { open: false, title: '修改客户' },
+            isReceive: false,
+            tagId: null,
+            tagsOptions: [],
+            addSms: { open: false, title: '发短信' },
+            addTag: { open: false, title: '打标签' },
+            addRemark: { open: false, title: '客户备注' },
+            duplicate: { open: false, title: '客户详情' },
+            customerId: null,
+            title: '',
+            open: false,
+            cityIds: [],
+            citys: [],
+            tags: [],
+            inputVisible: false,
+            inputValue: '',
+            receiveOptions: [],
+            statusOptions: [],
+            typeOptions: [],
+            sourceOptions: [],
+            sexOptions: [],
+            customerExts: [],
+            activeName: '',
+            item: null,
+            showDuplicate: false,
+            dCustomerId: null
+        }
+    },
+    created() {
+        this.getDicts('crm_customer_source').then((response) => {
+            this.sourceOptions = response.data
+        })
+        this.getDicts('sys_sex').then((response) => {
+            this.sexOptions = response.data
+        })
+        this.getDicts('crm_customer_tag').then((response) => {
+            this.tagsOptions = response.data
+        })
+        this.getDicts('crm_customer_status').then((response) => {
+            this.statusOptions = response.data
+        })
+        this.getDicts('crm_customer_type').then((response) => {
+            this.typeOptions = response.data
+        })
+        this.getDicts('crm_customer_is_receive').then((response) => {
+            this.receiveOptions = response.data
+        })
+    },
+    methods: {
+        getStatusType(status) {
+            const statusMap = { '1': 'success', '2': 'warning', '3': 'danger', '4': 'info' }
+            return statusMap[status] || 'info'
         },
         },
-        mounted(){
-
+        getStatusText(status) {
+            const item = this.statusOptions.find(d => d.dictValue === status)
+            return item ? item.dictLabel : status
+        },
+        handleMobile() {
+            const customerId = this.item.customerId
+            getCustomer1(customerId).then(response => {
+                this.item.mobile = response.mobile
+            })
+        },
+        handleEdit() {
+            this.customer.open = true
+            var that = this
+            setTimeout(() => {
+                that.$refs.customer.handleUpdate(that.customerId)
+            }, 200)
+        },
+        closeCustomer() {
+            this.customer.open = false
+            this.getDetails(this.customerId)
+        },
+        tagsChange(e) {
+            var item = this.tagsOptions.find(val => val.dictValue === e)
+            this.tags.push(item.dictLabel)
+            this.form.tags = this.tags.toString()
         },
         },
-        methods: {
-            handleMobile(){
-                const customerId = this.item.customerId;
-                getCustomer1(customerId).then(response =>{
-                    this.item.mobile = response.mobile;
+        closeSms() {
+            this.addSms.open = false
+            this.getDetails(this.customerId)
+        },
+        handleSms(mobile) {
+            this.addSms.open = true
+            var that = this
+            setTimeout(() => {
+                that.$refs.sms.reset(this.item.customerId, mobile, 1)
+            }, 500)
+        },
+        closeRemark() {
+            this.addRemark.open = false
+            this.getDetails(this.customerId)
+        },
+        handleAddRemark() {
+            this.addRemark.open = true
+            var that = this
+            setTimeout(() => {
+                that.$refs.remark.reset(this.item)
+            }, 500)
+        },
+        closeTag() {
+            this.addTag.open = false
+            this.getDetails(this.customerId)
+        },
+        handleAddTag() {
+            this.addTag.open = true
+            var that = this
+            setTimeout(() => {
+                that.$refs.tag.reset(this.item)
+            }, 500)
+        },
+        handleClick(tab, event) {
+            if (tab.name == 'contacts') {
+                this.$refs.contacts.getData(this.item.customerId)
+            }
+            if (tab.name == 'visit') {
+                this.$refs.visit.getData(this.item.customerId, this.isReceive)
+            }
+            if (tab.name == 'logs') {
+                this.$refs.logs.getData(this.item.customerId)
+            }
+            if (tab.name == 'voiceLogs') {
+                this.$refs.voiceLogs.getData(this.item.customerId)
+            }
+            if (tab.name == 'storeOrder') {
+                this.$refs.storeOrder.getData(this.item.customerId)
+            }
+            if (tab.name == 'smsLogs') {
+                this.$refs.smsLogs.getData(this.item.customerId)
+            }
+            if (tab.name == 'hisOrder') {
+                this.$refs.hisOrder.getData(this.item.customerId)
+            }
+            if (tab.name == 'aiVoiceLogs') {
+                this.$refs.aiVoiceRef.getData(this.item.customerId, this.calleesId)
+            } else if (tab.name == 'aiAddWxLogs') {
+                this.$refs.aiAddWxRef.getData(this.item.customerId, this.roboticId)
+            } else if (tab.name == 'aiSendMsgLogs') {
+                this.$refs.aiSendMsgRef.getData(this.item.customerId, this.calleesId, this.roboticId)
+            }
+        },
+        getDetails(customerId, calleesId, roboticId) {
+            if (!!calleesId) {
+                this.calleesId = calleesId
+            }
+            if (!!roboticId) {
+                this.roboticId = roboticId
+            }
+            var data = { customerId: customerId }
+            this.customerId = customerId
+            var that = this
+            this.exts = []
+            listCustomerExt(data).then(response => {
+                this.customerExts = response.data
+                this.customerExts.forEach(element => {
+                    var data = { extId: element.extId, name: element.name, value: '' }
+                    this.exts.push(data)
                 })
                 })
-            },
-            handleEdit() {
-                this.customer.open = true;
-                var that=this;
-                setTimeout(() => {
-                    that.$refs.customer.handleUpdate(that.customerId);
-                }, 200);
-            },
-            closeCustomer(){
-                this.customer.open=false;
-                this.getDetails(this.customerId)
-            },
-            tagsChange(e){
-                var item=this.tagsOptions.find(val => val.dictValue === e);
-                console.log(item);
-                this.tags.push(item.dictLabel);
-                this.form.tags=this.tags.toString();
-            },
-            closeSms(){
-                this.addSms.open=false;
-                this.getDetails(this.customerId)
-            },
-            handleSms(mobile){
-                this.addSms.open=true;
-                var that=this;
-                setTimeout(() => {
-                    that.$refs.sms.reset(this.item.customerId,mobile,1);
-                }, 500);
-
-            },
-            closeRemark(){
-                this.addRemark.open=false;
-                this.getDetails(this.customerId)
-            },
-            handleAddRemark(){
-                this.addRemark.open=true;
-                var that=this;
-                setTimeout(() => {
-                    that.$refs.remark.reset(this.item);
-                }, 500);
-            },
-            closeTag(){
-                this.addTag.open=false;
-                this.getDetails(this.customerId)
-            },
-            handleAddTag(){
-                this.addTag.open=true;
-                var that=this;
-                setTimeout(() => {
-                    that.$refs.tag.reset(this.item);
-                }, 500);
-
-            },
-            handleClick(tab, event) {
-                if(tab.name=="contacts"){
-                    this.$refs.contacts.getData(this.item.customerId);
-                }
-                if(tab.name == "visit"){
-                    this.$refs.visit.getData(this.item.customerId,this.isReceive);
-                }
-                if(tab.name=="logs"){
-                    this.$refs.logs.getData(this.item.customerId);
+            })
+            getCustomerDetails1(data).then(response => {
+                this.item = response.customer
+                this.isReceive = response.isReceive
+                if (this.item.extJson != null) {
+                    var extList = JSON.parse(this.item.extJson)
+                    that.exts.forEach(item => {
+                        extList.forEach(element => {
+                            if (item.extId == element.extId) {
+                                item.value = element.value
+                            }
+                        })
+                    })
                 }
                 }
-                if(tab.name=="voiceLogs"){
-                    this.$refs.voiceLogs.getData(this.item.customerId);
-                }
-                if(tab.name=="storeOrder"){
-                    this.$refs.storeOrder.getData(this.item.customerId);
-                }
-                if(tab.name=="smsLogs"){
-                    this.$refs.smsLogs.getData(this.item.customerId);
-                }
-                if(tab.name=="hisOrder"){
-                    this.$refs.hisOrder.getData(this.item.customerId);
-                }
-                if(tab.name=="aiVoiceLogs"){
-                    console.log(this.item.customerId);
-                    this.$refs.aiVoiceRef.getData(this.item.customerId,this.calleesId);
-                } else if(tab.name=="aiAddWxLogs"){
-                    console.log(this.item.customerId);
-                    this.$refs.aiAddWxRef.getData(this.item.customerId,this.roboticId);
-                } else if(tab.name=="aiSendMsgLogs"){
-                    console.log(this.item.customerId);
-                    this.$refs.aiSendMsgRef.getData(this.item.customerId,this.calleesId,this.roboticId);
-                }
-            },
-            getDetails(customerId,calleesId,roboticId) {
-                if(!!calleesId){
-                    this.calleesId = calleesId;
-                    console.log(this.calleesId);
-                }
-                if(!!roboticId){
-                    this.roboticId = roboticId;
-                    console.log(this.roboticId);
-                }
-                var data={customerId:customerId}
-                this.customerId=customerId;
-                var that=this;
-                this.exts=[];
-                listCustomerExt(data).then(response => {
-                    this.customerExts = response.data;
-                    this.customerExts.forEach(element => {
-                        var data={extId:element.extId,name:element.name,value:""};
-                        this.exts.push(data)
-                    });
-                });
-                getCustomerDetails1(data).then(response => {
-                    this.item = response.customer;
-                    this.isReceive=response.isReceive;
-                    if(this.item.extJson!=null){
-                        var extList=JSON.parse(this.item.extJson);
-                        that.exts.forEach(item => {
-                            extList.forEach(element => {
-                                if(item.extId==element.extId){
-                                    item.value=element.value
-                                }
-                            });
-
-                        });
-                    }
-                    this.activeName="visit"
-                    setTimeout(() => {
-                        that.$refs.visit.getData(customerId);
-                    }, 500);
-                });
-            },
-            initDuplicate(isDuplicate,dCustomerId){
-
-                this.showDuplicate=isDuplicate;
-                this.dCustomerId=dCustomerId;
-            },
-            handleDuplicate(){
-                this.duplicate.open=true;
-                var that=this;
-
+                this.activeName = 'visit'
                 setTimeout(() => {
                 setTimeout(() => {
-                    that.$refs.duplicateCustomer.getDetails(that.dCustomerId);
-                }, 200);
-            },
-            closeDuplicate(){
-                this.duplicate.open=false;
-                this.getDetails(this.customerId)
-            },
+                    that.$refs.visit.getData(customerId)
+                }, 500)
+            })
+        },
+        initDuplicate(isDuplicate, dCustomerId) {
+            this.showDuplicate = isDuplicate
+            this.dCustomerId = dCustomerId
+        },
+        handleDuplicate() {
+            this.duplicate.open = true
+            var that = this
+            setTimeout(() => {
+                that.$refs.duplicateCustomer.getDetails(that.dCustomerId)
+            }, 200)
+        },
+        closeDuplicate() {
+            this.duplicate.open = false
+            this.getDetails(this.customerId)
+        },
+        handleTagChange() {
+            console.log('AI 标签发生变化')
         }
         }
-    };
+    }
+}
 </script>
 </script>
+
 <style lang="scss" scoped>
 <style lang="scss" scoped>
-.contents{
-    height: 100%;
-    background-color: #fff;
-    padding: 0px 20px;
+.customer-detail-container {
+    background-color: #f5f7fa;
+    min-height: 100%;
+    padding: 20px;
+}
+
+.customer-header-card {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 12px;
+    padding: 24px;
+    display: flex;
+    align-items: center;
+    margin-bottom: 20px;
+    box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
+
+    .customer-avatar {
+        margin-right: 20px;
+    }
+
+    .customer-info {
+        flex: 1;
+        display: flex;
+        justify-content: space-between;
+        align-items: center;
+    }
+
+    .customer-main-info {
+        .customer-name-title {
+            color: #fff;
+            font-size: 24px;
+            margin: 0 0 12px 0;
+            display: flex;
+            align-items: center;
+        }
+
+        .customer-meta {
+            display: flex;
+            gap: 24px;
 
 
+            .meta-item {
+                color: rgba(255, 255, 255, 0.9);
+                font-size: 14px;
+                display: flex;
+                align-items: center;
+                gap: 6px;
+
+                i {
+                    font-size: 16px;
+                }
+
+                .el-button {
+                    color: rgba(255, 255, 255, 0.9);
+                    padding: 0 8px;
+
+                    &:hover {
+                        color: #fff;
+                    }
+                }
+            }
+        }
+    }
+
+    .customer-actions {
+        display: flex;
+        gap: 12px;
+    }
 }
 }
-.customer-title{
-    margin-bottom: 15px;
+
+.customer-tags-section {
+    background: #fff;
+    border-radius: 8px;
+    padding: 16px 20px;
+    margin-bottom: 20px;
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
-    justify-content: space-between;
-    .customer-name{
-        font-size: 28px;
+
+    .tags-label {
+        font-weight: 500;
+        color: #606266;
+        margin-right: 12px;
     }
     }
 }
 }
-</style>
-<style>
-  .el-descriptions-item__label.is-bordered-label{
-    font-weight: normal;
-  }
 
 
+.info-sections {
+    margin-bottom: 20px;
+}
+
+.info-section {
+    background: #fff;
+    border-radius: 8px;
+    margin-bottom: 16px;
+    overflow: hidden;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+
+    .section-header {
+        padding: 16px 20px;
+        border-bottom: 1px solid #ebeef5;
+        background: #fafbfc;
+
+        .section-title {
+            font-size: 15px;
+            font-weight: 600;
+            color: #303133;
+            display: flex;
+            align-items: center;
+            gap: 8px;
+
+            i {
+                color: #409eff;
+            }
+        }
+    }
+
+    .section-content {
+        padding: 20px;
+    }
+}
+
+.info-item {
+    margin-bottom: 16px;
+
+    .info-label {
+        display: block;
+        font-size: 12px;
+        color: #909399;
+        margin-bottom: 6px;
+    }
+
+    .info-value {
+        font-size: 14px;
+        color: #303133;
+
+        &.highlight {
+            color: #f56c6c;
+            font-weight: 600;
+            font-size: 16px;
+        }
+
+        &.text-ellipsis {
+            overflow: hidden;
+            text-overflow: ellipsis;
+            white-space: nowrap;
+        }
+    }
+}
+
+.remark-content {
+    background: #fafbfc;
+    padding: 16px;
+    border-radius: 6px;
+    color: #606266;
+    line-height: 1.6;
+}
+
+.content-area {
+    .customer-tabs {
+        background: #fff;
+        border-radius: 8px;
+        padding: 16px;
+        box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
+    }
+
+    .ai-tag-wrapper {
+        position: sticky;
+        top: 20px;
+    }
+}
 </style>
 </style>