|
|
@@ -0,0 +1,568 @@
|
|
|
+<!DOCTYPE html>
|
|
|
+<html lang="zh">
|
|
|
+<head>
|
|
|
+<meta charset="UTF-8">
|
|
|
+<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
|
+<title>龙虾引擎 - 聚合聊天</title>
|
|
|
+<script src="https://unpkg.com/vue@3/dist/vue.global.prod.js"></script>
|
|
|
+<style>
|
|
|
+*{margin:0;padding:0;box-sizing:border-box}
|
|
|
+body{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',sans-serif;background:#0a0a1a;color:#e0e0e0;height:100vh;overflow:hidden}
|
|
|
+.main{display:flex;height:100vh}
|
|
|
+
|
|
|
+/* 左侧账户列表 */
|
|
|
+.account-panel{width:80px;background:#0d0d1f;border-right:1px solid #1a1a3e;display:flex;flex-direction:column;padding:12px 0}
|
|
|
+.account-item{width:56px;height:56px;border-radius:50%;margin:0 auto 8px;cursor:pointer;position:relative;border:2px solid transparent;transition:.2s;display:flex;align-items:center;justify-content:center;font-size:24px}
|
|
|
+.account-item:hover{border-color:#e94560;transform:scale(1.05)}
|
|
|
+.account-item.active{border-color:#e94560;background:#e9456022}
|
|
|
+.account-item .badge{position:absolute;top:-2px;right:-2px;background:#e94560;color:#fff;border-radius:10px;padding:1px 5px;font-size:10px}
|
|
|
+.account-item.disabled{opacity:.4;cursor:not-allowed}
|
|
|
+
|
|
|
+/* 会话列表 */
|
|
|
+.session-panel{width:280px;background:#1a1a2e;border-right:1px solid #2a2a4a;display:flex;flex-direction:column}
|
|
|
+.session-header{padding:12px 16px;border-bottom:1px solid #2a2a4a}
|
|
|
+.session-header h3{font-size:14px;color:#e94560}
|
|
|
+.session-header .search{margin-top:8px}
|
|
|
+.session-header input{width:100%;padding:6px 10px;background:#0a0a1a;border:1px solid #2a2a4a;border-radius:4px;color:#e0e0e0;font-size:12px}
|
|
|
+.session-list{flex:1;overflow-y:auto;padding:8px}
|
|
|
+.session-item{display:flex;padding:10px;cursor:pointer;border-radius:6px;margin-bottom:4px;transition:.2s}
|
|
|
+.session-item:hover{background:#2a2a4a}
|
|
|
+.session-item.active{background:#0f3460}
|
|
|
+.session-avatar{width:40px;height:40px;border-radius:50%;background:linear-gradient(135deg,#e94560,#f59e0b);display:flex;align-items:center;justify-content:center;font-size:16px;flex-shrink:0}
|
|
|
+.session-info{flex:1;min-width:0;margin-left:10px}
|
|
|
+.session-info .name{font-size:13px;color:#e0e0e0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
|
|
+.session-info .msg{font-size:12px;color:#888;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;margin-top:2px}
|
|
|
+.session-time{font-size:11px;color:#666;text-align:right}
|
|
|
+.session-item.unread .name{font-weight:600;color:#fff}
|
|
|
+.session-item.unread .badge{background:#e94560;color:#fff;border-radius:10px;padding:1px 5px;font-size:10px}
|
|
|
+
|
|
|
+/* 聊天区域 */
|
|
|
+.chat-panel{flex:1;display:flex;flex-direction:column;background:#0a0a1a}
|
|
|
+.chat-header{padding:12px 20px;background:#1a1a2e;border-bottom:1px solid #2a2a4a;display:flex;align-items:center;justify-content:space-between}
|
|
|
+.chat-title .name{font-size:15px;color:#e0e0e0}
|
|
|
+.chat-title .type{font-size:11px;color:#888;margin-left:8px}
|
|
|
+.chat-actions{display:flex;gap:8px}
|
|
|
+.chat-actions button{padding:6px 12px;background:#0f3460;border:none;border-radius:4px;color:#e0e0e0;font-size:12px;cursor:pointer}
|
|
|
+.chat-actions button:hover{background:#1a4a80}
|
|
|
+
|
|
|
+/* 消息列表 */
|
|
|
+.message-list{flex:1;overflow-y:auto;padding:16px;display:flex;flex-direction:column}
|
|
|
+.message-item{display:flex;margin-bottom:12px;max-width:80%}
|
|
|
+.message-item.sent{align-self:flex-end}
|
|
|
+.message-item.sent .msg-bubble{background:#e94560;color:#fff;border-radius:12px 12px 0 12px}
|
|
|
+.message-item.received{align-self:flex-start}
|
|
|
+.message-item.received .msg-bubble{background:#1a1a2e;color:#e0e0e0;border-radius:12px 12px 12px 0;border:1px solid #2a2a4a}
|
|
|
+.msg-avatar{width:32px;height:32px;border-radius:50%;background:linear-gradient(135deg,#3b82f6,#22c55e);display:flex;align-items:center;justify-content:center;font-size:12px;flex-shrink:0}
|
|
|
+.message-item.sent .msg-avatar{order:2;margin-left:8px}
|
|
|
+.message-item.received .msg-avatar{order:1;margin-right:8px}
|
|
|
+.msg-content{display:flex;flex-direction:column}
|
|
|
+.message-item.sent .msg-content{order:1}
|
|
|
+.message-item.received .msg-content{order:2}
|
|
|
+.msg-bubble{padding:10px 14px;max-width:max-content}
|
|
|
+.msg-text{font-size:13px;line-height:1.5}
|
|
|
+.msg-time{font-size:10px;color:#666;margin-top:4px;text-align:right}
|
|
|
+.message-item.sent .msg-time{color:#fff8}
|
|
|
+
|
|
|
+/* 输入区域 */
|
|
|
+.chat-input{padding:12px 20px;background:#1a1a2e;border-top:1px solid #2a2a4a}
|
|
|
+.input-row{display:flex;gap:10px}
|
|
|
+.input-row textarea{flex:1;padding:10px 14px;background:#0a0a1a;border:1px solid #2a2a4a;border-radius:8px;color:#e0e0e0;font-size:13px;resize:none;min-height:44px;max-height:120px;font-family:inherit}
|
|
|
+.input-row textarea:focus{outline:none;border-color:#e94560}
|
|
|
+.input-row button{padding:10px 24px;background:#e94560;color:#fff;border:none;border-radius:8px;cursor:pointer;font-size:13px;font-weight:500;flex-shrink:0}
|
|
|
+.input-row button:hover{background:#d63850}
|
|
|
+.input-row button:disabled{opacity:.5;cursor:not-allowed}
|
|
|
+
|
|
|
+/* 客户信息面板 */
|
|
|
+.customer-panel{width:280px;background:#1a1a2e;border-left:1px solid #2a2a4a;display:flex;flex-direction:column}
|
|
|
+.customer-header{padding:16px;border-bottom:1px solid #2a2a4a;text-align:center}
|
|
|
+.customer-avatar{width:64px;height:64px;border-radius:50%;background:linear-gradient(135deg,#e94560,#f59e0b);display:flex;align-items:center;justify-content:center;font-size:24px;margin:0 auto}
|
|
|
+.customer-name{font-size:15px;color:#e0e0e0;margin-top:8px}
|
|
|
+.customer-id{font-size:11px;color:#666}
|
|
|
+.customer-tabs{display:flex;border-bottom:1px solid #2a2a4a}
|
|
|
+.customer-tabs .tab{flex:1;padding:8px;text-align:center;font-size:12px;color:#888;cursor:pointer;border-bottom:2px solid transparent}
|
|
|
+.customer-tabs .tab.active{border-color:#e94560;color:#e94560}
|
|
|
+.customer-detail{flex:1;overflow-y:auto;padding:12px}
|
|
|
+.detail-section{margin-bottom:16px}
|
|
|
+.detail-section h4{font-size:12px;color:#888;margin-bottom:8px}
|
|
|
+.detail-row{display:flex;justify-content:space-between;padding:4px 0;font-size:12px}
|
|
|
+.detail-row .label{color:#888}
|
|
|
+.detail-row .value{color:#e0e0e0}
|
|
|
+.tag-list{display:flex;flex-wrap:gap;gap:4px}
|
|
|
+.tag{display:inline-block;padding:3px 8px;background:#0f3460;color:#ccc;border-radius:4px;font-size:11px}
|
|
|
+.record-item{padding:8px;border-bottom:1px solid #1a1a3e}
|
|
|
+.record-item .time{font-size:11px;color:#666}
|
|
|
+.record-item .desc{font-size:12px;color:#e0e0e0;margin-top:2px}
|
|
|
+
|
|
|
+/* 渠道图标 */
|
|
|
+.channel-qw{background:linear-gradient(135deg,#1890ff,#096dd9)}
|
|
|
+.channel-wx{background:linear-gradient(135deg,#07c160,#10b981)}
|
|
|
+.channel-im{background:linear-gradient(135deg,#6366f1,#8b5cf6)}
|
|
|
+.channel-whatsapp{background:linear-gradient(135deg,#25d366,#10b981)}
|
|
|
+.channel-other{background:linear-gradient(135deg,#6b7280,#9ca3af)}
|
|
|
+
|
|
|
+/* 滚动条 */
|
|
|
+::-webkit-scrollbar{width:6px}
|
|
|
+::-webkit-scrollbar-track{background:#0a0a1a}
|
|
|
+::-webkit-scrollbar-thumb{background:#2a2a4a;border-radius:3px}
|
|
|
+::-webkit-scrollbar-thumb:hover{background:#3a3a5a}
|
|
|
+</style>
|
|
|
+</head>
|
|
|
+<body>
|
|
|
+<div id="app" class="main">
|
|
|
+ <!-- 左侧账户列表 -->
|
|
|
+ <div class="account-panel">
|
|
|
+ <div v-for="acc in accounts" :key="acc.id"
|
|
|
+ :class="['account-item', acc.active ? 'active' : '', acc.connected ? '' : 'disabled']"
|
|
|
+ @click="selectAccount(acc)" :title="acc.name">
|
|
|
+ <span>{{acc.icon}}</span>
|
|
|
+ <span v-if="acc.unread>0" class="badge">{{acc.unread}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 会话列表 -->
|
|
|
+ <div class="session-panel">
|
|
|
+ <div class="session-header">
|
|
|
+ <h3>🗨️ 聊天列表</h3>
|
|
|
+ <div class="search">
|
|
|
+ <input v-model="searchKey" placeholder="搜索联系人..." @keyup="filterSessions">
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="session-list">
|
|
|
+ <div v-for="sess in filteredSessions" :key="sess.sessionId"
|
|
|
+ :class="['session-item', sess.sessionId === currentSession?.sessionId ? 'active' : '', sess.unread > 0 ? 'unread' : '']"
|
|
|
+ @click="selectSession(sess)">
|
|
|
+ <div class="session-avatar">{{sess.avatar||'?'}}</div>
|
|
|
+ <div class="session-info">
|
|
|
+ <div class="name">{{sess.name}}</div>
|
|
|
+ <div class="msg">{{sess.lastMsg||'暂无消息'}}</div>
|
|
|
+ </div>
|
|
|
+ <div style="text-align:right;margin-left:8px">
|
|
|
+ <div class="session-time">{{sess.lastTime||''}}</div>
|
|
|
+ <span v-if="sess.unread>0" class="badge">{{sess.unread}}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="filteredSessions.length===0" style="text-align:center;padding:40px;color:#666;font-size:13px">
|
|
|
+ 暂无会话记录
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 聊天区域 -->
|
|
|
+ <div class="chat-panel">
|
|
|
+ <div v-if="currentSession" class="chat-header">
|
|
|
+ <div class="chat-title">
|
|
|
+ <span class="name">{{currentSession.name}}</span>
|
|
|
+ <span class="type">{{channelName(currentSession.channelType)}}</span>
|
|
|
+ </div>
|
|
|
+ <div class="chat-actions">
|
|
|
+ <button @click="toggleControlMode">{{currentSession.controlMode === 'ai' ? '🤖 AI接管中' : '👤 人工接管'}}</button>
|
|
|
+ <button @click="showCustomerInfo=true">👤 客户信息</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-else class="chat-header">
|
|
|
+ <div class="chat-title">
|
|
|
+ <span class="name">请选择一个会话</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="message-list" ref="messageList">
|
|
|
+ <div v-for="(msg, idx) in messages" :key="idx" :class="['message-item', msg.sendType === 1 ? 'received' : 'sent']">
|
|
|
+ <div class="msg-avatar">{{msg.sendType === 1 ? '👤' : '🤖'}}</div>
|
|
|
+ <div class="msg-content">
|
|
|
+ <div class="msg-bubble">
|
|
|
+ <div class="msg-text">{{msg.content}}</div>
|
|
|
+ </div>
|
|
|
+ <div class="msg-time">{{msg.time}}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div v-if="messages.length===0" style="text-align:center;padding:60px;color:#666">
|
|
|
+ <div style="font-size:48px;margin-bottom:12px">💬</div>
|
|
|
+ <p>开始与客户聊天</p>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class="chat-input">
|
|
|
+ <div class="input-row">
|
|
|
+ <textarea v-model="inputMsg" placeholder="输入消息..." @keyup.enter="sendMessage"></textarea>
|
|
|
+ <button @click="sendMessage" :disabled="!inputMsg.trim()">发送</button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 客户信息面板 -->
|
|
|
+ <div class="customer-panel" v-if="showCustomerInfo">
|
|
|
+ <div class="customer-header">
|
|
|
+ <div class="customer-avatar">{{currentSession?.avatar||'?'}}</div>
|
|
|
+ <div class="customer-name">{{currentSession?.name||'-'}}</div>
|
|
|
+ <div class="customer-id">{{currentSession?.channelSourceId||'-'}}</div>
|
|
|
+ </div>
|
|
|
+ <div class="customer-tabs">
|
|
|
+ <div :class="['tab', customerTab==='basic'?'active':'']" @click="customerTab='basic'">基本信息</div>
|
|
|
+ <div :class="['tab', customerTab==='tags'?'active':'']" @click="customerTab='tags'">标签</div>
|
|
|
+ <div :class="['tab', customerTab==='records'?'active':'']" @click="customerTab='records'">访问记录</div>
|
|
|
+ </div>
|
|
|
+ <div class="customer-detail">
|
|
|
+ <div v-if="customerTab==='basic'" class="detail-section">
|
|
|
+ <h4>📋 基本信息</h4>
|
|
|
+ <div class="detail-row"><span class="label">渠道</span><span class="value">{{channelName(currentSession?.channelType)}}</span></div>
|
|
|
+ <div class="detail-row"><span class="label">来源ID</span><span class="value">{{currentSession?.channelSourceId||'-'}}</span></div>
|
|
|
+ <div class="detail-row"><span class="label">联系人ID</span><span class="value">{{currentSession?.contactId||'-'}}</span></div>
|
|
|
+ <div class="detail-row"><span class="label">会话ID</span><span class="value">{{currentSession?.sessionId||'-'}}</span></div>
|
|
|
+ <div class="detail-row"><span class="label">创建时间</span><span class="value">{{currentSession?.createTime||'-'}}</span></div>
|
|
|
+ </div>
|
|
|
+ <div v-if="customerTab==='tags'" class="detail-section">
|
|
|
+ <h4>🏷️ 客户标签</h4>
|
|
|
+ <div class="tag-list">
|
|
|
+ <span v-for="tag in customerTags" :key="tag" class="tag">{{tag}}</span>
|
|
|
+ </div>
|
|
|
+ <div v-if="customerTags.length===0" style="color:#666;font-size:12px">暂无标签</div>
|
|
|
+ </div>
|
|
|
+ <div v-if="customerTab==='records'" class="detail-section">
|
|
|
+ <h4>📊 访问记录</h4>
|
|
|
+ <div v-for="record in visitRecords" :key="record.time" class="record-item">
|
|
|
+ <div class="time">{{record.time}}</div>
|
|
|
+ <div class="desc">{{record.desc}}</div>
|
|
|
+ </div>
|
|
|
+ <div v-if="visitRecords.length===0" style="color:#666;font-size:12px">暂无访问记录</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+</div>
|
|
|
+
|
|
|
+<script>
|
|
|
+const {createApp, ref, computed, watch, nextTick, onMounted} = Vue;
|
|
|
+ createApp({
|
|
|
+ setup(){
|
|
|
+ const searchKey = ref('');
|
|
|
+ const inputMsg = ref('');
|
|
|
+ const showCustomerInfo = ref(true);
|
|
|
+ const customerTab = ref('basic');
|
|
|
+ const messageList = ref(null);
|
|
|
+ const loading = ref(false);
|
|
|
+
|
|
|
+ // API请求工具函数
|
|
|
+ const request = async (url, options = {}) => {
|
|
|
+ const defaultOptions = {
|
|
|
+ headers: {
|
|
|
+ 'Content-Type': 'application/json',
|
|
|
+ },
|
|
|
+ };
|
|
|
+ const response = await fetch(url, { ...defaultOptions, ...options });
|
|
|
+ if (!response.ok) {
|
|
|
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
|
+ }
|
|
|
+ return await response.json();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 账户列表
|
|
|
+ const accounts = ref([]);
|
|
|
+
|
|
|
+ // 会话列表
|
|
|
+ const sessions = ref([]);
|
|
|
+
|
|
|
+ // 当前会话
|
|
|
+ const currentSession = ref(null);
|
|
|
+
|
|
|
+ // 消息列表
|
|
|
+ const messages = ref([]);
|
|
|
+
|
|
|
+ // 客户标签
|
|
|
+ const customerTags = ref([]);
|
|
|
+
|
|
|
+ // 访问记录
|
|
|
+ const visitRecords = ref([]);
|
|
|
+
|
|
|
+ // 当前登录用户的企微账户
|
|
|
+ const currentAccount = ref(null);
|
|
|
+
|
|
|
+ // 过滤后的会话
|
|
|
+ const filteredSessions = computed(()=>{
|
|
|
+ if(!searchKey.value) return sessions.value;
|
|
|
+ const key = searchKey.value.toLowerCase();
|
|
|
+ return sessions.value.filter(s=>{
|
|
|
+ const name = s.nickName || s.name || '';
|
|
|
+ return name.toLowerCase().includes(key);
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ // 加载账户列表
|
|
|
+ const loadAccounts = async () => {
|
|
|
+ try {
|
|
|
+ // 先获取当前登录用户绑定的企微账户
|
|
|
+ const res = await request('/qw/user/getMyQwUserList');
|
|
|
+ if (res.code === 200 || res.code === 0) {
|
|
|
+ const qwAccounts = (res.data || []).map((acc, idx) => ({
|
|
|
+ id: acc.id || `qw_${idx}`,
|
|
|
+ name: acc.qwUserName || '企微账户',
|
|
|
+ icon: '💼',
|
|
|
+ active: idx === 0,
|
|
|
+ connected: true,
|
|
|
+ unread: 0,
|
|
|
+ type: 'QW',
|
|
|
+ corpId: acc.corpId,
|
|
|
+ qwUserId: acc.qwUserId
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 添加个微账户占位(后续扩展)
|
|
|
+ const wxAccounts = []; // 暂时为空,后续实现个微账户绑定
|
|
|
+
|
|
|
+ accounts.value = [...qwAccounts, ...wxAccounts];
|
|
|
+
|
|
|
+ if (accounts.value.length > 0) {
|
|
|
+ currentAccount.value = accounts.value[0];
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载账户失败:', error);
|
|
|
+ // 降级显示模拟账户
|
|
|
+ accounts.value = [
|
|
|
+ {id:'qw', name:'企业微信', icon:'💼', active:true, connected:true, unread:0, type:'QW'},
|
|
|
+ {id:'wx', name:'个人微信', icon:'💬', active:false, connected:false, unread:0, type:'WX'},
|
|
|
+ ];
|
|
|
+ currentAccount.value = accounts.value[0];
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 加载会话列表
|
|
|
+ const loadSessions = async () => {
|
|
|
+ try {
|
|
|
+ // 先尝试加载chat会话
|
|
|
+ const chatRes = await request('/chat/chatSession/list?pageNum=1&pageSize=100');
|
|
|
+ if (chatRes.code === 200 || chatRes.rows) {
|
|
|
+ const chatSessions = (chatRes.rows || []).map(s => ({
|
|
|
+ sessionId: s.sessionId,
|
|
|
+ name: s.nickName || s.userName || '客户',
|
|
|
+ avatar: (s.nickName || s.userName || '客').charAt(0),
|
|
|
+ channelType: 'CHAT',
|
|
|
+ channelSourceId: s.userId,
|
|
|
+ contactId: s.userId,
|
|
|
+ lastMsg: '',
|
|
|
+ lastTime: s.createTime || '',
|
|
|
+ unread: 0,
|
|
|
+ createTime: s.createTime,
|
|
|
+ status: s.status
|
|
|
+ }));
|
|
|
+
|
|
|
+ // 再尝试加载企微外部联系人作为会话
|
|
|
+ const qwRes = await request('/qw/externalContact/list?pageNum=1&pageSize=100');
|
|
|
+ if (qwRes.code === 200 || qwRes.rows) {
|
|
|
+ const qwSessions = (qwRes.rows || []).map(c => ({
|
|
|
+ sessionId: `qw_${c.id}`,
|
|
|
+ name: c.name || c.remark || '企微客户',
|
|
|
+ avatar: (c.name || c.remark || '客').charAt(0),
|
|
|
+ channelType: 'QW',
|
|
|
+ channelSourceId: c.externalUserId,
|
|
|
+ contactId: c.id,
|
|
|
+ lastMsg: '',
|
|
|
+ lastTime: c.createTime || '',
|
|
|
+ unread: 0,
|
|
|
+ createTime: c.createTime,
|
|
|
+ tagIds: c.tagIds,
|
|
|
+ customerId: c.customerId,
|
|
|
+ remarkMobiles: c.remarkMobiles
|
|
|
+ }));
|
|
|
+ sessions.value = [...qwSessions, ...chatSessions];
|
|
|
+ } else {
|
|
|
+ sessions.value = chatSessions;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载会话失败:', error);
|
|
|
+ sessions.value = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sessions.value.length > 0 && !currentSession.value) {
|
|
|
+ selectSession(sessions.value[0]);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 选择账户
|
|
|
+ const selectAccount = (acc)=>{
|
|
|
+ if(!acc.connected) return;
|
|
|
+ accounts.value.forEach(a=>a.active = false);
|
|
|
+ acc.active = true;
|
|
|
+ currentAccount.value = acc;
|
|
|
+ loadSessions();
|
|
|
+ };
|
|
|
+
|
|
|
+ // 选择会话
|
|
|
+ const selectSession = async (sess)=>{
|
|
|
+ currentSession.value = sess;
|
|
|
+ // 清除未读
|
|
|
+ sess.unread = 0;
|
|
|
+ // 加载消息
|
|
|
+ await loadMessages(sess);
|
|
|
+ // 加载客户标签和信息
|
|
|
+ await loadCustomerInfo(sess);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 加载消息
|
|
|
+ const loadMessages = async (session) => {
|
|
|
+ messages.value = [];
|
|
|
+ try {
|
|
|
+ if (session.channelType === 'CHAT') {
|
|
|
+ // 加载chat会话消息
|
|
|
+ const chatDetailRes = await request(`/chat/chatSession/${session.sessionId}`);
|
|
|
+ if (chatDetailRes.code === 200 || chatDetailRes.data) {
|
|
|
+ const msgRes = await request(`/chat/chatMsg/list?sessionId=${session.sessionId}&pageNum=1&pageSize=100`);
|
|
|
+ if (msgRes.rows) {
|
|
|
+ messages.value = msgRes.rows.map(m => ({
|
|
|
+ content: m.content,
|
|
|
+ sendType: m.sendType,
|
|
|
+ time: m.createTime || new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'})
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } else if (session.channelType === 'QW') {
|
|
|
+ // TODO: 加载企微聊天记录
|
|
|
+ messages.value = [];
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载消息失败:', error);
|
|
|
+ messages.value = [];
|
|
|
+ }
|
|
|
+
|
|
|
+ nextTick(()=>{
|
|
|
+ if(messageList.value){
|
|
|
+ messageList.value.scrollTop = messageList.value.scrollHeight;
|
|
|
+ }
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 加载客户信息
|
|
|
+ const loadCustomerInfo = async (session) => {
|
|
|
+ customerTags.value = [];
|
|
|
+ visitRecords.value = [];
|
|
|
+
|
|
|
+ try {
|
|
|
+ if (session.tagIds && session.tagIds !== '[]') {
|
|
|
+ // 解析标签ID并加载标签名称
|
|
|
+ const tagIds = JSON.parse(session.tagIds);
|
|
|
+ if (tagIds.length > 0) {
|
|
|
+ const tagRes = await request(`/qw/tag/list?tagIds=${tagIds.join(',')}`);
|
|
|
+ if (tagRes.rows) {
|
|
|
+ customerTags.value = tagRes.rows.map(t => t.tagName || t.name);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载CRM客户信息
|
|
|
+ if (session.customerId) {
|
|
|
+ const crmRes = await request(`/crm/customer/${session.customerId}`);
|
|
|
+ if (crmRes.data) {
|
|
|
+ // 补充客户基本信息
|
|
|
+ if (crmRes.data.phone) {
|
|
|
+ visitRecords.value.push({
|
|
|
+ time: new Date().toLocaleDateString('zh-CN'),
|
|
|
+ desc: `手机号: ${crmRes.data.phone}`
|
|
|
+ });
|
|
|
+ }
|
|
|
+ if (crmRes.data.email) {
|
|
|
+ visitRecords.value.push({
|
|
|
+ time: new Date().toLocaleDateString('zh-CN'),
|
|
|
+ desc: `邮箱: ${crmRes.data.email}`
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载fastGpt聊天摘要
|
|
|
+ try {
|
|
|
+ const chatRes = await request('/fastGpt/fastGptChatSession/list?pageNum=1&pageSize=10');
|
|
|
+ if (chatRes.rows) {
|
|
|
+ const relatedChat = chatRes.rows.find(c =>
|
|
|
+ c.externalUserId === session.channelSourceId ||
|
|
|
+ c.userId === session.contactId
|
|
|
+ );
|
|
|
+ if (relatedChat) {
|
|
|
+ visitRecords.value.push({
|
|
|
+ time: relatedChat.createTime || new Date().toLocaleDateString('zh-CN'),
|
|
|
+ desc: `最近聊天: ${relatedChat.lastMsg || '暂无消息'}`
|
|
|
+ });
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (err) {
|
|
|
+ console.error('加载聊天摘要失败:', err);
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载客户信息失败:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 发送消息
|
|
|
+ const sendMessage = async ()=>{
|
|
|
+ if(!inputMsg.value.trim() || !currentSession.value) return;
|
|
|
+
|
|
|
+ const msg = {
|
|
|
+ content: inputMsg.value,
|
|
|
+ sendType: 2,
|
|
|
+ time: new Date().toLocaleTimeString('zh-CN',{hour:'2-digit',minute:'2-digit'})
|
|
|
+ };
|
|
|
+ messages.value.push(msg);
|
|
|
+ const contentToSend = inputMsg.value;
|
|
|
+ inputMsg.value = '';
|
|
|
+
|
|
|
+ nextTick(()=>{
|
|
|
+ if(messageList.value){
|
|
|
+ messageList.value.scrollTop = messageList.value.scrollHeight;
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 调用发送消息接口
|
|
|
+ try {
|
|
|
+ if (currentSession.value.channelType === 'QW') {
|
|
|
+ // 企微发送消息
|
|
|
+ await request('/qw/msg/send', {
|
|
|
+ method: 'POST',
|
|
|
+ body: JSON.stringify({
|
|
|
+ externalUserId: currentSession.value.channelSourceId,
|
|
|
+ content: contentToSend,
|
|
|
+ qwUserId: currentAccount.value?.qwUserId
|
|
|
+ })
|
|
|
+ });
|
|
|
+ } else if (currentSession.value.channelType === 'CHAT') {
|
|
|
+ // Chat会话发送消息
|
|
|
+ await request('/chat/chatMsg', {
|
|
|
+ method: 'POST',
|
|
|
+ body: JSON.stringify({
|
|
|
+ sessionId: currentSession.value.sessionId,
|
|
|
+ content: contentToSend,
|
|
|
+ sendType: 2
|
|
|
+ })
|
|
|
+ });
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('发送消息失败:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 切换控制模式
|
|
|
+ const toggleControlMode = ()=>{
|
|
|
+ if(currentSession.value){
|
|
|
+ currentSession.value.controlMode = currentSession.value.controlMode === 'ai' ? 'human' : 'ai';
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 渠道名称
|
|
|
+ const channelName = (type)=>{
|
|
|
+ const names = {QW:'企业微信', WX:'个人微信', IM:'系统IM', WHATSAPP:'WhatsApp', OTHER:'其他渠道', CHAT:'在线咨询'};
|
|
|
+ return names[type] || type;
|
|
|
+ };
|
|
|
+
|
|
|
+ // 初始化
|
|
|
+ onMounted(async () => {
|
|
|
+ await loadAccounts();
|
|
|
+ await loadSessions();
|
|
|
+ });
|
|
|
+
|
|
|
+ return {
|
|
|
+ searchKey, inputMsg, showCustomerInfo, customerTab, messageList, loading,
|
|
|
+ accounts, sessions, currentSession, messages, customerTags, visitRecords,
|
|
|
+ filteredSessions,
|
|
|
+ selectAccount, selectSession, sendMessage, toggleControlMode, channelName
|
|
|
+ };
|
|
|
+ }
|
|
|
+ }).mount('#app');
|
|
|
+</script>
|
|
|
+</body>
|
|
|
+</html>
|