|
@@ -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>
|