Commit cf20b3e5 by Sixong.Zhu

客服支持批量接待,支持2min内撤回功能

parent ef8432c0
......@@ -131,7 +131,7 @@ export default class ChatContainer extends Vue {
.chat-content-wrap {
display: inline-block;
width: 75%;
height: calc(100% - 60px);
height: 100%;
box-sizing: border-box;
vertical-align: top;
}
......
......@@ -21,15 +21,11 @@
round
size="small"
v-if="!isChatMember"
type="primary"
>我要接待</el-button
>
<el-button
class="button"
@click="exitChat"
round
size="small"
v-if="isChatMember"
>退出会话</el-button
<el-button class="button" @click="showAddMember" round size="small"
>添加客服</el-button
>
<el-button
class="button"
......@@ -37,10 +33,17 @@
round
size="small"
v-if="isChatMember && operatorType > 25"
type="warning"
>结束接待</el-button
>
<el-button class="button" @click="showAddMember" round size="small"
>添加客服</el-button
<el-button
class="button"
@click="exitChat"
round
size="small"
v-if="isChatMember"
type="danger"
>退出会话</el-button
>
<i
v-if="close && isSingleChat"
......@@ -133,17 +136,25 @@ export default class ChatTitle extends Vue {
}
}
private noop() {
return 1;
}
private async exitChat() {
try {
if (+this.operatorType === ChatRole.Default) {
await this._userExitChat();
} else if (+this.operatorType > ChatRole.Default) {
await this._csExitChat();
}
this.hideChat();
} catch (error) {
console.error(error);
}
this.$confirm("确认要退出此会话?")
.then(async () => {
try {
if (+this.operatorType === ChatRole.Default) {
await this._userExitChat();
} else if (+this.operatorType > ChatRole.Default) {
await this._csExitChat();
}
this.hideChat();
} catch (error) {
console.error(error);
}
})
.catch(this.noop);
}
private async startReception() {
......
import { MessageType } from "@/customer-service/model";
export function parserMessage(type: string, rawMsg: string) {
if (!type) return "";
if (!rawMsg) return "";
const msg = JSON.parse(rawMsg);
if (type === "text") {
if (type === MessageType.Text) {
return msg.text;
} else if (type === "image") {
} else if (type === MessageType.Image) {
return `[图片]`;
} else if (type === "file") {
} else if (type === MessageType.File) {
return `[文件]`;
} else if (type === MessageType.Withdraw) {
return `[您撤回了一条消息]`;
} else {
return `[系统自动回复]`;
}
......
......@@ -28,6 +28,7 @@
:data="item"
:shape="shape"
@open="open"
@withdraw="refresh"
/>
</div>
</template>
......@@ -353,6 +354,10 @@ export default class MessageList extends Vue {
}
return v;
}
private refresh(msg: number) {
this.fetchNewMsg();
}
}
</script>
......
<template>
<div
class="message-con d-flex align-items-center"
:class="isMyMessage ? 'my-message flex-row-reverse' : ''"
:class="{
'my-message flex-row-reverse': isMyMessage,
'justify-content-center': isWithdrawMessage,
}"
>
<div class="msg-content" :class="{ 'algin-left': !isMyMessage }">
<div
class="msg-name no-selection"
:class="{ 'algin-left': !isMyMessage }"
v-if="!isWithdrawMessage"
>
{{ userName }}
</div>
......@@ -85,6 +89,12 @@
>
<video-player-icon @click.native="openFile"></video-player-icon>
</div>
<div
class="msg-detail withdraw-message"
v-else-if="messageType === 'withdraw'"
>
您撤回了一条消息
</div>
<!-- Text -->
<div
class="msg-detail inline-text"
......@@ -115,7 +125,7 @@
></i>
<i class="el-icon-loading" v-else-if="isSendingMessage"></i>
<template v-if="showReadSummary">
<template v-if="showReadSummary && !isWithdrawMessage">
<div v-if="isMyMessage" class="msg-read pos-rel">
<span
@click="readListVisibility = true"
......@@ -135,6 +145,13 @@
/>
</div>
</template>
<span
class="withdraw"
v-if="isMyMessage && canWithdraw && !isWithdrawMessage"
@click="withdraw"
>撤回此消息</span
>
</div>
</template>
......@@ -163,6 +180,10 @@ import WhoReadList from "./who-read-list.vue";
import avatar from "@/customer-service/components/avatar.vue";
import { chatStore, ChatStore } from "@/customer-service/store/model";
import ximInstance from "../xim/xim";
import { dbController } from "../database";
const twoMinutes = 2 * 60 * 1000;
@Component({
components: { FileIcon, VoiceIcon, WhoReadList, VideoPlayerIcon, avatar },
......@@ -180,6 +201,9 @@ export default class Message extends Mixins(Filters) {
@chatStore.State(ChatStore.STATE_CHAT_CURRENT_CHAT_ID)
private readonly chatId!: ChatStore.STATE_CHAT_CURRENT_CHAT_ID;
@chatStore.Mutation(ChatStore.MUTATION_WITHDRAW)
private readonly executeWithDraw: ChatStore.MUTATION_WITHDRAW;
/**
* tbd: 文件消息所在的域名的url,逻辑需要补充
*/
......@@ -213,6 +237,17 @@ export default class Message extends Mixins(Filters) {
private org = "";
private get canWithdraw() {
if (this.data) {
return new Date().valueOf() - this.data.ts * 1000 < twoMinutes;
}
return false;
}
private get isWithdrawMessage() {
return this.data.type === dto.MessageType.Withdraw;
}
private get isAllRead() {
return this.data.read_count >= this.data.total_read_count;
}
......@@ -431,6 +466,16 @@ export default class Message extends Mixins(Filters) {
this.fileFailed2Load = true;
this.messageRealUrl = "";
}
private withdraw() {
ximInstance
.withdraw(this.chatId, this.data.id)
.then(() => {
this.executeWithDraw(this.data.id);
dbController.removeMessage(this.chatId, this.data.id);
})
.finally(() => this.$emit("withdraw", this.data.id));
}
}
</script>
......@@ -438,6 +483,7 @@ export default class Message extends Mixins(Filters) {
.message-con {
margin: 20px 0;
margin-right: 15px;
position: relative;
&.my-message {
.msg-avatar {
......@@ -471,6 +517,13 @@ export default class Message extends Mixins(Filters) {
background-color: #000;
border-radius: 0;
}
&.withdraw-message {
background-color: transparent;
padding: 0 4px;
font-size: 12px;
color: #999;
}
}
.msg-read {
......@@ -487,6 +540,22 @@ export default class Message extends Mixins(Filters) {
margin-left: 0;
margin-top: 0;
}
.withdraw {
color: #999;
position: absolute;
bottom: -18px;
right: 0;
font-size: 12px;
display: none;
cursor: pointer;
}
&:hover {
.withdraw {
display: inline-block;
}
}
}
> i {
......
......@@ -175,6 +175,11 @@ class ChatCacheDatabaseController {
});
}
public removeMessage(chat: number, msg: number) {
const store = this.buildChatMessageStore(chat);
store.delete(msg);
}
public mergeChatList(source1: Chat[], source2: Chat[]) {
for (const item of source2) {
const t = source1.find((i) => i.id === item.id);
......
......@@ -58,6 +58,16 @@ export type ChatListRequestList = {
total: number;
};
export const enum MessageType {
Text = "text",
Image = "image",
File = "file",
Video = "video",
Voice = "voice",
GeneralOrderMsg = "general_order_msg",
Withdraw = "withdraw",
}
export interface Message {
at_id: string;
chat_id: number;
......@@ -75,7 +85,7 @@ export interface Message {
status: number;
total_read_count: number;
ts: number;
type: "text" | "image" | "file" | "video" | "voice" | "general_order_msg";
type: MessageType;
update_time: number;
url: string;
}
......
......@@ -347,6 +347,14 @@ export default {
) => {
Vue.set(state[ChatStore.STATE_CHAT_USERNAME], param.id, param.name);
},
[ChatStore.MUTATION_WITHDRAW]: (state, id: number) => {
const old = state[ChatStore.STATE_CHAT_MSG_HISTORY] || [];
const chatid = state[ChatStore.STATE_CHAT_CURRENT_CHAT_ID];
if (chatid == null) return;
state[ChatStore.STATE_CHAT_MSG_HISTORY] = old.filter(
(i) => i.id !== id
);
},
},
actions: {
async [ChatStore.ACTION_GET_MY_CHAT_LIST]({ commit }) {
......
......@@ -170,6 +170,9 @@ export namespace ChatStore {
export const MUTATION_CLEAR_CURRENT_CHAT_UNIPLAT_ID = "清空chat uniplat id";
export type MUTATION_CLEAR_CURRENT_CHAT_UNIPLAT_ID = () => void;
export const MUTATION_WITHDRAW = "撤回";
export type MUTATION_WITHDRAW = (id: number) => void;
export const MUTATION_SAVE_MYSELF_ID =
"保存我的id:聊天窗口显示在右边那个人的id";
export type MUTATION_SAVE_MYSELF_ID = () => void;
......
......@@ -291,6 +291,16 @@ export class Xim {
return this;
}
public async withdraw(chat: number, msg: number) {
this.checkConnected();
if (this.client == null) {
throw new Error("client shouldn't undefined");
}
return this.client
.withdrawMsg(chatType, chat, msg)
.then((r) => r.args[0]);
}
/**
* 移除会话(用户端/移动端使用)
*/
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment