Преглед на файлове

Merge remote-tracking branch 'origin/master'

zyy преди 1 месец
родител
ревизия
427175356b

+ 51 - 0
public/static/images/ai-avatar.svg

@@ -0,0 +1,51 @@
+<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+  <!-- 渐变背景 -->
+  <defs>
+    <linearGradient id="aiGrad" x1="0%" y1="0%" x2="100%" y2="100%">
+      <stop offset="0%" style="stop-color:#667eea;stop-opacity:1" />
+      <stop offset="100%" style="stop-color:#764ba2;stop-opacity:1" />
+    </linearGradient>
+    
+    <!-- 发光效果 -->
+    <filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
+      <feGaussianBlur stdDeviation="3" result="coloredBlur"/>
+      <feMerge>
+        <feMergeNode in="coloredBlur"/>
+        <feMergeNode in="SourceGraphic"/>
+      </feMerge>
+    </filter>
+  </defs>
+  
+  <!-- 圆形背景 -->
+  <circle cx="50" cy="50" r="48" fill="url(#aiGrad)"/>
+  
+  <!-- AI 芯片/大脑图案 -->
+  <g filter="url(#glow)">
+    <!-- 中心圆环 -->
+    <circle cx="50" cy="50" r="20" fill="none" stroke="white" stroke-width="2.5" opacity="0.9"/>
+    <circle cx="50" cy="50" r="14" fill="none" stroke="white" stroke-width="2" opacity="0.7"/>
+    
+    <!-- 四个角的连接点 -->
+    <circle cx="50" cy="30" r="3" fill="white"/>
+    <circle cx="70" cy="50" r="3" fill="white"/>
+    <circle cx="50" cy="70" r="3" fill="white"/>
+    <circle cx="30" cy="50" r="3" fill="white"/>
+    
+    <!-- 连接线 -->
+    <line x1="50" y1="33" x2="50" y2="47" stroke="white" stroke-width="1.5" opacity="0.6"/>
+    <line x1="67" y1="50" x2="53" y2="50" stroke="white" stroke-width="1.5" opacity="0.6"/>
+    <line x1="50" y1="67" x2="50" y2="53" stroke="white" stroke-width="1.5" opacity="0.6"/>
+    <line x1="33" y1="50" x2="47" y2="50" stroke="white" stroke-width="1.5" opacity="0.6"/>
+    
+    <!-- 中心 AI 核心 -->
+    <circle cx="50" cy="50" r="6" fill="white" opacity="0.9"/>
+    
+    <!-- 电路纹理 -->
+    <path d="M 50 30 L 50 25 M 50 75 L 50 70 M 30 50 L 25 50 M 75 50 L 70 50" 
+          stroke="white" stroke-width="2" stroke-linecap="round" opacity="0.5"/>
+  </g>
+  
+  <!-- AI 标识文字 -->
+  <text x="50" y="54" font-family="Arial, sans-serif" font-size="10" font-weight="bold" 
+        fill="white" text-anchor="middle" dominant-baseline="middle" opacity="0.95">AI</text>
+</svg>

+ 41 - 0
public/static/images/customer-avatar.svg

@@ -0,0 +1,41 @@
+<svg width="100" height="100" viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
+  <!-- 渐变背景 -->
+  <defs>
+    <linearGradient id="customerGrad" x1="0%" y1="0%" x2="100%" y2="100%">
+      <stop offset="0%" style="stop-color:#10b981;stop-opacity:1" />
+      <stop offset="100%" style="stop-color:#059669;stop-opacity:1" />
+    </linearGradient>
+    
+    <!-- 发光效果 -->
+    <filter id="customerGlow" x="-50%" y="-50%" width="200%" height="200%">
+      <feGaussianBlur stdDeviation="2" result="coloredBlur"/>
+      <feMerge>
+        <feMergeNode in="coloredBlur"/>
+        <feMergeNode in="SourceGraphic"/>
+      </feMerge>
+    </filter>
+  </defs>
+  
+  <!-- 圆形背景 -->
+  <circle cx="50" cy="50" r="48" fill="url(#customerGrad)"/>
+  
+  <!-- 专业用户图标 -->
+  <g filter="url(#customerGlow)">
+    <!-- 外环装饰 -->
+    <circle cx="50" cy="50" r="35" fill="none" stroke="white" stroke-width="1.5" opacity="0.3"/>
+    
+    <!-- 头部轮廓 -->
+    <circle cx="50" cy="40" r="16" fill="white" opacity="0.95"/>
+    
+    <!-- 身体轮廓 -->
+    <path d="M 28 82 Q 28 65 50 65 Q 72 65 72 82 L 72 85 Q 72 88 68 88 L 32 88 Q 28 88 28 85 Z" 
+          fill="white" opacity="0.9"/>
+    
+    <!-- 领带/商务元素 -->
+    <path d="M 50 65 L 50 78 M 45 68 L 50 65 L 55 68" 
+          stroke="#10b981" stroke-width="2" fill="none" opacity="0.4" stroke-linecap="round" stroke-linejoin="round"/>
+    
+    <!-- 肩膀线条装饰 -->
+    <path d="M 30 75 Q 50 68 70 75" stroke="#10b981" stroke-width="1.5" fill="none" opacity="0.3" stroke-linecap="round"/>
+  </g>
+</svg>

+ 61 - 0
src/api/crm/customerAnalyze.js

@@ -0,0 +1,61 @@
+import request from '@/utils/request'
+
+// 查询客户聊天记录分析列表
+export function listAnalyze(query) {
+  return request({
+    url: '/crm/analyze/list',
+    method: 'get',
+    params: query
+  })
+}
+// 查询客户聊天记录分析列表
+export function listAllAnalyze(query) {
+    return request({
+        url: '/crm/analyze/listAll',
+        method: 'get',
+        params: query
+    })
+}
+
+// 查询客户聊天记录分析详细
+export function getAnalyze(id) {
+  return request({
+    url: '/crm/analyze/' + id,
+    method: 'get'
+  })
+}
+
+// 新增客户聊天记录分析
+export function addAnalyze(data) {
+  return request({
+    url: '/crm/analyze',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改客户聊天记录分析
+export function updateAnalyze(data) {
+  return request({
+    url: '/crm/analyze',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除客户聊天记录分析
+export function delAnalyze(id) {
+  return request({
+    url: '/crm/analyze/' + id,
+    method: 'delete'
+  })
+}
+
+// 导出客户聊天记录分析
+export function exportAnalyze(query) {
+  return request({
+    url: '/crm/analyze/export',
+    method: 'get',
+    params: query
+  })
+}

+ 147 - 0
src/api/qw/courseWatchLog.js

@@ -0,0 +1,147 @@
+import request from '@/utils/request'
+
+// 查询短链课程看课记录列表
+export function listCourseWatchLog(query) {
+  return request({
+    url: '/course/courseWatchLog/list',
+    method: 'POST',
+    data: query
+  })
+}
+
+// 查询短链课程看课记录列表
+export function listCourseWatchLogPage(query) {
+  return request({
+    url: '/course/courseWatchLog/pageList',
+    method: 'post',
+    data: query
+  })
+}
+
+// 查询短链课程看课记录详细
+export function getCourseWatchLog(logId) {
+  return request({
+    url: '/course/courseWatchLog/' + logId,
+    method: 'get'
+  })
+}
+
+// 新增短链课程看课记录
+export function addCourseWatchLog(data) {
+  return request({
+    url: '/course/courseWatchLog',
+    method: 'post',
+    data: data
+  })
+}
+
+// 修改短链课程看课记录
+export function updateCourseWatchLog(data) {
+  return request({
+    url: '/course/courseWatchLog',
+    method: 'put',
+    data: data
+  })
+}
+
+// 删除短链课程看课记录
+export function delCourseWatchLog(logId) {
+  return request({
+    url: '/course/courseWatchLog/' + logId,
+    method: 'delete'
+  })
+}
+
+// 导出短链课程看课记录
+export function exportCourseWatchLog(query) {
+  return request({
+    url: '/course/courseWatchLog/export',
+    method: 'post',
+    data: query
+  })
+}
+//会员看课统计导出
+export function exportCourseWatchLogStatisticsExport(query) {
+  return request({
+    url: '/course/courseWatchLog/statisticsExport',
+    method: 'POST',
+    data: query
+  })
+}
+
+
+
+export function statisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/statisticsList',
+    method: 'get',
+    params: query
+  })
+}
+
+export function qwWatchLogStatisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/qwWatchLogStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+
+export function qwWatchLogAllStatisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/qwWatchLogAllStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function myQwWatchLogStatisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/myQwWatchLogStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function myQwWatchLogAllStatisticsList(query) {
+  return request({
+    url: '/course/courseWatchLog/myQwWatchLogAllStatisticsList',
+    method: 'get',
+    params: query
+  })
+}
+export function watchLogStatistics(query) {
+  return request({
+    url: '/course/courseWatchLog/watchLogStatistics',
+    method: 'get',
+    params: query
+  })
+}
+
+// 查询课程小结详情总体数据
+export function getCourseStatisticsDetail(videoId, periodId) {
+  return request({
+    url: '/course/courseWatchLog/courseStatisticsDetail',
+    method: 'get',
+    params: {
+      videoId: videoId,
+      periodId: periodId
+    }
+  })
+}
+
+// 查询课程小结用户详情列表(分页)
+export function getCourseStatisticsUserDetailList(params) {
+  return request({
+    url: '/course/courseWatchLog/courseStatisticsUserDetail',
+    method: 'get',
+    params: params
+  })
+}
+
+// 导出课程小结用户详情(按创建时间倒序,最多50000条)
+export function exportCourseStatisticsUserDetail(videoId, periodId) {
+  return request({
+    url: '/course/courseWatchLog/courseStatisticsUserDetailExport',
+    method: 'get',
+    params: { videoId, periodId }
+  })
+}

+ 17 - 3
src/router/index.js

@@ -5,7 +5,6 @@ Vue.use(Router)
 
 /* Layout */
 import Layout from '@/layout'
-import ParentView from '@/components/ParentView';
 import LiveConsole from "@/views/live/liveConsole/index.vue";
 
 /**
@@ -317,7 +316,7 @@ export const constantRoutes = [
       meta: { title: 'AI工作流', activeMenu: '/company/aiWorkflow' }
     }
   ]
-},,
+},
   {
   path: '/companyWx/companyWorkflow',
   component: () => import('@/layout/index'),
@@ -390,12 +389,27 @@ export const constantRoutes = [
                 }
             }
         ]
-    }
+    },
 
+    {
+        path: '/crm/customer/detail/:customerId',
+        component: Layout,
+        hidden: true,
+        children: [
+            {
+                path: '',
+                component: () => import('@/views/crm/customer/customerDetail.vue'),
+                name: 'CustomerDetail',
+                meta: { title: '客户详情', activeMenu: '/crm/customer' }
+            }
+        ]
+    },
 
 ]
 
 
+
+
 export default new Router({
   mode: 'history', // 去掉url中的#
   scrollBehavior: () => ({ y: 0 }),

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

@@ -42,6 +42,10 @@
                 <span class="tip-text">(到第几位结束)</span>
               </el-form-item>
             </template>
+            
+             <el-form-item label="是否允许重复客户导入" prop="allowRepeatCustomer">
+              <el-switch v-model="cidConfig.allowRepeatCustomer"></el-switch>
+            </el-form-item>
 
             <div class="line"></div>
             <div style="float:right;margin-right:20px">

+ 56 - 7
src/views/company/companyVoiceRobotic/index.vue

@@ -94,7 +94,22 @@
        <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>
+          <el-tag v-if="scope.row.taskType == 2">场景任务 - {{scope.row.sceneTypeName}}</el-tag>
+        </template>
+      </el-table-column>
+       <el-table-column label="生效时间段" align="center" prop="taskType" width="250">
+        <template slot-scope="scope">
+          <div v-if="scope.row.taskType == 1">
+            {{scope.row.runtimeRangeStart}} ~ {{scope.row.runtimeRangeEnd}}
+          </div>
+          <div v-if="scope.row.taskType == 2" >
+            <div v-if="!!scope.row.availableStartTime && !!scope.row.availableEndTime ">
+            适用:{{scope.row.availableStartTime}} ~ {{scope.row.availableEndTime}}
+            </div>
+            <div v-if="!!scope.row.runtimeRangeStart && !!scope.row.runtimeRangeEnd">
+            运行:{{scope.row.runtimeRangeStart}} ~ {{scope.row.runtimeRangeEnd}}
+            </div>
+          </div>
         </template>
       </el-table-column>
       <el-table-column label="任务状态" align="center">
@@ -270,6 +285,40 @@
                 <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="runtimeRangeStart">
+                     <el-time-select
+                  placeholder="任务运行开始时间"
+                   style="width: 100%;"
+                  v-model="form.runtimeRangeStart"
+                  key="runtimeRangeStart"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22: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="runtimeRangeEnd">
+                     <el-time-select
+                      style="width: 100%;"
+                  placeholder="任务运行结束时间"
+                  v-model="form.runtimeRangeEnd"
+                  key="runtimeRangeEnd"
+                  :picker-options="{
+                    start: '07:00',
+                    step: '00:30',
+                    end: '22:00',
+                    minTime: form.runtimeRangeStart
+                  }">
+                </el-time-select>
+                  </el-form-item>
+                </el-col>
+              </el-form-item>
             <el-form-item label="适用时间" required>
                 <el-col :span="11">
                   <el-form-item prop="availableStartTime">
@@ -281,7 +330,7 @@
                   :picker-options="{
                     start: '00:00',
                     step: '00:30',
-                    end: '24:00'
+                    end: '23:30'
                   }">
                 </el-time-select>
                   </el-form-item>
@@ -297,7 +346,7 @@
                   :picker-options="{
                     start: '00:00',
                     step: '00:30',
-                    end: '24:00'
+                    end: '23:30'
                   }">
                 </el-time-select>
                   </el-form-item>
@@ -729,7 +778,7 @@ import {
   getCurrentCompanyId
 } from "@/api/company/companyVoiceRobotic";
 import draggable from 'vuedraggable'
-import { listAll } from '@/api/company/wxDialog';
+// import { listAll } from '@/api/company/wxDialog';
 import customerSelect from '@/views/crm/components/CustomerSelect.vue';
 import qwUserSelect from '@/views/components/QwUserSelect.vue';
 import qwUserSelectTwo from '@/views/components/QwUserSelectTwo.vue';
@@ -910,9 +959,9 @@ export default {
       this.robotList = e.robot;
       this.dialogList = e.dialog;
     })
-    listAll().then(e => {
-      this.wxDialogList = e.data;
-    })
+    // listAll().then(e => {
+    //   this.wxDialogList = e.data;
+    // })
     companyUserList().then(e => {
       this.qwUserList = e.data;
     })

+ 3 - 3
src/views/company/companyWorkflow/design.vue

@@ -394,12 +394,12 @@
                   />
                 </el-select>
               </el-form-item>
-              <el-form-item label="外呼网关">
+              <el-form-item label="外呼线路">
                 <el-select
                   :disabled="editAiDisable"
                   v-model="selectedNode.nodeConfig.gatewayId"
                   filterable
-                  placeholder="请选择外呼网关"
+                  placeholder="请选择外呼线路"
                   @change="handleConfigChange"
                 >
                   <el-option
@@ -1556,7 +1556,7 @@ export default {
           // callMode=2 为AI自动外呼,需要校验必选项
           if (callMode === 2) {
             if (!node.nodeConfig.gatewayId) {
-              this.$message.warning(`节点「${node.nodeName}」未选择外呼网关`)
+              this.$message.warning(`节点「${node.nodeName}」未选择外呼线路`)
               return
             }
             if (!node.nodeConfig.llmAccountId) {

+ 63 - 6
src/views/components/course/userCourseCatalogDetails.vue

@@ -62,6 +62,10 @@
                      type="text" v-if="projectFrom === 'hzyy'" @click="openDialog(scope.row)">
             复制看课链接
           </el-button>
+          <el-button size="mini"
+                   type="text" v-if="projectFrom === 'myhk'" @click="openXsyDialog(scope.row)">
+            复制参数
+          </el-button>
         </template>
       </el-table-column>
     </el-table>
@@ -127,15 +131,37 @@
       append-to-body
     />
 
+    <!-- 销售易参数弹窗 -->
+    <el-dialog
+      title="复制销售易参数"
+      :visible.sync="xsyDialogVisible"
+      width="500px"
+      append-to-body
+    >
+      <div style="margin-bottom: 20px;">
+        <el-input
+          v-model="xsyParams"
+          type="textarea"
+          :rows="3"
+          readonly
+          placeholder="参数将显示在这里"
+          class="xsy-textarea"
+        />
+      </div>
+      <div slot="footer" class="dialog-footer">
+        <el-button @click="xsyDialogVisible = false">取消</el-button>
+        <el-button type="primary" @click="copyXsyParams">复制参数</el-button>
+      </div>
+    </el-dialog>
+
   </div>
 </template>
 
 <script>
-import {getVideoListByCourseId, updatePacketMoney, updateUserCourseVideoUpdate} from "@/api/course/userCourseVideo";
-import userCourseVideoDetails from '../../components/course/userCourseVideoDetails.vue';
-import {createLinkUrl, createRoomLinkUrl, queryQwIds} from "@/api/course/sopCourseLink";
-import AutoTagDialog from "@/views/components/tag/AutoTagDialog.vue";
-import {addTag, updateTag} from "@/api/tag/api";
+import { getVideoListByCourseId, updatePacketMoney } from '@/api/course/userCourseVideo'
+import userCourseVideoDetails from '../../components/course/userCourseVideoDetails.vue'
+import { createLinkUrl, createRoomLinkUrl, queryQwIds } from '@/api/course/sopCourseLink'
+import AutoTagDialog from '@/views/components/tag/AutoTagDialog.vue'
 
 export default {
     name: "userCourseCatalog",
@@ -233,6 +259,9 @@ export default {
         },
         // 显示搜索条件
         showSearch: true,
+        // 销售易弹窗
+        xsyDialogVisible: false,
+        xsyParams: '',
         // 遮罩层
         loading: true,
         // 导出遮罩层
@@ -520,7 +549,29 @@ export default {
           this.$refs.userCourseVideoDetails.getDetails(row.videoId);
         }, 500);
 
-      }
+      },
+        // 复制销售易链接
+        openXsyDialog(row) {
+            // 获取当前登录用户信息
+            const currentUser = this.$store.state.user.user;
+            const companyId = currentUser.companyId;
+            const companyUserId = currentUser.userId;
+
+            // 构建JSON字符串
+            const courseData = {
+                videoId: row.videoId,
+                courseId: row.courseId,
+                companyId: companyId,
+                companyUserId: companyUserId
+            };
+
+            this.xsyParams = `pages_course/videoxsy?course=${JSON.stringify(courseData)}`;
+            this.xsyDialogVisible = true;
+        },
+        // 复制销售易参数
+        copyXsyParams() {
+            this.copyLink(this.xsyParams);
+        }
     }
   }
 </script>
@@ -546,3 +597,9 @@ export default {
      }
 
 </style>
+<style scoped>
+.xsy-textarea >>> .el-textarea__inner {
+    background-color: #f5f5f5 !important;
+    resize: none !important;
+}
+</style>

+ 638 - 0
src/views/course/userCoursePeriod/courseStatistics.vue

@@ -0,0 +1,638 @@
+<template>
+  <div class="course-statistics-container">
+    <!-- 筛选条件 -->
+    <el-form :inline="true" :model="queryParams" class="demo-form-inline" style="margin-bottom: 20px;">
+      <el-form-item label="小节名称">
+        <el-input
+          v-model="queryParams.videoName"
+          placeholder="请输入小节名称关键字"
+          clearable
+          size="small"
+          style="width: 300px;"
+          @keyup.enter.native="handleQuery"
+        />
+      </el-form-item>
+      <el-form-item>
+        <el-button type="primary" size="small" @click="handleQuery">查询</el-button>
+        <el-button size="small" @click="resetQuery">重置</el-button>
+      </el-form-item>
+    </el-form>
+
+    <!-- 数据表格 -->
+    <el-table
+      v-loading="loading"
+      :data="list"
+      border
+      style="width: 100%"
+    >
+      <el-table-column label="课程名称" align="center" prop="courseName" width="200" />
+      <el-table-column label="小节" align="center" prop="videoName" width="210" />
+      <el-table-column label="开课状态" align="center" prop="openStatus" width="120">
+        <template slot-scope="scope">
+          <el-tag :type="scope.row.openStatus === '已开课' ? 'success' : scope.row.openStatus === '已结束' ? 'info' : 'warning'">
+            {{ scope.row.openStatus }}
+          </el-tag>
+        </template>
+      </el-table-column>
+      <el-table-column label="营期时间" align="center" prop="periodDate" width="120" />
+      <el-table-column label="开始时间" align="center" prop="startTime" width="180" />
+      <el-table-column label="结束时间" align="center" prop="endTime" width="180" />
+      <el-table-column label="操作" align="center"  fixed="right">
+        <template slot-scope="scope">
+          <el-button
+            type="text"
+            size="small"
+            @click="handleViewDetail(scope.row)"
+          >
+            查看详情
+          </el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+
+    <!-- 分页 -->
+    <pagination
+      v-show="total > 0"
+      :total="total"
+      :page.sync="queryParams.pageNum"
+      :limit.sync="queryParams.pageSize"
+      @pagination="getList"
+    />
+
+    <!-- 课程详情抽屉 -->
+    <el-drawer
+      title="课程小结详情"
+      :visible.sync="detailDialog.visible"
+      direction="rtl"
+      size="60%"
+      :close-on-click-modal="false"
+      append-to-body
+      class="course-detail-drawer"
+    >
+      <div v-loading="detailDialog.loading" style="padding: 20px;">
+        <!-- 第一块:总体数据 -->
+        <el-card class="detail-card" shadow="never">
+          <div slot="header" class="card-header">
+            <span>总体数据</span>
+            <el-button type="primary" size="small" @click="handleViewUserDetail">查看用户详情</el-button>
+          </div>
+          <el-row :gutter="20">
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">视频时长</div>
+                <div class="stat-value">{{ formatDuration(detailDialog.data.videoDuration) }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">累计观看人数</div>
+                <div class="stat-value">{{ detailDialog.data.totalWatchCount || 0 }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">累计完课人数</div>
+                <div class="stat-value">{{ detailDialog.data.totalCompleteCount || 0 }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">到课完课率</div>
+                <div class="stat-value">{{ detailDialog.data.completeRate || '0%' }}</div>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+
+        <!-- 第二块:首次点播数据 -->
+<!--        <el-card class="detail-card" shadow="never">-->
+<!--          <div slot="header" class="card-header">-->
+<!--            <span>首次点播数据</span>-->
+<!--          </div>-->
+<!--          <el-row :gutter="20">-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">观看人数</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstWatchCount || 0 }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">>=20分钟人数(首次)</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstWatch20MinCount || 0 }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">>=30分钟人数(首次)</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstWatch30MinCount || 0 }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">到课完课率首次(>=20分钟)</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstCompleteRate20Min || '0%' }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--            <el-col :span="6">-->
+<!--              <div class="stat-item">-->
+<!--                <div class="stat-label">到课完课率首次(>=30分钟)</div>-->
+<!--                <div class="stat-value">{{ detailDialog.data.firstCompleteRate30Min || '0%' }}</div>-->
+<!--              </div>-->
+<!--            </el-col>-->
+<!--          </el-row>-->
+<!--        </el-card>-->
+
+        <!-- 第三块:实际看课数据(修复后) -->
+        <el-card class="detail-card" shadow="never">
+          <div slot="header" class="card-header">
+            <span>实际看课数据</span>
+          </div>
+          <el-row :gutter="20">
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">实际到课人数</div>
+                <div class="stat-value">{{ detailDialog.data.totalStudents || 0 }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">实际完课人数</div>
+                <div class="stat-value">{{ detailDialog.data.completedCount || 0 }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">实际完课率</div>
+                <div class="stat-value">{{ detailDialog.data.actualCompletionRate ? detailDialog.data.actualCompletionRate + '%' : '0%' }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">人均看课时长</div>
+                <div class="stat-value">{{ formatDuration(detailDialog.data.avgWatchDurationMinutes)}}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">人均完课时长</div>
+                <div class="stat-value">{{ formatDuration(detailDialog.data.avgCompletedDuration)}}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">人均完课完播率</div>
+                <div class="stat-value">{{ detailDialog.data.avgCompletionPlaybackRate ? detailDialog.data.avgCompletionPlaybackRate + '%' : '0%' }}</div>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+
+        <!-- 第四块:订单数据 -->
+        <el-card class="detail-card" shadow="never">
+          <div slot="header" class="card-header">
+            <span>订单数据</span>
+          </div>
+          <el-row :gutter="20">
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">GMV</div>
+                <div class="stat-value">¥{{ detailDialog.data.gmv || '0.00' }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">付费人数</div>
+                <div class="stat-value">{{ detailDialog.data.paidUserCount || 0 }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">付费单数</div>
+                <div class="stat-value">{{ detailDialog.data.paidOrderCount || 0 }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">总付费转换率</div>
+                <div class="stat-value">{{ detailDialog.data.totalPaidConversionRate || '0%' }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">20min付费转化率</div>
+                <div class="stat-value">{{ detailDialog.data.paidConversionRate20Min || '0%' }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">完课R值</div>
+                <div class="stat-value">{{ detailDialog.data.completeRValue || '0.00' }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">领红包人数</div>
+                <div class="stat-value">{{ detailDialog.data.redPacketUserCount || 0 }}</div>
+              </div>
+            </el-col>
+            <el-col :span="6">
+              <div class="stat-item">
+                <div class="stat-label">答题人数</div>
+                <div class="stat-value">{{ detailDialog.data.answerUserCount || 0 }}</div>
+              </div>
+            </el-col>
+          </el-row>
+        </el-card>
+
+        <!-- 第五块:单品销量统计 -->
+        <el-card class="detail-card" shadow="never">
+          <div slot="header" class="card-header">
+            <span>商品销量统计</span>
+          </div>
+          <el-table
+            :data="detailDialog.data.productList || []"
+            border
+            style="width: 100%"
+          >
+            <el-table-column label="商品名称" align="center" prop="productName" />
+            <el-table-column label="销量" align="center" prop="salesCount" />
+            <el-table-column label="销售额" align="center" prop="salesAmount">
+              <template slot-scope="scope">
+                ¥{{ scope.row.salesAmount || '0.00' }}
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-card>
+      </div>
+    </el-drawer>
+
+    <!-- 用户详情抽屉 -->
+    <el-drawer
+      title="用户看课数据"
+      :visible.sync="userDetailDialog.visible"
+      direction="rtl"
+      size="60%"
+      :close-on-click-modal="false"
+      append-to-body
+    >
+      <div v-loading="userDetailDialog.loading" style="padding: 20px;">
+        <div style="margin-bottom: 20px; text-align: right;">
+          <el-button type="primary" size="small" @click="handleExportUserDetail">导出用户详情</el-button>
+        </div>
+        <el-table
+          :data="userDetailDialog.list"
+          border
+          style="width: 100%"
+        >
+          <el-table-column label="用户名称" align="center" prop="userName" width="150" />
+<!--          <el-table-column label="观看时长" align="center" prop="watchDuration" width="120">-->
+<!--            <template slot-scope="scope">-->
+<!--              {{ formatDuration(scope.row.watchDuration) }}-->
+<!--            </template>-->
+<!--          </el-table-column>-->
+          <el-table-column label="观看时长" align="center" prop="repeatWatchDuration" width="150">
+            <template slot-scope="scope">
+              {{ formatDuration(scope.row.repeatWatchDuration) }}
+            </template>
+          </el-table-column>
+          <el-table-column label="订单数" align="center" prop="orderCount" width="100" />
+          <el-table-column label="订单金额" align="center" prop="orderAmount" width="120">
+            <template slot-scope="scope">
+              ¥{{ scope.row.orderAmount || '0.00' }}
+            </template>
+          </el-table-column>
+          <el-table-column label="分公司名称" align="center" prop="companyName" width="150" />
+          <el-table-column label="销售名称" align="center" prop="salesName" />
+        </el-table>
+
+        <!-- 分页 -->
+        <pagination
+          v-show="userDetailDialog.total > 0"
+          :total="userDetailDialog.total"
+          :page.sync="userDetailDialog.queryParams.pageNum"
+          :limit.sync="userDetailDialog.queryParams.pageSize"
+          @pagination="getUserDetailList"
+        />
+      </div>
+    </el-drawer>
+  </div>
+</template>
+
+<script>
+import { getDays } from "@/api/course/userCoursePeriod";
+import { getCourseStatisticsDetail, getCourseStatisticsUserDetailList, exportCourseStatisticsUserDetail } from "@/api/qw/courseWatchLog";
+import { download } from "@/utils/common";
+
+export default {
+  name: "CourseStatistics",
+  props: {
+    periodId: {
+      type: [String, Number],
+      default: null
+    },
+    active: {
+      type: Boolean,
+      default: false
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      list: [],
+      total: 0,
+      queryParams: {
+        pageNum: 1,
+        pageSize: 10,
+        videoName: null,
+        periodId: null
+      },
+      // 详情弹窗
+      detailDialog: {
+        visible: false,
+        loading: false,
+        data: {}
+      },
+      // 用户详情弹窗
+      userDetailDialog: {
+        visible: false,
+        loading: false,
+        list: [],
+        total: 0,
+        queryParams: {
+          pageNum: 1,
+          pageSize: 10,
+          videoId: null,
+          periodId: null
+        }
+      }
+    };
+  },
+  watch: {
+    active(newVal) {
+      if (newVal && this.periodId) {
+        this.queryParams.periodId = this.periodId;
+        this.getList();
+      }
+    },
+    periodId(newVal) {
+      if (newVal && this.active) {
+        this.queryParams.periodId = newVal;
+        this.getList();
+      }
+    }
+  },
+  mounted() {
+    if (this.active && this.periodId) {
+      this.queryParams.periodId = this.periodId;
+      this.getList();
+    }
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      if (!this.queryParams.periodId) {
+        this.loading = false;
+        return;
+      }
+      this.loading = true;
+      getDays(this.queryParams).then(response => {
+        if (response.code === 200) {
+          // 映射字段并格式化数据
+          this.list = (response.rows || []).map(item => {
+            return {
+              ...item,
+              // 映射字段
+              periodDate: item.dayDate,
+              startTime: item.startDateTime,
+              endTime: item.endDateTime,
+              // 格式化开课状态
+              openStatus: this.formatCourseStatus(item.status),
+              // 小节状态(可以根据需要设置,这里先使用开课状态)
+              videoStatus: this.formatCourseStatus(item.status)
+            };
+          });
+          this.total = response.total || 0;
+        } else {
+          this.$message.error(response.msg || '查询失败');
+          this.list = [];
+          this.total = 0;
+        }
+        this.loading = false;
+      }).catch(error => {
+        this.$message.error('查询失败:' + (error.message || '未知错误'));
+        this.loading = false;
+        this.list = [];
+        this.total = 0;
+      });
+    },
+    /** 格式化课程状态 */
+    formatCourseStatus(status) {
+      const statusMap = {
+        0: '未开始',
+        1: '已开课',
+        2: '已结束'
+      };
+      return statusMap[status] || '未知状态';
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.queryParams.videoName = null;
+      this.queryParams.pageNum = 1;
+      this.getList();
+    },
+    /** 查看详情 */
+    handleViewDetail(row) {
+      this.detailDialog.visible = true;
+      this.detailDialog.loading = true;
+
+      const videoId = row.videoId || row.id;
+      const periodId = this.queryParams.periodId;
+
+      if (!videoId || !periodId) {
+        this.$message.error('视频ID或营期ID不能为空');
+        this.detailDialog.loading = false;
+        return;
+      }
+
+      getCourseStatisticsDetail(videoId, periodId).then(response => {
+        if (response.code === 200 && response.data) {
+          const data = response.data;
+          // 安全获取实际看课数据对象
+          const actualVO = data.fsActualCompletionVO || {};
+
+          // 设置总体数据
+          this.detailDialog.data = {
+            // 总体数据
+            videoDuration: data.videoDuration ?? 0,
+            totalWatchCount: data.totalWatchCount ?? 0,
+            totalCompleteCount: data.totalCompleteCount ?? 0,
+            completeRate: data.completeRate != null ? Number(data.completeRate).toFixed(2) + '%' : '0%',
+            // 首次点播数据
+            firstWatchCount: data.firstWatchCount ?? 0,
+            firstWatch20MinCount: data.firstWatch20MinCount ?? 0,
+            firstWatch30MinCount: data.firstWatch30MinCount ?? 0,
+            firstCompleteRate20Min: data.firstCompleteRate20Min != null ? Number(data.firstCompleteRate20Min).toFixed(2) + '%' : '0%',
+            firstCompleteRate30Min: data.firstCompleteRate30Min != null ? Number(data.firstCompleteRate30Min).toFixed(2) + '%' : '0%',
+            // 实际看课数据(修复后,所有字段都有默认值)
+            totalStudents: actualVO.totalStudents ?? 0,
+            completedCount: actualVO.completedCount ?? 0,
+            actualCompletionRate: actualVO.actualCompletionRate != null ? Number(actualVO.actualCompletionRate).toFixed(2) : '0',
+            avgWatchDurationMinutes: actualVO.avgWatchDurationMinutes ?? 0,
+            avgCompletedDuration: actualVO.avgCompletedDuration ?? 0,
+            avgCompletionPlaybackRate: actualVO.avgCompletionPlaybackRate != null ? Number(actualVO.avgCompletionPlaybackRate).toFixed(2) : '0',
+            // 订单数据
+            gmv: data.gmv != null ? Number(data.gmv).toFixed(2) : '0.00',
+            paidUserCount: data.paidUserCount ?? 0,
+            paidOrderCount: data.paidOrderCount ?? 0,
+            totalPaidConversionRate: data.totalPaidConversionRate != null ? Number(data.totalPaidConversionRate).toFixed(2) + '%' : '0%',
+            paidConversionRate20Min: data.paidConversionRate20Min != null ? Number(data.paidConversionRate20Min).toFixed(2) + '%' : '0%',
+            completeRValue: data.completeRValue != null ? Number(data.completeRValue).toFixed(2) : '0.00',
+            redPacketUserCount: data.redPacketUserCount ?? 0,
+            answerUserCount: data.answerUserCount ?? 0,
+            productList: (data.productList || []).map(p => ({
+              productName: p.productName || '未知商品',
+              salesCount: p.salesCount ?? 0,
+              salesAmount: p.salesAmount != null ? Number(p.salesAmount).toFixed(2) : '0.00'
+            })),
+            videoId: videoId,
+            id: row.id
+          };
+        } else {
+          this.$message.error(response.msg || '获取数据失败');
+        }
+        this.detailDialog.loading = false;
+      }).catch(error => {
+        this.$message.error('获取数据失败:' + (error.message || '未知错误'));
+        this.detailDialog.loading = false;
+      });
+    },
+    /** 查看用户详情 */
+    handleViewUserDetail() {
+      this.userDetailDialog.visible = true;
+      this.userDetailDialog.queryParams.videoId = this.detailDialog.data.videoId || this.detailDialog.data.id;
+      this.userDetailDialog.queryParams.periodId = this.queryParams.periodId;
+      this.userDetailDialog.queryParams.pageNum = 1;
+      this.getUserDetailList();
+    },
+    /** 获取用户详情列表 */
+    getUserDetailList() {
+      const videoId = this.userDetailDialog.queryParams.videoId;
+      const periodId = this.userDetailDialog.queryParams.periodId;
+      if (!videoId || !periodId) {
+        this.$message.error('视频ID或营期ID不能为空');
+        this.userDetailDialog.loading = false;
+        return;
+      }
+      this.userDetailDialog.loading = true;
+      getCourseStatisticsUserDetailList(this.userDetailDialog.queryParams).then(response => {
+        if (response.code === 200 && response.data) {
+          const d = response.data;
+          this.userDetailDialog.list = d.list || d.rows || [];
+          this.userDetailDialog.total = d.total ?? 0;
+        } else {
+          this.userDetailDialog.list = [];
+          this.userDetailDialog.total = 0;
+        }
+        this.userDetailDialog.loading = false;
+      }).catch(() => {
+        this.userDetailDialog.list = [];
+        this.userDetailDialog.total = 0;
+        this.userDetailDialog.loading = false;
+      });
+    },
+    /** 导出用户详情(按创建时间倒序,最多50000条) */
+    handleExportUserDetail() {
+      const videoId = this.userDetailDialog.queryParams.videoId;
+      const periodId = this.userDetailDialog.queryParams.periodId;
+      if (!videoId || !periodId) {
+        this.$message.error('请先查看用户详情');
+        return;
+      }
+      this.$confirm('是否确认导出用户看课数据?(按创建时间倒序,最多50000条)', '提示', {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }).then(() => {
+        exportCourseStatisticsUserDetail(videoId, periodId).then(response => {
+          if (response.code === 200 && response.msg) {
+            download(response.msg);
+            this.$message.success('导出成功');
+          } else {
+            this.$message.error(response.msg || '导出失败');
+          }
+        }).catch(() => {
+          this.$message.error('导出失败');
+        });
+      }).catch(() => {});
+    },
+      /** 格式化时长 */
+      formatDuration(seconds) {
+          if (seconds == null || isNaN(seconds)) return '0秒';
+          let total = Math.abs(seconds);
+          const hours = Math.floor(total / 3600);
+          const minutes = Math.floor((total % 3600) / 60);
+          const secs = Math.floor(total % 60);
+
+          const parts = [];
+          if (hours > 0) parts.push(`${hours}小时`);
+          if (minutes > 0) parts.push(`${minutes}分`);
+          if (secs > 0 || parts.length === 0) {
+              parts.push(`${secs}秒`);
+          }
+
+          return parts.join('');
+      }
+  }
+};
+</script>
+
+<style scoped lang="scss">
+.course-statistics-container {
+  padding: 20px;
+}
+
+.detail-card {
+  margin-bottom: 20px;
+
+  .card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    font-weight: bold;
+    font-size: 16px;
+  }
+
+  .stat-item {
+    text-align: center;
+    padding: 20px;
+    border: 1px solid #EBEEF5;
+    border-radius: 4px;
+    background-color: #F5F7FA;
+    margin-top: 10px;
+
+    .stat-label {
+      font-size: 14px;
+      color: #606266;
+      margin-bottom: 10px;
+    }
+
+    .stat-value {
+      font-size: 24px;
+      font-weight: bold;
+      color: #303133;
+    }
+  }
+}
+
+.course-detail-drawer {
+  ::v-deep .el-drawer__body {
+    overflow-y: auto;
+  }
+}
+</style>

+ 10 - 1
src/views/course/userCoursePeriod/index.vue

@@ -301,6 +301,13 @@
               :active="activeTab === 'statistics'"
             />
           </el-tab-pane>
+
+            <el-tab-pane label="课程数据" name="courseStatistics">
+                <course-statistics-data
+                    :periodId="periodSettingsData.periodId"
+                    :active="activeTab === 'courseStatistics'"
+                />
+            </el-tab-pane>
         </el-tabs>
       </div>
     </el-drawer>
@@ -337,13 +344,15 @@ import {courseList, videoList} from '@/api/course/courseRedPacketLog'
 import RedPacket from './redPacket.vue'
 import BatchRedPacket from './batchRedPacket.vue'
 import CourseStatistics from './statistics.vue'
+import CourseStatisticsData from './courseStatistics.vue'
 
 export default {
   name: "Period",
   components: {
     RedPacket,
     BatchRedPacket,
-    CourseStatistics
+    CourseStatistics,
+      CourseStatisticsData
   },
   data() {
     return {

+ 1872 - 0
src/views/crm/customer/customerDetail.vue

@@ -0,0 +1,1872 @@
+<template>
+    <div class="customer-container">
+        <div class="main-grid-three-columns">
+            <div class="left-column">
+                <!-- 客户画像 (成交要素) -->
+                <div class="card">
+                    <div class="card-header">
+                        <h3><i class="fas fa-id-card"></i> 客户画像(成交要素)</h3>
+                    </div>
+                    <div class="profile-grid">
+                        <div class="profile-item profile-item-main">
+                            <span class="label"><i class="fas fa-user"></i> 客户姓名:</span>
+                            <span class="value highlight">{{ customerData.customerName }}</span>
+                        </div>
+                        <template v-for="(value, key) in customerPortraitData">
+                            <div
+                                v-if="key !== '需求'"
+                                :key="key"
+                                class="profile-item"
+                            >
+                                <span class="label">
+                                    <i class="fas fa-info-circle"></i> {{ key }}:
+                                </span>
+                                <span class="value">{{ value }}</span>
+                            </div>
+                        </template>
+                        <!-- 需求单独显示,占满整行 -->
+                        <div
+                            v-if="customerPortraitData['需求']"
+                            key="需求"
+                            class="profile-item profile-item-full"
+                        >
+                            <span class="label">
+                                <i class="fas fa-bullseye"></i> 需求:
+                            </span>
+                            <span class="value long-text">{{ customerPortraitData['需求'] }}</span>
+                        </div>
+                    </div>
+                </div>
+                <!-- AI 标签 -->
+                <div class="card">
+                    <div class="card-header">
+                        <h3>
+                            <i class="fas fa-tags"></i> AI 标签
+                        </h3>
+                    </div>
+                    <div class="tags-container">
+                        <div v-if="allAiTags.length > 0" class="tags-grid">
+                            <div
+                                v-for="(item, index) in visibleTags"
+                                :key="item.id"
+                                class="tag-chip"
+                                :class="{ 'tag-highlight': index < 3 }"
+                            >
+                                <i class="fas fa-tag tag-icon"></i>
+                                <span class="tag-name">{{ item.propertyName }}</span>
+                                <span class="tag-separator">:</span>
+                                <span class="tag-value">{{ item.propertyValue }}</span>
+                            </div>
+                        </div>
+                        <div v-else class="empty-tags">
+                            <i class="fas fa-inbox"></i>
+                            <span>暂无AI标签</span>
+                        </div>
+
+                        <!-- 加载更多按钮 -->
+                        <div v-if="allAiTags.length > tagsPageSize" class="tags-actions">
+                            <button
+                                v-if="!isExpanded"
+                                @click="loadMoreTags"
+                                class="btn-expand-tags"
+                                type="button"
+                            >
+                                <i class="fas fa-chevron-down"></i> 展开全部 ({{ allAiTags.length - tagsPageSize }})
+                            </button>
+
+                            <!-- 收起按钮 -->
+                            <button
+                                v-else
+                                @click="collapseTags"
+                                class="btn-collapse-tags"
+                                type="button"
+                            >
+                                <i class="fas fa-chevron-up"></i> 收起标签
+                            </button>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <div class="middle-column">
+                <!-- 沟通摘要 -->
+                <div class="card">
+                    <div class="card-header">
+                        <h3><i class="fas fa-comment-dots"></i> 沟通摘要</h3>
+                    </div>
+                    <div class="summary-text compact">
+                        {{ getCommunicationAbstract() }}
+                    </div>
+                </div>
+                <!-- AI 沟通总结 -->
+                <div class="card card-highlight">
+                    <div class="card-header">
+                        <h3><i class="fas fa-robot"></i> AI 沟通总结</h3>
+                    </div>
+                    <div class="summary-content">
+                        <p class="summary-text">{{ getCommunicationSummary() }}</p>
+                    </div>
+                    <div class="update-time-corner">沟通时间:{{ getUpdateTime() }}</div>
+                </div>
+                <!-- 沟通记录 -->
+                <div class="card card-table">
+                    <div class="card-header">
+                        <h3><i class="fas fa-history"></i> 沟通记录</h3>
+                    </div>
+                    <div class="records-table-wrapper">
+                        <table class="records-table">
+                            <thead>
+                                <tr>
+                                    <th><i class="fas fa-user"></i> 客户名称</th>
+                                    <th><i class="fas fa-chart-line"></i> 流失等级</th>
+                                    <th><i class="fas fa-heart"></i> 客户意向度</th>
+                                    <th><i class="far fa-clock"></i> 创建时间</th>
+                                    <th><i class="fas fa-cog"></i> 操作</th>
+                                </tr>
+                            </thead>
+                            <tbody>
+                                <tr v-for="record in communicationRecords" :key="record.id" class="record-row">
+                                    <td class="record-cell">{{ customerData.customerName }}</td>
+                                    <td class="record-cell">
+                                        <span class="risk-level-tag" :class="getRecordRiskLevelClass(record)">
+                                            {{ getRecordRiskLevelLabel(record) }}
+                                        </span>
+                                    </td>
+                                    <td class="record-cell">
+                                        <span class="intention-degree">
+                                            {{ getIntentionDegreeFromRecord(record) }}%
+                                        </span>
+                                    </td>
+                                    <td class="record-cell">{{ record.createTime }}</td>
+                                    <td class="record-cell">
+                                        <button @click="viewChat(record)" class="btn-view-chat">
+                                            <i class="fas fa-comments"></i> 聊天详情
+                                        </button>
+                                    </td>
+                                </tr>
+                                <tr v-if="!communicationRecords.length">
+                                    <td colspan="5" class="empty-tip">
+                                        <i class="fas fa-inbox"></i> 暂无沟通记录
+                                    </td>
+                                </tr>
+                            </tbody>
+                        </table>
+
+                        <!-- 分页组件 -->
+                        <div class="pagination-container" v-if="communicationRecordsTotal > 0">
+                            <el-pagination
+                                @current-change="handleCommunicationRecordsPageChange"
+                                @size-change="handleCommunicationRecordsSizeChange"
+                                :current-page="communicationRecordsPageNum"
+                                :page-sizes="[3, 10, 20, 50]"
+                                :page-size="communicationRecordsPageSize"
+                                layout="total, sizes, prev, pager, next, jumper"
+                                :total="communicationRecordsTotal"
+                            />
+                        </div>
+                    </div>
+                </div>
+                <!-- 微信风格聊天弹窗 -->
+                <div v-if="chatDialogVisible" class="chat-dialog-overlay" @click.self="closeChatDialog">
+                    <div class="chat-dialog">
+                        <div class="chat-dialog-header">
+                            <div class="chat-title">
+                                <i class="fas fa-comments"></i>
+                                <span>{{ (currentChatRecord && currentChatRecord.customerName) || (customerData && customerData.customerName) }} - 历史聊天记录</span>
+                            </div>
+                            <button @click="closeChatDialog" class="btn-close">
+                                ×
+                            </button>
+                        </div>
+                        <div class="chat-dialog-body">
+                            <div class="chat-messages">
+                                <!-- 根据 aiChatRecord 数组循环显示聊天记录 -->
+                                <div
+                                    v-for="(msg, index) in parseChatMessages(currentChatRecord && currentChatRecord.aiChatRecord)"
+                                    :key="index"
+                                    class="message-item"
+                                    :class="msg.type === 'ai' ? 'message-left' : 'message-right'"
+                                >
+                                    <!-- AI 消息:头像在左,名称在聊天内容上方靠左 -->
+                                    <div v-if="msg.type === 'ai'" class="message-wrapper message-wrapper-left">
+                                        <div class="message-avatar message-avatar-ai">
+                                            <img src="/static/images/ai-avatar.svg" alt="AI" @error="handleAvatarError($event, 'ai')" />
+                                        </div>
+                                        <div class="message-content">
+                                            <div class="message-name message-name-ai">AI</div>
+                                            <div class="message-bubble">
+                                                {{ msg.content }}
+                                            </div>
+                                        </div>
+                                    </div>
+
+                                    <!-- 客户消息:强制头像在右侧 -->
+                                    <div v-else class="message-item message-item-customer">
+                                        <div class="message-content-right">
+                                            <div class="message-bubble message-bubble-right">
+                                                {{ msg.content }}
+                                            </div>
+                                        </div>
+                                        <div class="message-avatar message-avatar-customer">
+                                            <img src="/static/images/customer-avatar.svg" alt="客户" @error="handleAvatarError($event, 'customer')" />
+                                        </div>
+                                    </div>
+                                </div>
+
+                                <!-- 空数据提示 -->
+                                <div v-if="!parseChatMessages(currentChatRecord && currentChatRecord.aiChatRecord).length" class="empty-chat-tip">
+                                    <i class="fas fa-inbox"></i> 暂无聊天内容
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 流失风险等级 + 客户关注点 & 意向度 -->
+            <div class="right-column">
+                <!-- 流失风险等级 -->
+                <div class="card risk-card" :class="getRiskLevelClass()">
+                    <div class="card-header">
+                        <h3><i class="fas fa-chart-line"></i> 流失风险等级</h3>
+                        <span class="risk-badge" :class="getRiskLevelBadgeClass()">{{ getRiskLevelLabel() }}</span>
+                    </div>
+                    <div class="risk-analysis">
+                        <p class="risk-text">{{ getRiskLevelAnalysis() }}</p>
+                        <div class="risk-tip" v-if="getRiskLevelTip()">
+                            <i class="fas fa-exclamation-triangle"></i> {{ getRiskLevelTip() }}
+                        </div>
+                    </div>
+                </div>
+                <!-- 客户关注点 & 意向度 -->
+                <div class="card card-focus">
+                    <div class="card-header">
+                        <h3><i class="fas fa-lightbulb"></i> 客户关注点 & 意向度</h3>
+                    </div>
+                    <div class="focus-points">
+                        <div class="focus-title">
+                            <i class="fas fa-search"></i> 核心关注点:
+                        </div>
+                        <ul class="focus-list">
+                            <li v-for="(point, idx) in customerFocusPoints" :key="idx" class="focus-item">
+                                <i class="fas fa-dot-circle"></i> {{ point }}
+                            </li>
+                        </ul>
+                    </div>
+                    <div class="intention-section">
+                        <div class="intention-header">
+                            <span class="intention-label">客户意向度</span>
+                            <span v-if="getIntentionDegree() === -1" class="no-data-tip">暂无分析数据</span>
+                        </div>
+                        <div class="progress-bar-wrapper" v-if="getIntentionDegree() !== -1">
+                            <div class="progress-bar-modern">
+                                <div class="progress-fill-modern"
+                                     :style="{
+                                         width: getIntentionDegree() + '%',
+                                         background: getProgressGradient(getIntentionDegree())
+                                     }"
+                                     :class="getIntentionClass(getIntentionDegree())">
+                                    <span class="progress-text">{{ getIntentionDegree() }}%</span>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script>
+
+import {listByCustomerId} from "../../../api/crm/customerProperty";
+import {listAnalyze} from "../../../api/crm/customerAnalyze";
+
+export default {
+    data() {
+        return {
+            customerUserId: null, // 客户 ID,从路由参数获取
+            customerData: null, // 从列表页传递过来的完整客户数据
+            aiTags: [],// 需要显示的 AI 标签
+            allAiTags: [], // 存储所有 AI 标签
+            tagsPageSize: 3,//默认展开标签的数量
+            isExpanded: false, // 是否已展开显示全部标签
+            // 聊天记录分页相关
+            communicationRecords: [],
+            communicationRecordsTotal: 0,
+            communicationRecordsPageNum: 1,
+            communicationRecordsPageSize: 3,
+            // 聊天弹窗相关
+            chatDialogVisible: false, // 聊天弹窗是否显示
+            currentChatRecord: null, // 当前查看的聊天记录
+        }
+    },
+    computed: {
+        // 根据是否展开控制显示的标签数量
+        visibleTags() {
+            if (this.isExpanded) {
+                return this.allAiTags;
+            } else {
+                // 未展开时只显示前 3 条
+                return this.allAiTags.slice(0, this.tagsPageSize);
+            }
+        },
+        // 客户画像数据(从最新的沟通记录中获取)
+        customerPortraitData() {
+            if (!this.communicationRecords || this.communicationRecords.length === 0) {
+                return {};
+            }
+            // 获取最新的沟通记录
+            const latestRecord = this.communicationRecords[0];
+            if (latestRecord && latestRecord.customerPortraitJson) {
+                try {
+                    // 如果是字符串,解析为 JSON 对象
+                    if (typeof latestRecord.customerPortraitJson === 'string') {
+                        return JSON.parse(latestRecord.customerPortraitJson);
+                    }
+                    // 如果已经是对象,直接返回
+                    return latestRecord.customerPortraitJson;
+                } catch (error) {
+                    console.error('解析客户画像 JSON 失败:', error);
+                    return {};
+                }
+            }
+            return {};
+        },
+        // 客户关注点数据(从最新的沟通记录中获取)
+        customerFocusPoints() {
+            if (!this.communicationRecords || this.communicationRecords.length === 0) {
+                return ['暂无分析数据'];
+            }
+            const latestRecord = this.communicationRecords[0];
+            if (latestRecord && latestRecord.customerFocusJson) {
+                try {
+                    // 如果是字符串,尝试解析为 JSON 数组
+                    if (typeof latestRecord.customerFocusJson === 'string') {
+                        const parsed = JSON.parse(latestRecord.customerFocusJson);
+                        // 如果解析后的数组为空,返回默认提示
+                        if (Array.isArray(parsed) && parsed.length > 0) {
+                            return parsed;
+                        }
+                    }
+                    // 如果已经是数组且不为空,直接返回
+                    if (Array.isArray(latestRecord.customerFocusJson) && latestRecord.customerFocusJson.length > 0) {
+                        return latestRecord.customerFocusJson;
+                    }
+                } catch (error) {
+                    console.error('解析客户关注点 JSON 失败:', error);
+                }
+            }
+            return ['暂无分析数据'];
+        }
+    },
+    created() {
+        // 从路由参数获取客户 ID
+        this.customerUserId = this.$route.params.customerId || this.$route.query.customerUserId;
+
+        // 从 query 参数获取列表页传递的完整客户数据
+        if (this.$route.query.customerData) {
+            try {
+                this.customerData = JSON.parse(this.$route.query.customerData);
+            } catch (error) {
+                console.error('解析客户数据失败:', error);
+            }
+        }
+        // 获取客户标签
+        this.loadCustomerTags();
+        //加载客户分析信息
+        this.getCustomerInfoList();
+    },
+    methods: {
+        loadCustomerTags() {
+            listByCustomerId(this.customerUserId).then((response) => {
+                if(response.code === 200){
+                    this.allAiTags = response.data || [];
+                    // 强制 Vue 更新视图
+                    this.$forceUpdate();
+                } else {
+                    console.error('获取 AI 标签失败:', response);
+                }
+            }).catch(error => {
+                console.error('获取 AI 标签异常:', error);
+            });
+        },
+        getCustomerInfoList() {
+            const params = {
+                pageNum: this.communicationRecordsPageNum,
+                pageSize: this.communicationRecordsPageSize,
+                customerId: this.customerUserId
+            };
+            listAnalyze(params).then((response) => {
+                if(response.code === 200){
+                    this.communicationRecords = response.rows || [];
+                    this.communicationRecordsTotal = response.total || 0;
+                } else {
+                    console.error('获取客户信息失败:', response);
+                }
+            }).catch(error => {
+                console.error('获取客户信息异常:', error);
+            });
+        },
+        // 加载更多标签 - 显示全部
+        loadMoreTags() {
+            this.isExpanded = true;
+        },
+        // 收起标签 - 只显示前 3 条
+        collapseTags() {
+            this.isExpanded = false;
+        },
+        // 查看聊天内容
+        viewChat(record) {
+            this.currentChatRecord = record;
+            this.chatDialogVisible = true;
+        },
+        // 关闭聊天弹窗
+        closeChatDialog() {
+            this.chatDialogVisible = false;
+            this.currentChatRecord = null;
+        },
+        // 解析聊天消息数组
+        parseChatMessages(content) {
+            if (!content) {
+                return [];
+            }
+            // 如果 content 是字符串,尝试解析为 JSON 数组
+            if (typeof content === 'string') {
+                try {
+                    const parsed = JSON.parse(content);
+                    // 如果是数组,直接返回
+                    if (Array.isArray(parsed)) {
+                        return parsed.map(item => ({
+                            content: item.ai || item.user,
+                            type: item.ai ? 'ai' : 'user'
+                        }));
+                    }
+                    // 如果是对象,转换为数组
+                    return [{ content: parsed.content, type: parsed.type || 'ai' }];
+                } catch (e) {
+                    // 解析失败,返回空数组
+                    console.error('解析聊天记录失败:', e);
+                    return [];
+                }
+            }
+            // 如果已经是数组,直接返回
+            if (Array.isArray(content)) {
+                return content;
+            }
+            // 如果是对象,转换为数组
+            return [content];
+        },
+        // 处理头像加载失败
+        handleAvatarError(event, type) {
+            const img = event.target;
+            if (type === 'ai') {
+                // AI 头像加载失败时,使用渐变色背景 + 机器人图标
+                img.style.display = 'none';
+                img.parentElement.innerHTML = '<i class="fas fa-robot" style="font-size: 24px; color: white;"></i>';
+            } else {
+                // 客户头像加载失败时,使用渐变色背景 + 用户图标
+                img.style.display = 'none';
+                img.parentElement.innerHTML = '<i class="fas fa-user" style="font-size: 24px; color: white;"></i>';
+            }
+        },
+        // 获取意向度样式类
+        getIntentionClass(percent) {
+            if (percent >= 80) return 'excellent';
+            if (percent >= 60) return 'good';
+            if (percent >= 40) return 'normal';
+            return 'poor';
+        },
+        // 获取进度条渐变色
+        getProgressGradient(percent) {
+            if (percent >= 80) {
+                return 'linear-gradient(90deg, #10b981 0%, #059669 100%)';
+            } else if (percent >= 60) {
+                return 'linear-gradient(90deg, #3b82f6 0%, #2563eb 100%)';
+            } else if (percent >= 40) {
+                return 'linear-gradient(90deg, #f59e0b 0%, #d97706 100%)';
+            } else {
+                return 'linear-gradient(90deg, #ef4444 0%, #dc2626 100%)';
+            }
+        },
+        // 分页改变事件
+        handleCommunicationRecordsPageChange(pageNum) {
+            this.communicationRecordsPageNum = pageNum;
+            this.getCustomerInfoList();
+        },
+        // 每页条数改变事件
+        handleCommunicationRecordsSizeChange(pageSize) {
+            this.communicationRecordsPageSize = pageSize;
+            this.communicationRecordsPageNum = 1; // 重置为第一页
+            this.getCustomerInfoList();
+        },
+        // 获取沟通摘要
+        getCommunicationAbstract() {
+            if (!this.communicationRecords || this.communicationRecords.length === 0) {
+                return '暂无沟通记录';
+            }
+            const latestRecord = this.communicationRecords[0];
+            return latestRecord.communicationAbstract || '暂无沟通摘要';
+        },
+        // 获取 AI 沟通总结
+        getCommunicationSummary() {
+            if (!this.communicationRecords || this.communicationRecords.length === 0) {
+                return '暂无沟通记录';
+            }
+            const latestRecord = this.communicationRecords[0];
+            return latestRecord.communicationSummary || '暂无 AI 沟通总结';
+        },
+        // 获取最后更新时间
+        getUpdateTime() {
+            if (!this.communicationRecords || this.communicationRecords.length === 0) {
+                return '-';
+            }
+            const latestRecord = this.communicationRecords[0];
+            return latestRecord.createTime || '-';
+        },
+        // 获取流失风险等级数值
+        getAttritionLevel() {
+            if (!this.communicationRecords || this.communicationRecords.length === 0) {
+                return 0;
+            }
+            const latestRecord = this.communicationRecords[0];
+            return parseInt(latestRecord.attritionLevel) || 0;
+        },
+        // 获取流失风险等级标签
+        getRiskLevelLabel() {
+            const level = this.getAttritionLevel();
+            const labels = ['未知', '无风险', '低风险', '中风险', '高风险'];
+            return labels[level] || '未知';
+        },
+        // 获取流失风险等级徽章样式类
+        getRiskLevelBadgeClass() {
+            const level = this.getAttritionLevel();
+            const badgeClasses = [
+                'badge-unknown',
+                'badge-none',
+                'badge-low',
+                'badge-medium',
+                'badge-high'
+            ];
+            return badgeClasses[level] || 'badge-unknown';
+        },
+        // 获取客户意向度
+        getIntentionDegree() {
+            if (!this.communicationRecords || this.communicationRecords.length === 0) {
+                return -1;
+            }
+            const latestRecord = this.communicationRecords[0];
+            const degree = parseInt(latestRecord.intentionDegree);
+            // 如果是有效数字且在 0-100 之间,返回该值,否则返回 -1 表示无数据
+            return (degree >= 0 && degree <= 100) ? degree : -1;
+        },
+        // 获取单条记录的风险等级数值
+        getRecordAttritionLevel(record) {
+            if (!record) return 0;
+            return parseInt(record.attritionLevel) || 0;
+        },
+        // 获取单条记录的风险等级标签
+        getRecordRiskLevelLabel(record) {
+            const level = this.getRecordAttritionLevel(record);
+            const labels = ['未知', '无风险', '低风险', '中风险', '高风险'];
+            return labels[level] || '未知';
+        },
+        // 获取单条记录的风险等级样式类
+        getRecordRiskLevelClass(record) {
+            const level = this.getRecordAttritionLevel(record);
+            const classes = ['risk-unknown', 'risk-none', 'risk-low', 'risk-medium', 'risk-high'];
+            return classes[level] || 'risk-unknown';
+        },
+        // 获取单条记录的客户意向度
+        getIntentionDegreeFromRecord(record) {
+            if (!record) return 0;
+            const degree = parseInt(record.intentionDegree);
+            return (degree >= 0 && degree <= 100) ? degree : 0;
+        },
+        // 获取流失风险等级样式类
+        getRiskLevelClass() {
+            const level = this.getAttritionLevel();
+            const classes = ['risk-unknown', 'risk-none', 'risk-low', 'risk-medium', 'risk-high'];
+            return classes[level] || 'risk-unknown';
+        },
+        // 获取流失风险分析
+        getRiskLevelAnalysis() {
+            const level = this.getAttritionLevel();
+            if (!this.communicationRecords || this.communicationRecords.length === 0) {
+                return '暂无分析数据';
+            }
+            const latestRecord = this.communicationRecords[0];
+            return latestRecord.attritionLevelPrompt || "暂无分析数据";
+        },
+        // 获取流失风险提示
+        getRiskLevelTip() {
+            const level = this.getAttritionLevel();
+            const tips = [
+                '暂无分析',
+                '客户稳定,可以放心。',
+                '建议定期回访,了解客户最新需求。',
+                '建议安排专项跟进,深入了解客户痛点和需求。',
+                '建议立即联系客户,了解问题原因并提供解决方案。'
+            ];
+            return tips[level] || '';
+        },
+    }
+}</script>
+
+<style scoped>
+* {
+    box-sizing: border-box;
+}
+
+.customer-container {
+    max-width: 1600px;
+    margin: 0 auto;
+    font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
+    background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%);
+    padding: 24px;
+    min-height: 100vh;
+}
+
+@keyframes pulse {
+    0%, 100% {
+        transform: scale(1);
+    }
+    50% {
+        transform: scale(1.1);
+    }
+}
+
+.main-grid-three-columns {
+    display: grid;
+    grid-template-columns: 380px 1fr 340px;
+    gap: 28px;
+    animation: fadeIn 0.6s ease-in-out;
+}
+
+@keyframes fadeIn {
+    from {
+        opacity: 0;
+        transform: translateY(20px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+@media (max-width: 1400px) {
+    .main-grid-three-columns {
+        grid-template-columns: 360px 1fr 320px;
+        gap: 24px;
+    }
+}
+
+@media (max-width: 1200px) {
+    .main-grid-three-columns {
+        grid-template-columns: 1fr;
+    }
+}
+
+.card {
+    background: white;
+    border-radius: 20px;
+    padding: 24px;
+    margin-bottom: 28px;
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.08);
+    transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+    border: 1px solid rgba(226, 232, 240, 0.5);
+    position: relative;
+    overflow: hidden;
+}
+
+.card::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    height: 3px;
+    background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
+    transform: scaleX(0);
+    transition: transform 0.4s ease;
+}
+
+.card:hover::before {
+    transform: scaleX(1);
+}
+
+.card:hover {
+    box-shadow: 0 12px 48px rgba(0, 0, 0, 0.15);
+    transform: translateY(-4px);
+    border-color: rgba(102, 126, 234, 0.3);
+}
+
+/* 高亮卡片(AI 总结) */
+.card-highlight {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+    border: none;
+    box-shadow: 0 8px 32px rgba(102, 126, 234, 0.4);
+    position: relative;
+    overflow: hidden;
+}
+
+.card-highlight::after {
+    content: '';
+    position: absolute;
+    top: -50%;
+    right: -50%;
+    width: 200%;
+    height: 200%;
+    background: radial-gradient(circle, rgba(255,255,255,0.1) 0%, transparent 70%);
+    animation: shimmer 3s infinite;
+}
+
+@keyframes shimmer {
+    0%, 100% { transform: translate(0, 0); }
+    50% { transform: translate(-30%, -30%); }
+}
+
+.card-highlight .card-header h3 {
+    color: white;
+}
+
+.card-highlight .summary-text {
+    color: white;
+    font-size: 15px;
+    line-height: 1.7;
+    max-height: 150px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    padding-right: 4px;
+}
+
+.card-highlight .summary-text::-webkit-scrollbar {
+    width: 6px;
+}
+
+.card-highlight .summary-text::-webkit-scrollbar-track {
+    background: rgba(255, 255, 255, 0.2);
+    border-radius: 3px;
+}
+
+.card-highlight .summary-text::-webkit-scrollbar-thumb {
+    background: rgba(255, 255, 255, 0.4);
+    border-radius: 3px;
+}
+
+.card-highlight .summary-text::-webkit-scrollbar-thumb:hover {
+    background: rgba(255, 255, 255, 0.6);
+}
+
+.card-highlight .summary-meta span {
+    color: rgba(255, 255, 255, 0.9);
+}
+/* 表格卡片 */
+.card-table {
+    background: white;
+}
+/* 风险卡片 */
+.risk-card {
+    border-left: 4px solid #10b981;
+    background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
+    transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+.risk-card.risk-unknown {
+    border-left-color: #6b7280;
+    background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
+}
+
+.risk-card.risk-unknown::before {
+    background: linear-gradient(90deg, #6b7280 0%, #4b5563 100%);
+}
+
+.risk-card::before {
+    background: linear-gradient(90deg, #10b981 0%, #34d399 100%);
+}
+
+.risk-unknown .risk-badge {
+    background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%);
+    box-shadow: 0 2px 8px rgba(107, 114, 128, 0.3);
+}
+
+.risk-none .risk-badge {
+    background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
+    box-shadow: 0 2px 8px rgba(16, 185, 129, 0.3);
+}
+
+.risk-low .risk-badge {
+    background: linear-gradient(135deg, #3b82f6 0%, #60a5fa 100%);
+    box-shadow: 0 2px 8px rgba(59, 130, 246, 0.3);
+}
+
+.risk-medium .risk-badge {
+    background: linear-gradient(135deg, #f59e0b 0%, #fbbf24 100%);
+    box-shadow: 0 2px 8px rgba(245, 158, 11, 0.3);
+}
+
+.risk-high .risk-badge {
+    background: linear-gradient(135deg, #ef4444 0%, #f87171 100%);
+    box-shadow: 0 2px 8px rgba(239, 68, 68, 0.3);
+}
+
+.risk-card:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 12px 48px rgba(0, 0, 0, 0.1);
+}
+
+/* 风险等级标签 */
+.risk-badge {
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    padding: 6px 14px;
+    border-radius: 8px;
+    font-size: 13px;
+    font-weight: 700;
+    color: white;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+}
+
+.risk-badge::before {
+    content: '';
+    width: 6px;
+    height: 6px;
+    background: white;
+    border-radius: 50%;
+    animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+    0%, 100% {
+        opacity: 1;
+        transform: scale(1);
+    }
+    50% {
+        opacity: 0.5;
+        transform: scale(1.2);
+    }
+}
+
+.risk-card:hover .risk-badge {
+    transform: scale(1.05);
+}
+
+/* 风险分析内容 */
+.risk-analysis {
+    margin-top: 16px;
+    padding: 16px;
+    background: rgba(255, 255, 255, 0.7);
+    border-radius: 12px;
+    backdrop-filter: blur(10px);
+    max-height: 200px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    padding-right: 4px;
+}
+
+.risk-analysis::-webkit-scrollbar {
+    width: 6px;
+}
+
+.risk-analysis::-webkit-scrollbar-track {
+    background: rgba(255, 255, 255, 0.5);
+    border-radius: 3px;
+}
+
+.risk-analysis::-webkit-scrollbar-thumb {
+    background: linear-gradient(180deg, #cbd5e1 0%, #94a3b8 100%);
+    border-radius: 3px;
+}
+
+.risk-analysis::-webkit-scrollbar-thumb:hover {
+    background: linear-gradient(180deg, #94a3b8 0%, #64748b 100%);
+}
+
+.risk-text {
+    font-size: 14px;
+    line-height: 1.8;
+    color: #475569;
+    margin: 0;
+}
+
+.risk-tip {
+    margin-top: 12px;
+    padding: 12px;
+    background: linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(248, 250, 252, 0.9) 100%);
+    border-left: 3px solid #f59e0b;
+    border-radius: 8px;
+    font-size: 13px;
+    color: #92400e;
+    display: flex;
+    align-items: flex-start;
+    gap: 8px;
+}
+
+.risk-tip i {
+    font-size: 14px;
+    margin-top: 2px;
+    color: #f59e0b;
+}
+
+/* 关注点卡片 */
+.card-focus {
+    background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+    border: 2px solid #bae6fd;
+    box-shadow: 0 4px 16px rgba(186, 230, 253, 0.3);
+}
+
+.card-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    border-bottom: 1px solid #eef2ff;
+    padding-bottom: 12px;
+    margin-bottom: 16px;
+    flex-wrap: wrap;
+}
+
+.card-header h3 {
+    font-size: 17px;
+    font-weight: 700;
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    color: #0f172a;
+    letter-spacing: -0.02em;
+}
+
+.records-table-wrapper {
+    overflow-x: auto;
+}
+
+/* 分页容器样式 */
+.pagination-container {
+    padding: 16px 0 12px;
+    display: flex;
+    justify-content: center;
+    background: white;
+    border-top: 1px solid #f1f5f9;
+    margin-top: 12px;
+}
+
+/* Element UI 分页样式覆盖 */
+
+.pagination-container .el-pagination li {
+    min-width: 32px;
+    height: 32px;
+    line-height: 32px;
+    border-radius: 6px;
+    transition: all 0.3s ease;
+}
+
+.pagination-container .el-pagination li:hover {
+    background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%);
+    border-color: #667eea;
+}
+
+.records-table {
+    width: 100%;
+    border-collapse: collapse;
+    font-size: 14px;
+}
+
+.records-table thead {
+    background: transparent;
+    color: #475569;
+}
+
+.records-table th {
+    padding: 12px 16px;
+    text-align: center !important;
+    font-weight: 600;
+    font-size: 13px;
+    border-bottom: 2px solid #e2e8f0;
+    color: #64748b;
+}
+
+.records-table th i {
+    margin-right: 6px;
+    opacity: 0.8;
+}
+
+.records-table tbody tr {
+    border-bottom: 1px solid #f1f5f9;
+    transition: all 0.2s ease;
+}
+
+.records-table tbody tr:hover {
+    background-color: #f8fafc;
+    transform: none;
+    box-shadow: none;
+}
+
+.records-table td {
+    padding: 12px 16px;
+    vertical-align: middle;
+}
+
+.record-cell {
+    font-size: 14px;
+    color: #334155;
+    text-align: center !important;
+}
+
+/* 风险等级标签 */
+.risk-level-tag {
+    display: inline-block;
+    padding: 4px 12px;
+    border-radius: 6px;
+    font-size: 12px;
+    font-weight: 500;
+    border: 1px solid;
+}
+
+/* 客户意向度 */
+.intention-degree {
+    display: inline-block;
+    padding: 4px 12px;
+    border-radius: 6px;
+    font-size: 13px;
+    font-weight: 600;
+    color: #6366f1;
+    background: rgba(99, 102, 241, 0.08);
+    border: 1px solid rgba(99, 102, 241, 0.2);
+}
+
+.btn-view-chat {
+    background: transparent;
+    color: #667eea;
+    border: 1px solid #667eea;
+    padding: 6px 12px;
+    border-radius: 6px;
+    font-size: 13px;
+    cursor: pointer;
+    display: inline-flex;
+    align-items: center;
+    gap: 4px;
+    transition: all 0.2s ease;
+}
+
+.btn-view-chat:hover {
+    background: #f0f4ff;
+}
+
+.empty-tip {
+    color: #94a3b8;
+    font-size: 13px;
+    text-align: center;
+    padding: 40px 20px;
+    background: transparent;
+    border: none;
+}
+
+/* 沟通摘要样式 */
+.summary-text.compact {
+    max-height: 120px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    line-height: 1.6;
+    font-size: 14px;
+    color: #475569;
+    padding-right: 4px;
+}
+
+.summary-text.compact::-webkit-scrollbar {
+    width: 6px;
+}
+
+.summary-text.compact::-webkit-scrollbar-track {
+    background: #f1f5f9;
+    border-radius: 3px;
+}
+
+.summary-text.compact::-webkit-scrollbar-thumb {
+    background: linear-gradient(180deg, #cbd5e1 0%, #94a3b8 100%);
+    border-radius: 3px;
+}
+
+.summary-text.compact::-webkit-scrollbar-thumb:hover {
+    background: linear-gradient(180deg, #94a3b8 0%, #64748b 100%);
+}
+
+/* AI 标签美化样式 */
+.tags-container {
+    padding: 0;
+    max-height: 300px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    display: flex;
+    flex-direction: column;
+}
+
+.tags-container::-webkit-scrollbar {
+    width: 6px;
+}
+
+.tags-container::-webkit-scrollbar-track {
+    background: #f1f5f9;
+    border-radius: 3px;
+}
+
+.tags-container::-webkit-scrollbar-thumb {
+    background: linear-gradient(180deg, #cbd5e1 0%, #94a3b8 100%);
+    border-radius: 3px;
+}
+
+.tags-container::-webkit-scrollbar-thumb:hover {
+    background: linear-gradient(180deg, #94a3b8 0%, #64748b 100%);
+}
+
+.tags-grid {
+    display: grid;
+    grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+    gap: 6px;
+    margin-bottom: 12px;
+}
+
+.tag-chip {
+    display: grid;
+    grid-template-columns: 35% 65%;
+    align-items: center;
+    gap: 8px;
+    padding: 6px 12px;
+    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+    border: 1px solid #e2e8f0;
+    border-radius: 8px;
+    font-size: 13px;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    cursor: default;
+    position: relative;
+    overflow: hidden;
+    word-break: break-word;
+}
+
+.tag-chip::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    width: 3px;
+    height: 100%;
+    background: linear-gradient(180deg, #94a3b8 0%, #64748b 100%);
+    transition: width 0.3s ease;
+}
+
+.tag-chip:hover {
+    background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
+    border-color: #cbd5e1;
+    transform: translateX(4px);
+    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
+}
+
+.tag-chip:hover::before {
+    width: 4px;
+    background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
+}
+
+.tag-highlight {
+    background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
+    border-color: #bfdbfe;
+}
+
+.tag-highlight::before {
+    background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
+}
+
+.tag-icon {
+    color: #64748b;
+    font-size: 12px;
+    transition: all 0.3s ease;
+}
+
+.tag-chip:hover .tag-icon {
+    color: #3b82f6;
+    transform: scale(1.05);
+}
+
+.tag-name {
+    font-weight: 600;
+    color: #475569;
+    white-space: normal;
+    word-break: break-word;
+    line-height: 1.3;
+}
+
+.tag-separator {
+    color: #94a3b8;
+    font-weight: 300;
+}
+
+.tag-value {
+    color: #1e293b;
+    font-weight: 500;
+    word-break: break-word;
+    flex: 1;
+    min-width: 0;
+}
+
+.empty-tags {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 40px 20px;
+    color: #94a3b8;
+    font-size: 14px;
+    background: linear-gradient(135deg, rgba(248, 250, 252, 0.5) 0%, rgba(241, 245, 249, 0.5) 100%);
+    border-radius: 12px;
+    border: 2px dashed #e2e8f0;
+}
+
+.empty-tags i {
+    font-size: 32px;
+    margin-bottom: 12px;
+    opacity: 0.5;
+}
+
+.tags-actions {
+    display: flex;
+    justify-content: center;
+    padding: 16px 0;
+    border-top: 1px solid #f1f5f9;
+    margin-top: auto;
+    background: white;
+    position: sticky;
+    bottom: 0;
+    z-index: 10;
+}
+
+.btn-expand-tags,
+.btn-collapse-tags {
+    background: transparent;
+    color: #667eea;
+    border: 1px solid #667eea;
+    padding: 8px 16px;
+    border-radius: 6px;
+    font-size: 13px;
+    cursor: pointer;
+    display: inline-flex;
+    align-items: center;
+    gap: 6px;
+    transition: all 0.2s ease;
+}
+
+.btn-expand-tags:hover,
+.btn-collapse-tags:hover {
+    background: rgba(102, 126, 234, 0.05);
+    border-color: #5a67d8;
+    color: #5a67d8;
+}
+
+.btn-expand-tags:active,
+.btn-collapse-tags:active {
+    transform: scale(0.98);
+}
+
+/* 微信风格聊天弹窗 */
+.chat-dialog-overlay {
+    position: fixed;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(0, 0, 0, 0.5);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    z-index: 9999;
+    backdrop-filter: blur(4px);
+}
+
+.chat-dialog {
+    background: white;
+    border-radius: 16px;
+    width: 90%;
+    max-width: 800px;
+    height: 600px;
+    display: flex;
+    flex-direction: column;
+    box-shadow: 0 8px 32px rgba(0, 0, 0, 0.3);
+    animation: slideIn 0.3s ease-out;
+    position: relative;
+}
+
+@keyframes slideIn {
+    from {
+        opacity: 0;
+        transform: translateY(-20px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+.chat-dialog-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    padding: 16px 20px;
+    border-bottom: 1px solid #eef2ff;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    border-radius: 16px 16px 0 0;
+    color: white;
+}
+
+.chat-title {
+    display: flex;
+    align-items: center;
+    gap: 10px;
+    font-size: 16px;
+    font-weight: 700;
+}
+
+.btn-close {
+    position: absolute;
+    top: 16px;
+    right: 16px;
+    background: white;
+    border: 2px solid #e2e8f0;
+    color: #1a202c;
+    width: 36px;
+    height: 36px;
+    border-radius: 50%;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all 0.3s ease;
+    font-size: 24px;
+    font-weight: bold;
+    line-height: 1;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+    z-index: 10;
+}
+
+.btn-close:hover {
+    background: #ef4444;
+    border-color: #ef4444;
+    color: white;
+    transform: rotate(90deg) scale(1.1);
+    box-shadow: 0 4px 16px rgba(239, 68, 68, 0.4);
+}
+
+.chat-dialog-body {
+    flex: 1;
+    overflow-y: auto;
+    padding: 20px;
+    background: #f5f7fa;
+}
+
+.chat-messages {
+    display: flex;
+    flex-direction: column;
+    gap: 16px;
+}
+
+.message-item {
+    display: flex;
+    align-items: flex-start;
+    margin-bottom: 8px;
+}
+
+.message-left {
+    justify-content: flex-start;
+}
+
+.message-right {
+    justify-content: flex-end;
+}
+
+/* 客户消息强制布局:头像在右 */
+.message-item-customer {
+    display: flex !important;
+    flex-direction: row !important;
+    justify-content: flex-end !important;
+    gap: 10px !important;
+}
+
+.message-wrapper {
+    display: flex;
+    align-items: flex-start;
+    gap: 10px;
+    max-width: 75%;
+}
+
+.message-wrapper-left {
+    flex-direction: row;
+}
+
+.message-name {
+    font-size: 12px;
+    color: #94a3b8;
+    white-space: nowrap;
+    text-align: left;
+    margin-bottom: 4px;
+    line-height: 1.2;
+}
+
+.message-name-ai {
+    color: #667eea;
+    font-weight: 500;
+}
+.message-avatar {
+    width: 32px;
+    height: 32px;
+    border-radius: 6px;
+    overflow: hidden;
+    flex-shrink: 0;
+    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.12);
+    background: #f0f0f0;
+}
+
+.message-avatar img {
+    width: 100%;
+    height: 100%;
+    object-fit: cover;
+    transition: transform 0.3s ease;
+}
+
+.message-avatar:hover img {
+    transform: scale(1.1);
+}
+
+.message-wrapper-left .message-avatar {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+}
+
+.message-wrapper-right .message-avatar {
+    background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-shrink: 0;
+}
+
+.message-content {
+    display: flex;
+    flex-direction: column;
+    max-width: calc(100% - 42px);
+}
+
+.message-wrapper-left .message-content {
+    align-items: flex-start;
+    margin-left: 4px;
+}
+
+.message-wrapper-right .message-content {
+    align-items: flex-end !important;
+    margin-right: 4px;
+}
+
+/* 客户聊天内容容器 */
+.message-content-right {
+    flex: 0 0 auto !important;
+    max-width: calc(100% - 42px) !important;
+    display: flex;
+    align-items: flex-start !important;
+}
+
+.message-bubble {
+    background: white;
+    padding: 9px 13px;
+    border-radius: 6px;
+    font-size: 14px;
+    line-height: 1.5;
+    color: #0f172a;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+    word-break: break-word;
+    position: relative;
+    max-width: 500px;
+}
+
+.message-bubble::before {
+    content: '';
+    position: absolute;
+    left: -6px;
+    top: 12px;
+    width: 0;
+    height: 0;
+    border-top: 5px solid transparent;
+    border-bottom: 5px solid transparent;
+    border-right: 6px solid white;
+}
+
+.message-bubble-right {
+    background: #d9fdd3;
+    color: #0f172a;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.08);
+    display: inline-block;
+    position: relative;
+}
+
+.message-bubble-right::before {
+    content: '';
+    position: absolute;
+    right: -6px;
+    left: auto;
+    top: 16px !important;
+    transform: none !important;
+    width: 0;
+    height: 0;
+    border-top: 5px solid transparent;
+    border-bottom: 5px solid transparent;
+    border-left: 6px solid #d9fdd3;
+    border-right: none;
+}
+.empty-chat-tip {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    padding: 60px 20px;
+    color: #94a3b8;
+    font-size: 14px;
+}
+
+.empty-chat-tip i {
+    font-size: 48px;
+    margin-bottom: 12px;
+    opacity: 0.5;
+}
+
+/* 客户画像样式 */
+.profile-grid {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+    max-height: 400px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    padding-right: 4px;
+    padding-top: 0;
+}
+
+.profile-grid::-webkit-scrollbar {
+    width: 6px;
+}
+
+.profile-grid::-webkit-scrollbar-track {
+    background: #f1f5f9;
+    border-radius: 3px;
+}
+
+.profile-grid::-webkit-scrollbar-thumb {
+    background: linear-gradient(180deg, #cbd5e1 0%, #94a3b8 100%);
+    border-radius: 3px;
+}
+
+.profile-grid::-webkit-scrollbar-thumb:hover {
+    background: linear-gradient(180deg, #94a3b8 0%, #64748b 100%);
+}
+
+.profile-item {
+    display: grid;
+    grid-template-columns: 35% 65%;
+    align-items: flex-start;
+    gap: 8px;
+    padding: 6px 12px;
+    border-radius: 8px;
+    transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+    border: 1px solid #e2e8f0;
+    word-break: break-word;
+}
+
+.profile-item:hover {
+    background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
+    transform: translateX(4px);
+    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.06);
+    border-color: #cbd5e1;
+}
+
+.profile-item-main {
+    background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
+    border-color: #bfdbfe;
+    position: sticky;
+    top: 0;
+    z-index: 10;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
+}
+
+.profile-item-main:hover {
+    background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
+}
+
+.profile-item-full {
+    grid-template-columns: 35% 65%;
+}
+
+.profile-item .label {
+    font-size: 13px;
+    color: #64748b;
+    font-weight: 600;
+    white-space: normal;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    line-height: 1.4;
+    min-width: 0;
+}
+
+.profile-item .label i {
+    color: #94a3b8;
+    font-size: 12px;
+    width: 14px;
+    text-align: center;
+    flex-shrink: 0;
+}
+
+.profile-item .value {
+    font-size: 14px;
+    color: #0f172a;
+    font-weight: 500;
+    word-break: break-word;
+    line-height: 1.4;
+    min-width: 0;
+}
+
+.profile-item .value.highlight {
+    color: #0369a1;
+    font-size: 14px;
+    font-weight: 600;
+}
+
+.profile-item .value.long-text {
+    color: #334155;
+    font-weight: 400;
+}
+
+.update-time-corner {
+    position: absolute;
+    bottom: 12px;
+    right: 16px;
+    font-size: 12px;
+    color: #94a3b8;
+    font-style: italic;
+}
+
+/* 客户关注点 & 意向度样式 */
+.focus-points {
+    padding: 0;
+    margin-bottom: 20px;
+}
+
+.focus-title {
+    font-size: 14px;
+    color: #64748b;
+    font-weight: 600;
+    margin-bottom: 12px;
+    display: flex;
+    align-items: center;
+    gap: 8px;
+}
+
+.focus-title i {
+    color: #3b82f6;
+    font-size: 16px;
+}
+
+.focus-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+    max-height: 180px;
+    overflow-y: auto;
+    overflow-x: hidden;
+    padding-right: 4px;
+}
+
+.focus-list::-webkit-scrollbar {
+    width: 6px;
+}
+
+.focus-list::-webkit-scrollbar-track {
+    background: #f1f5f9;
+    border-radius: 3px;
+}
+
+.focus-list::-webkit-scrollbar-thumb {
+    background: linear-gradient(180deg, #cbd5e1 0%, #94a3b8 100%);
+    border-radius: 3px;
+}
+
+.focus-list::-webkit-scrollbar-thumb:hover {
+    background: linear-gradient(180deg, #94a3b8 0%, #64748b 100%);
+}
+
+.focus-item {
+    display: flex;
+    align-items: flex-start;
+    gap: 8px;
+    padding: 8px 12px;
+    margin-bottom: 6px;
+    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+    border-radius: 8px;
+    border: 1px solid #e2e8f0;
+    transition: all 0.3s ease;
+    font-size: 14px;
+    color: #334155;
+    line-height: 1.5;
+}
+
+.focus-item:hover {
+    background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
+    border-color: #bfdbfe;
+    transform: translateX(4px);
+    box-shadow: 0 2px 6px rgba(59, 130, 246, 0.1);
+}
+
+.focus-item i {
+    color: #3b82f6;
+    font-size: 12px;
+    margin-top: 2px;
+    flex-shrink: 0;
+}
+
+/* 意向度样式 */
+.intention-section {
+    margin-top: 20px;
+    padding-top: 16px;
+    border-top: 1px solid #e2e8f0;
+}
+
+.intention-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 12px;
+}
+
+.no-data-tip {
+    font-size: 13px;
+    color: #94a3b8;
+    font-style: italic;
+    padding: 4px 12px;
+    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+    border-radius: 6px;
+    border: 1px dashed #cbd5e1;
+}
+
+.intention-label {
+    font-size: 14px;
+    font-weight: 600;
+    color: #64748b;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+}
+
+.intention-label::before {
+    content: '';
+    width: 4px;
+    height: 16px;
+    background: linear-gradient(180deg, #3b82f6 0%, #2563eb 100%);
+    border-radius: 2px;
+}
+
+.progress-bar-wrapper {
+    position: relative;
+}
+
+.progress-bar-modern {
+    width: 100%;
+    height: 24px;
+    background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
+    border-radius: 12px;
+    overflow: hidden;
+    box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06);
+    border: 1px solid #e2e8f0;
+    position: relative;
+}
+
+.progress-bar-modern::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: repeating-linear-gradient(
+        45deg,
+        transparent,
+        transparent 10px,
+        rgba(255, 255, 255, 0.03) 10px,
+        rgba(255, 255, 255, 0.03) 20px
+    );
+    z-index: 1;
+    pointer-events: none;
+}
+
+.progress-fill-modern {
+    height: 100%;
+    border-radius: 12px;
+    display: flex;
+    align-items: center;
+    justify-content: flex-end;
+    padding-right: 12px;
+    transition: width 1s cubic-bezier(0.4, 0, 0.2, 1), background 0.3s ease;
+    position: relative;
+    overflow: hidden;
+    min-width: 60px;
+}
+
+.progress-fill-modern::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: linear-gradient(
+        90deg,
+        rgba(255, 255, 255, 0.2) 0%,
+        rgba(255, 255, 255, 0) 50%,
+        rgba(255, 255, 255, 0.2) 100%
+    );
+    animation: shimmer 2s infinite;
+    z-index: 1;
+}
+
+@keyframes shimmer {
+    0% {
+        transform: translateX(-100%);
+    }
+    100% {
+        transform: translateX(100%);
+    }
+}
+
+.progress-text {
+    font-size: 13px;
+    font-weight: 700;
+    color: white;
+    text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
+    z-index: 2;
+    font-variant-numeric: tabular-nums;
+}
+
+.card-header h3 i {
+    font-size: 20px;
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    -webkit-background-clip: text;
+    -webkit-text-fill-color: transparent;
+    background-clip: text;
+}
+
+.card-highlight .card-header h3 i {
+    color: white;
+    background: none;
+    -webkit-text-fill-color: white;
+}
+
+
+</style>

+ 55 - 0
src/views/crm/customer/index.vue

@@ -129,6 +129,25 @@
               <el-option key="0"  label="否" value="0" />
             </el-select>
           </el-form-item>
+          <el-form-item label="流失风险" prop="attritionLevel">
+                <el-select style="width:220px" v-model="queryParams.attritionLevel" placeholder="请选择流失风险等级" clearable size="small">
+                    <el-option label="全部" value="" />
+                    <el-option label="未知" :value="0" />
+                    <el-option label="无风险" :value="1" />
+                    <el-option label="低风险" :value="2" />
+                    <el-option label="中风险" :value="3" />
+                    <el-option label="高风险" :value="4" />
+                </el-select>
+          </el-form-item>
+          <el-form-item label="意向度">
+              <div class="time-range">
+                    <el-input-number v-model="queryParams.intentionDegreeGt" placeholder="最小值" size="small"
+                                     style="width: 130px" :min="0" :max="100"/>
+                    <span class="range-separator">-</span>
+                    <el-input-number v-model="queryParams.intentionDegreelt" placeholder="最大值" size="small"
+                                     style="width: 130px" :min="0" :max="100"/>
+                </div>
+          </el-form-item>
           <el-form-item>
             <el-button type="cyan" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
             <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
@@ -235,6 +254,21 @@
             </template>
           </el-table-column>
           <el-table-column label="标签" align="center" prop="tags" />
+          <el-table-column label="流失风险" align="center" prop="attritionLevel">
+            <template slot-scope="scope">
+              <el-tag v-if="scope.row.attritionLevel == null" type="info">未分析</el-tag>
+              <el-tag v-if="scope.row.attritionLevel === 0" type="info">未知</el-tag>
+              <el-tag v-else-if="scope.row.attritionLevel === 1" type="success">无风险</el-tag>
+              <el-tag v-else-if="scope.row.attritionLevel === 2" type="info">低风险</el-tag>
+              <el-tag v-else-if="scope.row.attritionLevel === 3" type="warning">中风险</el-tag>
+              <el-tag v-else-if="scope.row.attritionLevel === 4" type="danger">高风险</el-tag>
+            </template>
+          </el-table-column>
+          <el-table-column label="意向度" align="center" prop="intentionDegree">
+            <template slot-scope="scope">
+              {{ scope.row.intentionDegree != null ? scope.row.intentionDegree + '%' : '' }}
+            </template>
+          </el-table-column>
           <el-table-column label="备注" align="center" prop="remark" />
           <el-table-column label="进线客户详情" align="center" :show-overflow-tooltip="true" prop="registerDesc" />
           <el-table-column label="领取时间" align="center" prop="receiveTime" />
@@ -262,6 +296,13 @@
                 @click="handleShow(scope.row)"
                 v-hasPermi="['crm:customer:query']"
               >查看</el-button>
+              <el-button
+                v-if="scope.row.attritionLevel !== null && scope.row.attritionLevel !== undefined"
+                size="mini"
+                type="text"
+                @click="toDetailPage(scope.row)"
+                v-hasPermi="['crm:analyze:list']"
+              >AI 分析</el-button>
               <el-button
                 v-if="scope.row.isReceive==1"
                 size="mini"
@@ -466,6 +507,9 @@ export default {
         tags: null,
         tagList:[],
         visitStatus:null,
+        attritionLevel: null,
+        intentionDegreeGt: null,
+        intentionDegreelt: null,
       },
       // 表单参数
       form: {
@@ -531,6 +575,14 @@ export default {
     this.getList();
   },
   methods: {
+    toDetailPage(row) {
+      this.$router.push({
+        path: '/crm/customer/detail/' + row.customerId,
+        query: {
+          customerData: JSON.stringify(row)
+        }
+      });
+    },
     handleShow(row){
       this.show.open=true;
       var that=this;
@@ -757,6 +809,9 @@ export default {
       this.tagIds =  [];
       this.ctsTypeArr =  [];
       this.dateRange = [];
+      this.queryParams.attritionLevel = null;
+      this.queryParams.intentionDegreeGt = null;
+      this.queryParams.intentionDegreelt = null;
       this.handleQuery();
     },
     // 多选框选中数据

+ 1545 - 0
src/views/crm/customerAiChat/index.vue

@@ -0,0 +1,1545 @@
+<template>
+    <div class="ai-chat-container">
+        <!-- 聊天主界面 -->
+        <div class="chat-main">
+            <!-- 左侧边栏 - 会话列表 -->
+            <div class="sidebar">
+                <div class="sidebar-header">
+                    <button class="new-chat-btn" @click="createNewChatDirect">
+                        <svg width="16" height="16" viewBox="0 0 16 16" fill="none">
+                            <path d="M8 3.5V12.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+                            <path d="M3.5 8H12.5" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/>
+                        </svg>
+                        <span>新建对话</span>
+                    </button>
+                </div>
+
+                <div class="chat-list">
+                    <div
+                        v-for="(chat, index) in chatList"
+                        :key="chat.id"
+                        :class="['chat-item', { active: currentChatId === chat.id }]"
+                        @click="selectChat(chat)"
+                    >
+                        <div class="chat-item-content">
+                            <el-input
+                                v-if="editingChatIndex === index"
+                                v-model="chat.editingTitle"
+                                size="mini"
+                                @blur="confirmEditTitle(index)"
+                                @keydown.native.enter.prevent="confirmEditTitle(index)"
+                                @click.stop
+                                ref="editInput"
+                                class="chat-title-input"
+                            >
+                            </el-input>
+                            <div 
+                                v-else 
+                                class="chat-item-title"
+                                @dblclick.stop="startEditTitle(index, chat)"
+                                title="双击编辑标题"
+                            >{{ chat.title }}</div>
+                            <div class="chat-item-time">{{ formatTime(chat.time) }}</div>
+                        </div>
+                        <div class="chat-item-actions">
+                            <i
+                                class="el-icon-edit chat-item-edit"
+                                @click.stop="startEditTitle(index, chat)"
+                                title="修改标题"
+                            ></i>
+                            <i
+                                v-if="index !== 0"
+                                class="el-icon-top chat-item-pin"
+                                @click.stop="pinChat(index)"
+                                title="置顶会话"
+                            ></i>
+                            <i
+                                v-if="chatList.length > 1"
+                                class="el-icon-close chat-item-close"
+                                @click.stop="closeChat(index)"
+                                title="关闭会话"
+                            ></i>
+                        </div>
+                    </div>
+                </div>
+            </div>
+
+            <!-- 中间聊天区域 -->
+            <div class="chat-content">
+                <!-- 左侧客户画像面板 -->
+                <div v-if="linkedCustomer" class="customer-portrait-panel">
+                    <!-- 客户姓名固定顶部 -->
+                    <div class="profile-item profile-item-main profile-item-sticky">
+                        <span class="label"><i class="fas fa-user-circle"></i> 客户姓名:</span>
+                        <span class="value highlight">{{ linkedCustomer.name }}</span>
+                    </div>
+                    <!-- 其他画像数据可滚动 -->
+                    <div class="profile-scroll-content">
+                        <template v-for="(value, key) in customerPortraitData">
+                            <div
+                                v-if="key !== '需求'"
+                                :key="key"
+                                class="profile-item"
+                            >
+                                    <span class="label">
+                                        <i :class="getIconClass(key)"></i> {{ key }}:
+                                    </span>
+                                <span class="value">{{ value }}</span>
+                            </div>
+                        </template>
+                        <!-- 需求单独显示,占满整行 -->
+                        <div
+                            v-if="customerPortraitData['需求']"
+                            key="需求"
+                            class="profile-item profile-item-full"
+                        >
+                                <span class="label">
+                                    <i class="fas fa-bullseye"></i> 需求:
+                                </span>
+                            <span class="value long-text">{{ customerPortraitData['需求'] }}</span>
+                        </div>
+                        <!-- 当画像数据为空时显示提示 -->
+                        <div v-if="Object.keys(customerPortraitData).length === 0"
+                             class="profile-item profile-item-full">
+                            <span class="value empty-tip">暂无画像信息</span>
+                        </div>
+                    </div>
+                </div>
+                <!-- 聊天标题 -->
+                <div v-if="currentChatTitle && messages.length > 0" class="chat-title-header">
+                    <h2 class="chat-title-text">{{ currentChatTitle }}</h2>
+                </div>
+
+                <!-- 消息列表 -->
+                <div class="messages-container" ref="messagesContainer">
+                    <!-- 欢迎界面 -->
+                    <div v-if="messages.length === 0" class="welcome-container">
+                        <div class="welcome-avatar">
+                            <img src="/static/images/ai-avatar.svg" alt="AI" class="welcome-avatar-img"/>
+                        </div>
+                        <div class="welcome-text">今天有什么可以帮到你?</div>
+                    </div>
+
+                    <div v-else class="messages-list">
+                        <div
+                            v-for="(message, index) in messages"
+                            :key="index"
+                            :class="['message-row', message.type === 'user' ? 'message-user' : 'message-ai']"
+                        >
+                            <!-- AI 消息 -->
+                            <div v-if="message.type === 'ai'" class="message-wrapper">
+                                <div class="avatar avatar-ai">
+                                    <img src="/static/images/ai-avatar.svg" alt="AI" @error="handleAvatarError"/>
+                                </div>
+                                <div class="message-body">
+                                    <div class="message-name">AI 助手</div>
+                                    <div class="message-bubble message-bubble-ai">
+                                        <div class="message-content" v-html="formatMessage(message.content)"></div>
+                                    </div>
+                                </div>
+                            </div>
+
+                            <!-- 用户消息 -->
+                            <div v-else class="message-row message-user">
+                                <div class="message-bubble message-bubble-user">
+                                    <div class="message-content" v-html="formatUserMessage(message.content)"></div>
+                                </div>
+                            </div>
+                        </div>
+
+                        <!-- 加载中提示 -->
+                        <div v-if="isLoading" class="message-row message-ai">
+                            <div class="message-wrapper">
+                                <div class="avatar avatar-ai">
+                                    <img src="/static/images/ai-avatar.svg" alt="AI" @error="handleAvatarError"/>
+                                </div>
+                                <div class="message-body">
+                                    <div class="message-name">AI 助手</div>
+                                    <div class="message-bubble message-bubble-ai thinking-bubble">
+                                        <div class="thinking-indicator">
+                                            <span></span>
+                                            <span></span>
+                                            <span></span>
+                                        </div>
+                                    </div>
+                                </div>
+                            </div>
+                        </div>
+                    </div>
+                </div>
+
+                <!-- 输入区域 -->
+                <div class="input-area">
+                    <!-- 关联客户按钮 -->
+                    <div class="customer-link-bar">
+                        <el-button
+                            size="small"
+                            type="primary"
+                            plain
+                            @click="linkCustomer"
+                            class="customer-link-btn"
+                        >
+                            <i class="el-icon-user"></i>
+                            关联客户
+                        </el-button>
+                        <span v-if="linkedCustomer" class="linked-customer-info">
+              <i class="el-icon-check"></i>
+              已关联:{{ linkedCustomer.name }}
+              <el-tag size="mini" style="margin-left: 8px;">
+                意向度:{{ linkedCustomer.intentionDegree }}
+              </el-tag>
+              <el-tag
+                  size="mini"
+                  :type="getAttritionLevelTagType(linkedCustomer.attritionLevel)"
+                  style="margin-left: 8px;"
+              >
+                {{ getAttritionLevelText(linkedCustomer.attritionLevel) }}
+              </el-tag>
+              <i class="el-icon-close unlink-icon" @click="unlinkCustomer" title="取消关联"></i>
+            </span>
+                    </div>
+                    <div class="input-container">
+                        <div class="input-box">
+                            <el-input
+                                v-model="inputMessage"
+                                type="textarea"
+                                :rows="3"
+                                placeholder="输入消息... (Shift+Enter 换行,Enter 发送)"
+                                @keydown.native.enter.exact.prevent="sendMessage"
+                                resize="none"
+                                class="message-input"
+                            >
+                            </el-input>
+                            <div class="send-btn-wrapper" @click="sendMessage" title="点击发送">
+                                <i class="el-icon-top"></i>
+                            </div>
+                        </div>
+                    </div>
+                    <div class="input-tip">
+                        AI 生成内容仅供参考,请谨慎判断
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        <!-- 客户选择对话框 -->
+        <el-dialog
+            v-if="customerDialogVisible"
+            :visible.sync="customerDialogVisible"
+            title="选择客户"
+            width="900px"
+            :close-on-click-modal="false"
+            @close="cancelSelectCustomer"
+        >
+            <div class="customer-dialog-content">
+                <!-- 筛选区域 -->
+                <div class="customer-filter">
+                    <el-form :inline="true" size="small">
+                        <el-form-item label="客户名称">
+                            <el-input
+                                v-model="customerFilter.customerName"
+                                placeholder="请输入客户名称"
+                                clearable
+                                @keyup.enter.native="handleCustomerFilter"
+                            />
+                        </el-form-item>
+                        <el-form-item label="流失风险等级">
+                            <el-select
+                                v-model="customerFilter.attritionLevel"
+                                placeholder="请选择风险等级"
+                                clearable
+                            >
+                                <el-option label="未知" :value="0" />
+                                <el-option label="无风险" :value="1" />
+                                <el-option label="低风险" :value="2" />
+                                <el-option label="中风险" :value="3" />
+                                <el-option label="高风险" :value="4" />
+                            </el-select>
+                        </el-form-item>
+                        <el-form-item>
+                            <el-button type="primary" @click="handleCustomerFilter">
+                                <i class="el-icon-search"></i> 搜索
+                            </el-button>
+                            <el-button @click="resetCustomerFilter">
+                                <i class="el-icon-refresh"></i> 重置
+                            </el-button>
+                        </el-form-item>
+                    </el-form>
+                </div>
+
+                <el-table
+                    :data="customerList"
+                    v-loading="customerLoading"
+                    highlight-current-row
+                    @current-change="handleCurrentChange"
+                    border
+                >
+                    <el-table-column type="radio" width="50" align="center"></el-table-column>
+                    <el-table-column prop="customerId" label="客户 ID" width="120" align="center"></el-table-column>
+                    <el-table-column prop="customerName" label="客户名称" min-width="200"
+                                     align="center"></el-table-column>
+                    <el-table-column label="流失风险等级" width="150" align="center">
+                        <template slot-scope="scope">
+                            <el-tag :type="getAttritionLevelTagType(scope.row.attritionLevel)" size="small">
+                                {{ getAttritionLevelText(scope.row.attritionLevel) }}
+                            </el-tag>
+                        </template>
+                    </el-table-column>
+                    <el-table-column prop="intentionDegree" label="意向度" width="150" align="center">
+                        <template slot-scope="scope">
+                            <el-tag type="success" size="small" effect="plain">
+                                {{ scope.row.intentionDegree }}
+                            </el-tag>
+                        </template>
+                    </el-table-column>
+                    <el-table-column label="操作" width="100" align="center" fixed="right">
+                        <template slot-scope="scope">
+                            <el-button
+                                type="primary"
+                                size="small"
+                                @click="selectCustomerByClick(scope.row)"
+                            >
+                                选择
+                            </el-button>
+                        </template>
+                    </el-table-column>
+                </el-table>
+
+                <div class="customer-pagination">
+                    <el-pagination
+                        @current-change="handleCustomerPageChange"
+                        :current-page="customerPagination.pageNum"
+                        :page-size="customerPagination.pageSize"
+                        :total="customerPagination.total"
+                        layout="total, prev, pager, next, jumper"
+                        background
+                        :page-sizes="[5, 10, 20, 50]"
+                    >
+                    </el-pagination>
+                </div>
+            </div>
+
+            <span slot="footer" class="dialog-footer">
+        <el-button @click="cancelSelectCustomer">关 闭</el-button>
+      </span>
+        </el-dialog>
+
+
+    </div>
+</template>
+
+<script>
+import {listAllAnalyze} from "../../../api/crm/customerAnalyze";
+
+export default {
+    name: 'CustomerAiChat',
+    data() {
+        return {
+            // 聊天列表
+            chatList: [],
+            currentChatId: null,
+            // 当前聊天消息
+            messages: [],
+            inputMessage: '',
+            isLoading: false,
+            currentChatTitle: '', // 当前聊天标题
+            linkedCustomer: null, // 已关联的客户
+            customerLinkMap: {}, // 每个会话关联的客户映射 {chatId: customer}
+            hasSentFirstMessage: false, // 标记是否已发送第一条消息
+            manuallyEditedTitles: {}, // 记录手动编辑过标题的会话 ID
+            // 客户选择对话框相关
+            customerDialogVisible: false,
+            customerList: [],
+            customerPagination: {
+                pageNum: 1,
+                pageSize: 5,
+                total: 0
+            },
+            customerLoading: false,
+            selectedCustomerId: null, // 当前选中的客户 ID
+            // 客户筛选条件
+            customerFilter: {
+                customerName: '',
+                attritionLevel: null
+            },
+            // 会话标题编辑相关
+            editingChatIndex: -1,
+            isEditingChat: false
+        }
+    },
+    created() {
+        // 初始化时创建第一个默认会话
+        this.createDefaultChat()
+    },
+    computed: {
+        // 客户画像数据(从最新的沟通记录中获取)
+        customerPortraitData() {
+            if (!this.linkedCustomer || !this.linkedCustomer.customerPortraitJson) {
+                return {};
+            }
+            try {
+                // 如果是字符串,解析为 JSON 对象
+                if (typeof this.linkedCustomer.customerPortraitJson === 'string') {
+                    return JSON.parse(this.linkedCustomer.customerPortraitJson);
+                }
+                // 如果已经是对象,直接返回
+                return this.linkedCustomer.customerPortraitJson;
+            } catch (error) {
+                console.error('解析客户画像 JSON 失败:', error);
+            }
+            return {};
+        }
+    },
+    methods: {
+        // 创建默认会话
+        createDefaultChat() {
+            const newTitle = '新会话 1'
+            const defaultChat = {
+                id: Date.now(),
+                title: newTitle,
+                time: Date.now(),
+                messages: []
+            }
+            this.chatList.push(defaultChat)
+            this.selectChat(defaultChat)
+            this.currentChatTitle = newTitle
+            this.hasSentFirstMessage = false
+        },
+
+        // 直接创建新会话(按序号)
+        createNewChatDirect() {
+            const newTitle = '新会话 ' + (this.chatList.length + 1)
+            const newChat = {
+                id: Date.now(),
+                title: newTitle,
+                time: Date.now(),
+                messages: []
+            }
+            this.chatList.unshift(newChat)
+            this.selectChat(newChat)
+            // 更新显示的标题
+            this.currentChatTitle = newTitle
+
+            // 清空当前消息,显示空状态
+            this.messages = []
+            // 新建对话时重置关联客户
+            this.linkedCustomer = null
+            this.hasSentFirstMessage = false
+
+            // 滚动到顶部以显示欢迎界面
+            this.$nextTick(() => {
+                const container = this.$refs.messagesContainer
+                if (container) {
+                    container.scrollTop = 0
+                }
+            })
+        },
+
+        // 开始编辑标题
+        startEditTitle(index, chat) {
+            this.$set(chat, 'editingTitle', chat.title)
+            this.editingChatIndex = index
+            this.isEditingChat = true
+            this.$nextTick(() => {
+                if (this.$refs.editInput && this.$refs.editInput[index]) {
+                    this.$refs.editInput[index].focus()
+                    this.$refs.editInput[index].select()
+                }
+            })
+        },
+
+        // 确认编辑标题
+        confirmEditTitle(index) {
+            const chat = this.chatList[index]
+            if (!chat) return
+
+            const newTitle = (chat.editingTitle || '').trim()
+            if (!newTitle) {
+                this.$message.warning('标题不能为空')
+                chat.editingTitle = chat.title
+                return
+            }
+
+            chat.title = newTitle
+            if (chat.id === this.currentChatId) {
+                this.currentChatTitle = newTitle
+            }
+
+            // 标记该会话标题已被手动编辑
+            this.manuallyEditedTitles[chat.id] = true
+
+            this.editingChatIndex = -1
+            this.isEditingChat = false
+            delete chat.editingTitle
+        },
+
+        // 选择聊天
+        selectChat(chat) {
+            // 保存当前会话的关联客户
+            if (this.currentChatId) {
+                if (this.linkedCustomer) {
+                    this.customerLinkMap[this.currentChatId] = this.linkedCustomer
+                } else {
+                    delete this.customerLinkMap[this.currentChatId]
+                }
+            }
+
+            this.currentChatId = chat.id
+            this.messages = chat.messages || []
+            this.currentChatTitle = chat.title || ''
+
+            // 恢复目标会话的关联客户
+            this.linkedCustomer = this.customerLinkMap[chat.id] || null
+
+            this.$nextTick(() => {
+                this.scrollToBottom()
+            })
+        },
+
+        // 发送消息
+        sendMessage() {
+            if (!this.inputMessage.trim() || this.isLoading) return
+
+            // 如果没有当前会话,自动创建新对话
+            if (!this.currentChatId || this.chatList.length === 0) {
+                this.createNewChat()
+            }
+
+            const userMessage = {
+                type: 'user',
+                content: this.inputMessage.trim(),
+                time: Date.now()
+            }
+
+            this.messages.push(userMessage)
+            const userInput = this.inputMessage
+            this.inputMessage = ''
+            this.isLoading = true
+
+            // 如果是第一条消息,更新会话标题(仅当标题未被手动编辑过时)
+            if (!this.hasSentFirstMessage) {
+                const currentChat = this.chatList.find(c => c.id === this.currentChatId)
+                if (currentChat) {
+                    // 检查标题是否被手动编辑过
+                    if (!this.manuallyEditedTitles[currentChat.id]) {
+                        const newTitle = userInput.substring(0, 50) + (userInput.length > 50 ? '...' : '')
+                        currentChat.title = newTitle
+                        this.currentChatTitle = newTitle
+                    }
+                    this.hasSentFirstMessage = true
+                }
+            }
+
+            this.$nextTick(() => {
+                this.scrollToBottom()
+            })
+
+            // 模拟 AI 回复(实际项目中替换为 API 调用)
+            setTimeout(() => {
+                this.getAIResponse(userInput)
+            }, 1000)
+        },
+
+        // 获取 AI 回复
+        getAIResponse(userInput) {
+            // TODO: 这里调用实际的 AI 接口
+            const aiMessage = {
+                type: 'ai',
+                content: '您好!我已经收到您的消息:"' + userInput + '"。\n\n这是一个演示回复,实际项目中请调用后端 AI 接口获取真实响应。',
+                time: Date.now()
+            }
+
+            this.messages.push(aiMessage)
+            this.isLoading = false
+
+            // 更新当前聊天的消息列表
+            const currentChat = this.chatList.find(c => c.id === this.currentChatId)
+            if (currentChat) {
+                currentChat.messages = this.messages
+                // 将当前对话移动到最前面
+                const index = this.chatList.indexOf(currentChat)
+                if (index > 0) {
+                    this.chatList.splice(index, 1)
+                    this.chatList.unshift(currentChat)
+                }
+            }
+
+            this.$nextTick(() => {
+                this.scrollToBottom()
+            })
+        },
+
+        // 滚动到底部
+        scrollToBottom() {
+            const container = this.$refs.messagesContainer
+            if (container) {
+                this.$nextTick(() => {
+                    container.scrollTop = container.scrollHeight
+                })
+            }
+        },
+
+        // 格式化时间
+        formatTime(timestamp) {
+            const date = new Date(timestamp)
+            const now = new Date()
+            const diff = now.getTime() - date.getTime()
+
+            // 今天
+            if (diff < 86400000 && date.getDate() === now.getDate()) {
+                const hours = date.getHours().toString().padStart(2, '0')
+                const minutes = date.getMinutes().toString().padStart(2, '0')
+                return `今天 ${hours}:${minutes}`
+            }
+
+            // 昨天
+            const yesterday = new Date(now)
+            yesterday.setDate(yesterday.getDate() - 1)
+            if (date.getDate() === yesterday.getDate() &&
+                date.getMonth() === yesterday.getMonth() &&
+                date.getFullYear() === yesterday.getFullYear()) {
+                return '昨天'
+            }
+
+            // 更早
+            const month = (date.getMonth() + 1).toString().padStart(2, '0')
+            const day = date.getDate().toString().padStart(2, '0')
+            return `${month}-${day}`
+        },
+
+        // 格式化消息内容(支持简单的 markdown)
+        formatMessage(content) {
+            if (!content) return ''
+
+            // 换行转<br>
+            let formatted = content.replace(/\n/g, '<br>')
+
+            // **bold** 转粗体
+            formatted = formatted.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
+
+            // *italic* 转斜体
+            formatted = formatted.replace(/\*(.+?)\*/g, '<em>$1</em>')
+
+            return formatted
+        },
+
+        // 格式化用户消息(支持换行)
+        formatUserMessage(content) {
+            if (!content) return ''
+
+            // 换行转<br>
+            return content.replace(/\n/g, '<br>')
+        },
+
+        // 关闭会话
+        closeChat(index) {
+            const chat = this.chatList[index]
+
+            // 如果关闭的是当前选中的会话
+            if (this.currentChatId === chat.id) {
+                // 尝试切换到前一个会话,如果没有则切换到后一个
+                if (index > 0) {
+                    this.selectChat(this.chatList[index - 1])
+                } else if (this.chatList.length > 1) {
+                    this.selectChat(this.chatList[1])
+                } else {
+                    this.currentChatId = null
+                    this.messages = []
+                    this.linkedCustomer = null
+                    this.customerLinkMap = {}
+                }
+            }
+
+            // 删除会话的关联客户映射
+            delete this.customerLinkMap[chat.id]
+
+            // 删除会话
+            this.chatList.splice(index, 1)
+        },
+
+        // 头像加载失败处理
+        handleAvatarError(event) {
+            event.target.style.display = 'none'
+            event.target.parentElement.innerHTML = '<i class="el-icon-user" style="font-size: 20px; color: white;"></i>'
+        },
+
+        // 关联客户
+        linkCustomer() {
+            this.customerDialogVisible = true
+            // 重置到第一页
+            this.customerPagination.pageNum = 1
+            // 重置筛选条件
+            this.resetCustomerFilter()
+            this.getCustomerList()
+        },
+
+        // 获取客户列表
+        getCustomerList() {
+            this.customerLoading = true
+            const params = {
+                pageNum: this.customerPagination.pageNum,
+                pageSize: this.customerPagination.pageSize
+            }
+            // 添加筛选条件
+            if (this.customerFilter.customerName) {
+                params.customerName = this.customerFilter.customerName
+            }
+            if (this.customerFilter.attritionLevel !== null && this.customerFilter.attritionLevel !== undefined) {
+                params.attritionLevel = this.customerFilter.attritionLevel
+            }
+            listAllAnalyze(params).then(response => {
+                this.customerList = response.rows || []
+                this.customerPagination.total = response.total || 0
+                this.customerLoading = false
+            }).catch(() => {
+                this.customerLoading = false
+            })
+        },
+
+        // 处理客户筛选
+        handleCustomerFilter() {
+            this.customerPagination.pageNum = 1
+            this.getCustomerList()
+        },
+
+        // 重置客户筛选
+        resetCustomerFilter() {
+            this.customerFilter.customerName = ''
+            this.customerFilter.attritionLevel = null
+            this.customerPagination.pageNum = 1
+            this.getCustomerList()
+        },
+
+        // 处理客户分页
+        handleCustomerPageChange(page) {
+            this.customerPagination.pageNum = page
+            this.getCustomerList()
+        },
+
+        // 点击选择按钮选择客户
+        selectCustomerByClick(customer) {
+            if (customer) {
+                this.linkedCustomer = {
+                    id: customer.customerId,
+                    name: customer.customerName,
+                    attritionLevel: customer.attritionLevel,
+                    intentionDegree: customer.intentionDegree,
+                    customerPortraitJson: customer.customerPortraitJson
+                }
+                this.customerDialogVisible = false
+                this.$message.success('已成功关联客户-' + customer.customerName)
+            }
+        },
+        // 获取图标类名
+        getIconClass(key) {
+            const iconMap = {
+                '性别': 'fas fa-venus-mars',
+                '年龄': 'fas fa-calendar-alt',
+                '职业': 'fas fa-briefcase',
+                '地区': 'fas fa-map-marker-alt',
+                '来源': 'fas fa-source',
+                '意向度': 'fas fa-heart',
+                '备注': 'fas fa-sticky-note'
+            };
+            return iconMap[key] || 'fas fa-info-circle';
+        },
+
+        // 取消选择客户
+        cancelSelectCustomer() {
+            this.customerDialogVisible = false
+            this.selectedCustomerId = null
+        },
+
+        // 获取流失风险等级文本
+        getAttritionLevelText(level) {
+            const levelMap = {
+                0: '未知',
+                1: '无风险',
+                2: '低风险',
+                3: '中风险',
+                4: '高风险'
+            }
+            return levelMap[level] !== undefined ? levelMap[level] : '未知'
+        },
+
+        // 获取流失风险等级标签类型
+        getAttritionLevelTagType(level) {
+            const typeMap = {
+                0: 'info', // 未知
+                1: 'success', // 无风险
+                2: '', // 低风险
+                3: 'warning', // 中风险
+                4: 'danger' // 高风险
+            }
+            return typeMap[level] !== undefined ? typeMap[level] : 'info'
+        },
+
+        // 处理表格行选择(单选)
+        handleCurrentChange(val) {
+            if (val) {
+                this.selectedCustomerId = val.customerId
+            }
+        },
+
+        // 取消关联客户
+        unlinkCustomer() {
+            this.linkedCustomer = null
+            this.$message.success('已取消关联客户')
+        },
+
+        // 置顶会话
+        pinChat(index) {
+            if (index === 0) return // 第一个不需要置顶
+
+            const chat = this.chatList[index]
+            // 从当前位置移除
+            this.chatList.splice(index, 1)
+            // 添加到第一个位置
+            this.chatList.unshift(chat)
+            this.$message.success('会话已置顶')
+        }
+    },
+}
+</script>
+
+<style lang="scss" scoped>
+.ai-chat-container {
+    height: 100%;
+    width: 100%;
+    display: flex;
+    background: #f9fafb;
+}
+
+.chat-main {
+    display: flex;
+    width: 100%;
+    height: 100%;
+}
+
+/* 左侧边栏 */
+.sidebar {
+    width: 260px;
+    min-width: 260px;
+    background: #f9f9fb;
+    border-right: 1px solid #e5e5ea;
+    display: flex;
+    flex-direction: column;
+}
+
+.sidebar-header {
+    padding: 16px;
+    border-bottom: 1px solid #e5e5ea;
+}
+
+.new-chat-btn {
+    width: 100%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    gap: 8px;
+    padding: 10px 16px;
+    background: white;
+    color: #1c1c1c;
+    border: 1px solid #e5e5ea;
+    border-radius: 8px;
+    cursor: pointer;
+    font-size: 14px;
+    transition: all 0.2s ease;
+
+    &:hover {
+        background: #f5f5f5;
+        border-color: #d4d4d4;
+    }
+}
+
+.chat-list {
+    flex: 1;
+    overflow-y: auto;
+    padding: 8px;
+}
+
+.chat-item {
+    display: flex;
+    align-items: center;
+    gap: 12px;
+    padding: 10px 12px;
+    margin-bottom: 4px;
+    border-radius: 8px;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    position: relative;
+
+    &:hover {
+        background: #f3f4f6;
+
+        .chat-item-actions {
+            opacity: 1;
+        }
+    }
+
+    &.active {
+        background: #e8f4ff;
+    }
+}
+
+.chat-item-actions {
+    display: flex;
+    align-items: center;
+    gap: 2px;
+    opacity: 0;
+    transition: opacity 0.2s ease;
+    position: absolute;
+    right: 8px;
+    top: 50%;
+    transform: translateY(-50%);
+    z-index: 10;
+}
+
+.chat-item-close {
+    font-size: 14px;
+    color: #9ca3af;
+    cursor: pointer;
+    padding: 6px;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    &:hover {
+        background: #fee2e2;
+        color: #ef4444;
+    }
+}
+
+.chat-item-pin {
+    font-size: 14px;
+    color: #9ca3af;
+    cursor: pointer;
+    padding: 6px;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    &:hover {
+        background: #fef3c7;
+        color: #f59e0b;
+    }
+}
+
+.chat-item-edit {
+    font-size: 14px;
+    color: #9ca3af;
+    cursor: pointer;
+    padding: 6px;
+    border-radius: 6px;
+    transition: all 0.2s ease;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    &:hover {
+        background: #dbeafe;
+        color: #3b82f6;
+    }
+}
+
+.chat-item-icon {
+    width: 0;
+    min-width: 0;
+}
+
+.chat-item-content {
+    flex: 1;
+    overflow: hidden;
+}
+
+.chat-item-title {
+    font-size: 14px;
+    color: #1c1c1c;
+    margin-bottom: 4px;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    cursor: pointer;
+
+    &:hover {
+        background: rgba(0, 0, 0, 0.05);
+        border-radius: 4px;
+        padding: 2px 4px;
+        margin: -2px -4px;
+    }
+}
+
+.chat-title-input {
+    ::v-deep .el-input__inner {
+        padding: 4px 8px;
+        font-size: 14px;
+        height: 28px;
+    }
+}
+
+.chat-item-time {
+    font-size: 12px;
+    color: #999;
+}
+
+/* 聊天内容区域 */
+.chat-content {
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+    background: white;
+    height: calc(100vh - 90px);
+    overflow: hidden;
+    position: relative;
+}
+
+// 客户画像面板
+.customer-portrait-panel {
+    position: absolute;
+    top: 50%;
+    left: 12px;
+    transform: translateY(-50%);
+    width: 320px;
+    background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+    border-radius: 16px;
+    padding: 16px;
+    box-shadow: 0 0 0 1px #d1d5db, 0 8px 32px rgba(0, 0, 0, 0.15);
+    z-index: 100;
+    max-height: calc(100vh - 320px);
+    display: flex;
+    flex-direction: column;
+}
+
+@keyframes slideInLeft {
+    from {
+        opacity: 0;
+        transform: translateX(-30px);
+    }
+    to {
+        opacity: 1;
+        transform: translateX(0);
+    }
+}
+
+.profile-grid {
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+}
+
+.profile-scroll-content {
+    flex: 1;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+    gap: 5px;
+    min-height: 0;
+    margin-top: 8px;
+    padding-top: 0;
+}
+
+.profile-scroll-content::-webkit-scrollbar {
+    width: 6px;
+}
+
+.profile-scroll-content::-webkit-scrollbar-track {
+    background: transparent;
+}
+
+.profile-scroll-content::-webkit-scrollbar-thumb {
+    background: #bae6fd;
+    border-radius: 3px;
+
+    &:hover {
+        background: #7dd3fc;
+    }
+}
+
+.profile-item-sticky {
+    position: sticky;
+    top: 0;
+    z-index: 1;
+    background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
+    border-color: #bfdbfe;
+    box-shadow: 0 2px 8px rgba(186, 230, 253, 0.3);
+}
+
+.profile-item-sticky::after {
+    content: '';
+    position: absolute;
+    bottom: 0;
+    left: 0;
+    right: 0;
+    height: 2px;
+    background: #d1d5db;
+}
+
+.profile-item {
+    display: grid;
+    grid-template-columns: 85px 1fr;
+    align-items: flex-start;
+    gap: 5px;
+    padding: 5px 8px;
+    border-radius: 10px;
+    background: linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(248, 250, 252, 0.95) 100%);
+}
+
+.profile-item-main {
+    background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
+
+    &:hover {
+        background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
+    }
+}
+
+.profile-item-full {
+    grid-template-columns: 85px 1fr;
+}
+
+.label {
+    font-size: 13px;
+    color: #0f172a;
+    font-weight: 600;
+    white-space: normal;
+    word-break: break-all;
+    max-width: 85px;
+    display: flex;
+    align-items: center;
+    gap: 6px;
+    line-height: 1.4;
+
+    i {
+        color: #0ea5e9;
+        font-size: 14px;
+        width: 14px;
+        text-align: center;
+        flex-shrink: 0;
+    }
+}
+
+.value {
+    font-size: 14px;
+    color: #0369a1;
+    font-weight: 600;
+    word-break: break-word;
+    line-height: 1.5;
+    min-width: 0;
+
+    &.highlight {
+        color: #dc2626;
+        font-weight: 600;
+        font-size: 15px;
+    }
+
+    &.long-text {
+        color: #334155;
+        font-weight: 400;
+    }
+
+    &.empty-tip {
+        color: #94a3b8;
+        font-style: italic;
+        font-size: 14px;
+    }
+}
+
+// 聊天标题头部
+.chat-title-header {
+    position: sticky;
+    top: 0;
+    left: 0;
+    right: 0;
+    background: white;
+    padding: 20px 24px;
+    border-bottom: 1px solid #f0f0f0;
+    z-index: 10;
+    text-align: center;
+}
+
+.chat-title-text {
+    margin: 0;
+    font-size: 18px;
+    font-weight: 600;
+    color: #1c1c1c;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    max-width: 100%;
+    letter-spacing: -0.02em;
+}
+
+.messages-container {
+    flex: 1;
+    overflow-y: auto;
+    padding: 24px;
+    scroll-behavior: smooth;
+    max-width: 768px;
+    margin: 0 auto;
+    width: 100%;
+    position: relative;
+}
+
+// 欢迎界面
+.welcome-container {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    justify-content: center;
+    height: 100%;
+    min-height: 400px;
+    animation: fadeIn 0.5s ease;
+}
+
+@keyframes fadeIn {
+    from {
+        opacity: 0;
+        transform: translateY(20px);
+    }
+    to {
+        opacity: 1;
+        transform: translateY(0);
+    }
+}
+
+.welcome-avatar {
+    width: 120px;
+    height: 120px;
+    margin-bottom: 24px;
+    animation: float 3s ease-in-out infinite;
+}
+
+@keyframes float {
+    0%, 100% {
+        transform: translateY(0);
+    }
+    50% {
+        transform: translateY(-10px);
+    }
+}
+
+.welcome-avatar-img {
+    width: 100%;
+    height: 100%;
+    object-fit: contain;
+}
+
+.welcome-text {
+    font-size: 28px;
+    font-weight: 600;
+    color: #1c1c1c;
+    text-align: center;
+    letter-spacing: 0.5px;
+}
+
+
+.message-row {
+    display: flex;
+    margin-bottom: 24px;
+
+    &.message-user {
+        justify-content: flex-end;
+    }
+
+    &.message-ai {
+        justify-content: flex-start;
+    }
+}
+
+.message-wrapper {
+    display: flex;
+    gap: 12px;
+    max-width: 80%;
+
+    &.message-wrapper-user {
+        flex-direction: row-reverse;
+    }
+}
+
+.avatar {
+    width: 36px;
+    height: 36px;
+    border-radius: 50%;
+    overflow: hidden;
+    flex-shrink: 0;
+    background: #f5f5f5;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+
+    img {
+        width: 100%;
+        height: 100%;
+        object-fit: cover;
+    }
+
+    &.avatar-ai {
+        background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
+    }
+
+    &.avatar-user {
+        background: linear-gradient(135deg, #10b981 0%, #059669 100%);
+    }
+}
+
+.message-body {
+    display: flex;
+    flex-direction: column;
+
+    &.message-body-user {
+        align-items: flex-end;
+    }
+}
+
+.message-name {
+    font-size: 13px;
+    color: #999;
+    margin-bottom: 8px;
+
+    &.message-name-user {
+        text-align: right;
+    }
+}
+
+.message-bubble {
+    padding: 12px 16px;
+    border-radius: 12px;
+    font-size: 15px;
+    line-height: 1.6;
+    word-break: break-word;
+
+    &.message-bubble-ai {
+        background: transparent;
+        border: none;
+        padding-left: 0;
+        box-shadow: none;
+    }
+
+    &.message-bubble-user {
+        background: #f5f5f5;
+        color: #1c1c1c;
+        box-shadow: none;
+    }
+}
+
+.thinking-bubble {
+    display: flex;
+    align-items: center;
+    padding: 16px 20px;
+}
+
+.thinking-indicator {
+    display: flex;
+    gap: 4px;
+
+    span {
+        width: 6px;
+        height: 6px;
+        border-radius: 50%;
+        background: #999;
+        animation: bounce 1.4s infinite ease-in-out both;
+
+        &:nth-child(1) {
+            animation-delay: -0.32s;
+        }
+
+        &:nth-child(2) {
+            animation-delay: -0.16s;
+        }
+    }
+}
+
+@keyframes bounce {
+    0%, 80%, 100% {
+        transform: scale(0);
+    }
+    40% {
+        transform: scale(1);
+    }
+}
+
+.input-area {
+    background: white;
+    border-top: 1px solid #e5e5ea;
+    padding: 16px 24px;
+    flex-shrink: 0;
+}
+
+// 关联客户按钮栏
+.customer-link-bar {
+    max-width: 768px;
+    margin: 0 auto 12px;
+    display: flex;
+    align-items: center;
+    gap: 12px;
+}
+
+.customer-link-btn {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+}
+
+.linked-customer-info {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+    padding: 6px 12px;
+    background: #f0f9ff;
+    border: 1px solid #bae6fd;
+    border-radius: 6px;
+    font-size: 13px;
+    color: #0366d6;
+
+    i {
+        font-size: 12px;
+    }
+
+    .unlink-icon {
+        cursor: pointer;
+        color: #999;
+        font-size: 14px;
+
+        &:hover {
+            color: #ef4444;
+        }
+    }
+}
+
+.input-container {
+    max-width: 768px;
+    margin: 0 auto;
+    position: relative;
+}
+
+.input-box {
+    flex: 1;
+    position: relative;
+
+    ::v-deep .el-textarea__inner {
+        border: 1px solid #e5e5ea;
+        border-radius: 12px;
+        padding: 12px 50px 12px 16px;
+        font-size: 15px;
+        resize: none;
+        transition: all 0.2s ease;
+        background: #fafafa;
+
+        &:focus {
+            border-color: #d4d4d4;
+            background: white;
+            box-shadow: none;
+        }
+
+        &:hover:not(:focus) {
+            border-color: #d4d4d4;
+        }
+    }
+}
+
+.send-btn-wrapper {
+    position: absolute;
+    right: 8px;
+    bottom: 8px;
+    width: 32px;
+    height: 32px;
+    border-radius: 8px;
+    background: #1c1c1c;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    cursor: pointer;
+    transition: all 0.2s ease;
+    color: white;
+    font-size: 16px;
+
+    &:hover {
+        background: #333;
+    }
+
+    &:active {
+        transform: scale(0.95);
+    }
+}
+
+
+.input-tip {
+    max-width: 768px;
+    margin: 8px auto 0;
+    text-align: center;
+    font-size: 12px;
+    color: #999;
+}
+
+// 滚动条样式
+.messages-container::-webkit-scrollbar {
+    width: 6px;
+}
+
+.messages-container::-webkit-scrollbar-track {
+    background: transparent;
+}
+
+.messages-container::-webkit-scrollbar-thumb {
+    background: #d1d5db;
+    border-radius: 3px;
+
+    &:hover {
+        background: #9ca3af;
+    }
+}
+
+.chat-list::-webkit-scrollbar {
+    width: 6px;
+}
+
+.chat-list::-webkit-scrollbar-track {
+    background: transparent;
+}
+
+.chat-list::-webkit-scrollbar-thumb {
+    background: #e5e7eb;
+    border-radius: 3px;
+
+    &:hover {
+        background: #d1d5db;
+    }
+}
+
+// 客户选择对话框样式
+.customer-dialog-content {
+    .customer-filter {
+        margin-bottom: 16px;
+        padding: 16px;
+        background: #f5f7fa;
+        border-radius: 8px;
+        border: 1px solid #e4e7ed;
+
+        ::v-deep .el-form-item {
+            margin-bottom: 0;
+            margin-right: 20px;
+        }
+
+        ::v-deep .el-input__inner {
+            width: 200px;
+        }
+
+        ::v-deep .el-select {
+            width: 150px;
+        }
+    }
+
+    .el-table {
+        margin-bottom: 16px;
+    }
+
+    .customer-pagination {
+        display: flex;
+        justify-content: flex-end;
+        padding: 12px 0;
+    }
+}
+</style>