|
|
@@ -0,0 +1,1684 @@
|
|
|
+<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>
|
|
|
+ </div>
|
|
|
+ <div class="progress-bar-wrapper">
|
|
|
+ <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 this.focusPoints; // 返回默认关注点
|
|
|
+ }
|
|
|
+ const latestRecord = this.communicationRecords[0];
|
|
|
+ if (latestRecord && latestRecord.customerFocusJson) {
|
|
|
+ try {
|
|
|
+ // 如果是字符串,解析为 JSON 数组
|
|
|
+ if (typeof latestRecord.customerFocusJson === 'string') {
|
|
|
+ const parsed = JSON.parse(latestRecord.customerFocusJson);
|
|
|
+ return Array.isArray(parsed) ? parsed : this.focusPoints;
|
|
|
+ }
|
|
|
+ // 如果已经是数组,直接返回
|
|
|
+ return Array.isArray(latestRecord.customerFocusJson) ? latestRecord.customerFocusJson : this.focusPoints;
|
|
|
+ } catch (error) {
|
|
|
+ console.error('解析客户关注点 JSON 失败:', error);
|
|
|
+ return this.focusPoints;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return this.focusPoints; // 默认返回硬编码数据
|
|
|
+ }
|
|
|
+ },
|
|
|
+ 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-none',
|
|
|
+ 'badge-low',
|
|
|
+ 'badge-medium',
|
|
|
+ 'badge-high'
|
|
|
+ ];
|
|
|
+ return badgeClasses[level] || 'badge-none';
|
|
|
+ },
|
|
|
+ // 获取客户意向度
|
|
|
+ getIntentionDegree() {
|
|
|
+ if (!this.communicationRecords || this.communicationRecords.length === 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ const latestRecord = this.communicationRecords[0];
|
|
|
+ const degree = parseInt(latestRecord.intentionDegree);
|
|
|
+ // 如果是有效数字且在 0-100 之间,返回该值,否则返回默认值
|
|
|
+ return (degree >= 0 && degree <= 100) ? degree : 0;
|
|
|
+ },
|
|
|
+ // 获取单条记录的风险等级数值
|
|
|
+ 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-none', 'risk-low', 'risk-medium', 'risk-high'];
|
|
|
+ return classes[level] || 'risk-none';
|
|
|
+ },
|
|
|
+ // 获取单条记录的客户意向度
|
|
|
+ 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-none', 'risk-low', 'risk-medium', 'risk-high'];
|
|
|
+ return classes[level] || 'risk-none';
|
|
|
+ },
|
|
|
+ // 获取流失风险分析
|
|
|
+ getRiskLevelAnalysis() {
|
|
|
+ if (!this.communicationRecords || this.communicationRecords.length === 0) {
|
|
|
+ return 0;
|
|
|
+ }
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+.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::before {
|
|
|
+ background: linear-gradient(90deg, #10b981 0%, #34d399 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.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);
|
|
|
+}
|
|
|
+
|
|
|
+.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;
|
|
|
+}
|
|
|
+
|
|
|
+/* AI 标签美化样式 */
|
|
|
+.tags-container {
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.tags-grid {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
|
|
+ gap: 6px;
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.tag-chip {
|
|
|
+ display: inline-flex;
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+.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: nowrap;
|
|
|
+}
|
|
|
+
|
|
|
+.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: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.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;
|
|
|
+}
|
|
|
+
|
|
|
+.profile-item {
|
|
|
+ display: grid;
|
|
|
+ grid-template-columns: 110px 1fr;
|
|
|
+ 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;
|
|
|
+}
|
|
|
+
|
|
|
+.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;
|
|
|
+}
|
|
|
+
|
|
|
+.profile-item-main:hover {
|
|
|
+ background: linear-gradient(135deg, #dbeafe 0%, #bfdbfe 100%);
|
|
|
+}
|
|
|
+
|
|
|
+.profile-item-full {
|
|
|
+ grid-template-columns: 110px 1fr;
|
|
|
+}
|
|
|
+
|
|
|
+.profile-item .label {
|
|
|
+ font-size: 13px;
|
|
|
+ color: #64748b;
|
|
|
+ font-weight: 600;
|
|
|
+ white-space: nowrap;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ line-height: 1.3;
|
|
|
+}
|
|
|
+
|
|
|
+.profile-item .label i {
|
|
|
+ color: #94a3b8;
|
|
|
+ font-size: 12px;
|
|
|
+ width: 14px;
|
|
|
+ text-align: center;
|
|
|
+}
|
|
|
+
|
|
|
+.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;
|
|
|
+}
|
|
|
+
|
|
|
+.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;
|
|
|
+}
|
|
|
+
|
|
|
+.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>
|