|
|
@@ -15,13 +15,13 @@
|
|
|
<div class="login-tip">请扫码登录企微</div>
|
|
|
<div id="login-container" ref="loginContainer" class="login-container"></div>
|
|
|
</div>
|
|
|
- <!-- 已登录但未选择员工 -->
|
|
|
- <div v-else-if="!staffUserId" class="empty-tip">
|
|
|
+ <!-- 已登录但未选择员工(群聊不需要员工) -->
|
|
|
+ <div v-else-if="!staffUserId && !chatId" class="empty-tip">
|
|
|
<i class="el-icon-info"></i>
|
|
|
<p>请选择员工</p>
|
|
|
</div>
|
|
|
- <!-- 已登录但未选择客户 -->
|
|
|
- <div v-else-if="!customerId" class="empty-tip">
|
|
|
+ <!-- 已登录但未选择客户(群聊不需要客户) -->
|
|
|
+ <div v-else-if="!customerId && !chatId" class="empty-tip">
|
|
|
<i class="el-icon-info"></i>
|
|
|
<p>请选择客户查看会话</p>
|
|
|
</div>
|
|
|
@@ -42,7 +42,6 @@
|
|
|
|
|
|
<script>
|
|
|
import * as ww from '@wecom/jssdk';
|
|
|
-import defaultStaffAvatar from '@/assets/images/user.png';
|
|
|
import { qwLogin, qwSignature, qwConversations, getQwSessionConfig } from '@/api/qw/companySession';
|
|
|
|
|
|
// ========== 全局配置缓存 ==========
|
|
|
@@ -70,8 +69,8 @@ export default {
|
|
|
props: {
|
|
|
corpId: { type: String, default: null },
|
|
|
customerId: { type: String, default: null },
|
|
|
- customerAvatar: { type: String, default: '' },
|
|
|
- staffUserId: { type: String, default: null }
|
|
|
+ staffUserId: { type: String, default: null },
|
|
|
+ chatId: { type: String, default: null }
|
|
|
},
|
|
|
data() {
|
|
|
return {
|
|
|
@@ -83,7 +82,6 @@ export default {
|
|
|
loadingMore: false,
|
|
|
chatInstance: null,
|
|
|
_sdkInited: false,
|
|
|
- defaultStaffAvatar: defaultStaffAvatar,
|
|
|
config: { corpid: '', agentid: '', domain: '' },
|
|
|
configReady: false,
|
|
|
configError: false,
|
|
|
@@ -97,7 +95,7 @@ export default {
|
|
|
},
|
|
|
watch: {
|
|
|
customerId(newId, oldId) {
|
|
|
- if (newId && newId !== oldId && this.isLoggedIn && this.staffUserId && this.configReady && !this.configError) {
|
|
|
+ if (newId && newId !== oldId && this.isLoggedIn && (this.staffUserId || this.chatId) && this.configReady && !this.configError) {
|
|
|
this.resetAndReload();
|
|
|
}
|
|
|
},
|
|
|
@@ -106,6 +104,11 @@ export default {
|
|
|
this.resetAndReload();
|
|
|
}
|
|
|
},
|
|
|
+ chatId(newVal, oldVal) {
|
|
|
+ if (newVal && newVal !== oldVal && this.isLoggedIn && this.configReady && !this.configError) {
|
|
|
+ this.resetAndReload();
|
|
|
+ }
|
|
|
+ },
|
|
|
corpId(newId, oldId) {
|
|
|
if (newId !== oldId) {
|
|
|
this.destroyChat();
|
|
|
@@ -137,11 +140,6 @@ export default {
|
|
|
}
|
|
|
}
|
|
|
},
|
|
|
- customerAvatar(newAvatar) {
|
|
|
- if (this.chatInstance && newAvatar) {
|
|
|
- this.chatInstance.setData({ customerAvatar: newAvatar });
|
|
|
- }
|
|
|
- },
|
|
|
isLoggedIn: {
|
|
|
handler(newVal) {
|
|
|
if (!newVal && this.configReady && !this.configError && this._sdkInited && !this._loginPanelCreated) {
|
|
|
@@ -264,7 +262,7 @@ export default {
|
|
|
await this.handleLogin(code);
|
|
|
this.storeLoginState(this.corpId);
|
|
|
this.isLoggedIn = true;
|
|
|
- if (this.customerId && this.staffUserId) await this.loadFirstPage();
|
|
|
+ if ((this.customerId && this.staffUserId) || this.chatId) await this.loadFirstPage();
|
|
|
} catch (err) {
|
|
|
this.clearLoginState(this.corpId);
|
|
|
this.isLoggedIn = false;
|
|
|
@@ -273,7 +271,7 @@ export default {
|
|
|
} else {
|
|
|
if (this.checkLoginState(this.corpId)) {
|
|
|
this.isLoggedIn = true;
|
|
|
- if (this.customerId && this.staffUserId) await this.loadFirstPage();
|
|
|
+ if ((this.customerId && this.staffUserId) || this.chatId) await this.loadFirstPage();
|
|
|
} else {
|
|
|
this.isLoggedIn = false;
|
|
|
this.safeEmitLogout();
|
|
|
@@ -316,7 +314,7 @@ export default {
|
|
|
onLoginSuccess: async ({ code }) => {
|
|
|
try {
|
|
|
await this.handleLogin(code);
|
|
|
- if (this.customerId && this.staffUserId) await this.loadFirstPage();
|
|
|
+ if ((this.customerId && this.staffUserId) || this.chatId) await this.loadFirstPage();
|
|
|
} catch (e) {
|
|
|
this.clearLoginState(this.corpId);
|
|
|
this.isLoggedIn = false;
|
|
|
@@ -394,11 +392,13 @@ export default {
|
|
|
staffUserId: this.staffUserId,
|
|
|
limit: 100,
|
|
|
cursor: this.cursor,
|
|
|
- corpid: this.corpId
|
|
|
+ corpid: this.corpId,
|
|
|
+ chatId: this.chatId || undefined
|
|
|
});
|
|
|
const data = this._extractResponse(res);
|
|
|
if (data.errcode !== 0) throw new Error(data.errmsg || '拉取失败');
|
|
|
- this.msgList = data.data || [];
|
|
|
+ const rawList = data.data || [];
|
|
|
+ this.msgList = this.processMessages(rawList, false);
|
|
|
this.cursor = data.next_cursor || '';
|
|
|
this.hasMore = data.has_more === 1;
|
|
|
this.isReady = true;
|
|
|
@@ -421,13 +421,15 @@ export default {
|
|
|
staffUserId: this.staffUserId,
|
|
|
limit: 50,
|
|
|
cursor: this.cursor,
|
|
|
- corpid: this.corpId
|
|
|
+ corpid: this.corpId,
|
|
|
+ chatId: this.chatId || undefined
|
|
|
});
|
|
|
const data = this._extractResponse(res);
|
|
|
if (data.errcode !== 0) throw new Error(data.errmsg || '拉取更多失败');
|
|
|
const newMessages = data.data || [];
|
|
|
if (newMessages.length) {
|
|
|
- this.msgList.push(...newMessages);
|
|
|
+ const processed = this.processMessages(newMessages, true);
|
|
|
+ this.msgList.push(...processed);
|
|
|
this.cursor = data.next_cursor || '';
|
|
|
this.hasMore = data.has_more === 1;
|
|
|
if (this.chatInstance) this.chatInstance.setData({ msgList: this.msgList });
|
|
|
@@ -442,6 +444,27 @@ export default {
|
|
|
}
|
|
|
},
|
|
|
|
|
|
+ processMessages(messages, isAppend = false) {
|
|
|
+ const prevLastMsg = isAppend && this.msgList.length > 0
|
|
|
+ ? this.msgList[this.msgList.length - 1]
|
|
|
+ : null
|
|
|
+ let prevDate = prevLastMsg ? (prevLastMsg.dateStr || '') : ''
|
|
|
+
|
|
|
+ return messages.map(msg => {
|
|
|
+ const timeStr = msg.send_time_str || ''
|
|
|
+ const parts = timeStr.split(' ')
|
|
|
+ const dateStr = parts[0] || ''
|
|
|
+ const displayTime = parts[1] ? parts[1].substring(0, 5) : ''
|
|
|
+ const showDate = !!dateStr && dateStr !== prevDate
|
|
|
+ prevDate = dateStr
|
|
|
+ return {
|
|
|
+ ...msg,
|
|
|
+ dateStr,
|
|
|
+ displayTime,
|
|
|
+ showDate
|
|
|
+ }
|
|
|
+ })
|
|
|
+ },
|
|
|
async renderOrUpdateChat() {
|
|
|
if (this.msgList.length === 0 || this.configError) return;
|
|
|
const container = document.getElementById('chat-container');
|
|
|
@@ -460,26 +483,87 @@ export default {
|
|
|
template: `
|
|
|
<scroll-view scroll-y="{{true}}" bindscrolltolower="onScrollToLower" style="height: 100%;">
|
|
|
<view wx:for="{{data.msgList}}" wx:for-item="msg" wx:key="msgid" style="margin-bottom: 15px;">
|
|
|
- <view style="text-align: center; margin-bottom: 8px;">
|
|
|
- <text style="background:#e9e9e9; color:#333; padding:4px 12px; border-radius:16px; font-size:12px;">
|
|
|
- {{ msg.send_time_str }}
|
|
|
- </text>
|
|
|
+ <!-- 日期分隔线:仅跨天时显示 -->
|
|
|
+ <view wx:if="{{msg.showDate}}" style="text-align: center; margin-bottom: 12px;">
|
|
|
+ <span style="background:#e9e9e9; color:#888; padding:3px 12px; border-radius:12px; font-size:11px;">{{ msg.dateStr }}</span>
|
|
|
+ </view>
|
|
|
+ <!-- 客户/机器人消息(左对齐) -->
|
|
|
+ <view wx:if="{{msg.sender.type == 2 || msg.sender.type == 3}}" style="display:flex; flex-direction:row; align-items:flex-start;">
|
|
|
+ <ww-open-data wx:if="{{msg.sender.type == 2}}" type="externalUserAvatar" openid="{{msg.sender.id}}" class="msg-avatar-left" />
|
|
|
+ <ww-open-data wx:if="{{msg.sender.type == 3}}" type="userAvatar" openid="{{msg.sender.id}}" class="msg-avatar-left" />
|
|
|
+ <view style="display:flex; flex-direction:column; max-width:70%; margin-left:10px;">
|
|
|
+ <view style="display:flex; align-items:center; margin-bottom:4px;">
|
|
|
+ <ww-open-data wx:if="{{msg.sender.type == 2}}" type="externalUserName" openid="{{msg.sender.id}}" class="sender-name" />
|
|
|
+ <ww-open-data wx:if="{{msg.sender.type == 3}}" type="userName" openid="{{msg.sender.id}}" class="sender-name" />
|
|
|
+ <span style="font-size:11px;color:#999;margin-left:8px;">{{ msg.displayTime }}</span>
|
|
|
+ </view>
|
|
|
+ <view class="msg-bubble msg-bubble-left">
|
|
|
+ <ww-open-message message-id="{{msg.msgid}}" secret-key="{{msg.secretKey}}" open-type="viewMessage"/>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
</view>
|
|
|
- <view style="display:flex; flex-direction:row; {{msg.sender.type == 1 ? 'justify-content:flex-end;' : 'justify-content:flex-start;'}}">
|
|
|
- <image wx:if="{{msg.sender.type == 2}}" src="{{data.customerAvatar}}" style="width:36px;height:36px;border-radius:4px;margin-right:10px;background:#fff;"></image>
|
|
|
- <view style="max-width:70%;padding:10px 14px;border-radius:8px;word-break:break-all;background:{{msg.sender.type == 1 ? '#95ec69' : '#ffffff'}};box-shadow:0 1px 3px rgba(0,0,0,0.1);display:flex;align-items:center;">
|
|
|
+ <!-- 员工消息(右对齐) -->
|
|
|
+ <view wx:if="{{msg.sender.type == 1}}" style="display:flex; flex-direction:row; justify-content:flex-end; align-items:flex-start;">
|
|
|
+ <view style="display:flex; flex-direction:column; align-items:flex-end; max-width:70%; margin-right:10px;">
|
|
|
+ <view style="display:flex; align-items:center; margin-bottom:4px;">
|
|
|
+ <span style="font-size:11px;color:#999;margin-right:8px;">{{ msg.displayTime }}</span>
|
|
|
+ <ww-open-data type="userName" openid="{{msg.sender.id}}" class="sender-name" />
|
|
|
+ </view>
|
|
|
+ <view class="msg-bubble msg-bubble-right">
|
|
|
+ <ww-open-message message-id="{{msg.msgid}}" secret-key="{{msg.secretKey}}" open-type="viewMessage"/>
|
|
|
+ </view>
|
|
|
+ </view>
|
|
|
+ <ww-open-data type="userAvatar" openid="{{msg.sender.id}}" class="msg-avatar-right" />
|
|
|
+ </view>
|
|
|
+ <!-- 未知类型消息(居中,无头像) -->
|
|
|
+ <view wx:if="{{msg.sender.type != 1 && msg.sender.type != 2 && msg.sender.type != 3}}" style="display:flex; justify-content:center;">
|
|
|
+ <view class="msg-bubble msg-bubble-left">
|
|
|
<ww-open-message message-id="{{msg.msgid}}" secret-key="{{msg.secretKey}}" open-type="viewMessage"/>
|
|
|
</view>
|
|
|
- <image wx:if="{{msg.sender.type == 1}}" src="{{data.defaultStaffAvatar}}" style="width:36px;height:36px;border-radius:4px;margin-left:10px;background:#fff;"></image>
|
|
|
</view>
|
|
|
</view>
|
|
|
- <view wx:if="{{data.hasMore && data.loadingMore}}" style="text-align:center;padding:10px;color:#999;">加载更多...</view>
|
|
|
+ <view wx:if="{{data.hasMore && data.loadingMore}}" style="text-align:center; padding:10px; color:#999;">加载更多...</view>
|
|
|
</scroll-view>
|
|
|
`,
|
|
|
+ style: `
|
|
|
+ .msg-avatar-left {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ .msg-avatar-right {
|
|
|
+ width: 36px;
|
|
|
+ height: 36px;
|
|
|
+ border-radius: 4px;
|
|
|
+ flex-shrink: 0;
|
|
|
+ }
|
|
|
+ .sender-name {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #666;
|
|
|
+ max-width: 120px;
|
|
|
+ overflow: hidden;
|
|
|
+ text-overflow: ellipsis;
|
|
|
+ white-space: nowrap;
|
|
|
+ }
|
|
|
+ .msg-bubble {
|
|
|
+ padding: 10px 14px;
|
|
|
+ border-radius: 8px;
|
|
|
+ word-break: break-all;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ }
|
|
|
+ .msg-bubble-left {
|
|
|
+ background: #ffffff;
|
|
|
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
|
+ }
|
|
|
+ .msg-bubble-right {
|
|
|
+ background: #95ec69;
|
|
|
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
|
|
|
+ }
|
|
|
+ `,
|
|
|
data: {
|
|
|
msgList: this.msgList,
|
|
|
- customerAvatar: this.customerAvatar,
|
|
|
- defaultStaffAvatar: this.defaultStaffAvatar,
|
|
|
hasMore: this.hasMore,
|
|
|
loadingMore: this.loadingMore
|
|
|
},
|