提交 a21e58e2 作者: 施汉文

feat: 客服样式

上级 869d9cf1
<!DOCTYPE html>
<html lang="">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<link rel="icon" href="<%= BASE_URL %>favicon.ico" />
<!-- HTTP 1.1 -->
<meta http-equiv="pragma" content="no-cache">
<meta http-equiv="pragma" content="no-cache" />
<!-- HTTP 1.0 -->
<meta http-equiv="cache-control" content="no-cache">
<meta http-equiv="cache-control" content="no-cache" />
<!-- Prevent caching at the proxy server -->
<meta http-equiv="expires" content="0">
<meta http-equiv="expires" content="0" />
<meta http-equiv="X-UA-Compatible" content="IE=EmulateIE9" />
<!-- <title><%= htmlWebpackPlugin.options.title %></title> -->
<title>企微侧边栏</title>
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0,shrink-to-fit=no,user-scalable=no"> -->
<script src="https://g.alicdn.com/dingding/dinglogin/0.0.5/ddLogin.js"></script>
<!-- iconpark CDN链接 -->
<script src="https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/icons_27278_168.abab0e7be76224e5929cf4315f14d58c.es5.js"></script>
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
<strong
>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work
properly without JavaScript enabled. Please enable it to
continue.</strong
>
</noscript>
<div id="app"></div>
</body>
......
<template>
<div id="app" class="mobile-app-wrapper">
<Debug />
<div class="mobile-menu-bar" v-if="token && external_userid && showMemberId">
<div class="mobile-content">
<router-view></router-view>
</div>
<div
class="mobile-menu-bar w-[40px] overflow-hidden"
v-if="token && external_userid && showMemberId"
>
<!-- 临时调试信息 -->
<div class="menu-container">
<el-menu :default-active="selectedPath" mode="horizontal" class="mobile-el-menu"
:class="{ 'collapsed': !isMenuExpanded && shouldShowToggle }" background-color="#fff" router
@select="handleSelect" ref="menuRef">
<el-menu-item v-for="item in menuList" :key="item.path" :index="item.path" class="mobile-menu-item">
<el-menu
:default-active="selectedPath"
class="mobile-el-menu"
router
@select="handleSelect"
ref="menuRef"
active-text-color="#fff"
text-color="#B0B2B5"
>
<el-menu-item
v-for="item in menuList"
:key="item.path"
:index="item.path"
class="mobile-menu-item rounded-full"
style="padding: 0; margin: 8px 0; height: 24px; width: 24px"
>
<div
class="w-[24px] h-[24px] rounded-full bg-[#F2F3F5] flex items-center justify-center"
>
<!-- 任务列表菜单项显示红点 -->
<div v-if="item.path === '/taskList' && hasTaskRedDot" class="menu-item-with-badge">
<div class="task-badge">
<el-badge is-dot>
<span>{{ item.label }}</span>
<div
v-if="
(item.path === '/taskList' && hasTaskRedDot) ||
item.path === '/agentStatusManagement'
"
class="menu-item-with-badge"
>
<el-tooltip
class="item"
effect="dark"
:disabled="item.path !== '/agentStatusManagement'"
:content="'客服状态:' + csStatusInfo.text"
placement="left"
>
<div
class="task-badge"
:class="{
'agent-status-management':
item.path === '/agentStatusManagement',
}"
>
<el-badge is-dot :type="csStatusInfo.type">
<!-- <span>{{ item.label }}</span> -->
<iconpark-icon
class="w-[14px] h-[14px]"
:name="item.icon"
></iconpark-icon>
</el-badge>
</div>
</el-tooltip>
</div>
<!-- 普通菜单项 -->
<span v-else>{{ item.label }}</span>
<!-- <span >{{ item.label }}</span> -->
<iconpark-icon
v-else
class="w-[14px] h-[14px]"
:name="item.icon"
></iconpark-icon>
</div>
</el-menu-item>
</el-menu>
<!-- 展开收起按钮 -->
<el-button type="text" size="mini" v-if="shouldShowToggle" class="menu-toggle-btn" @click="toggleMenu">
<i :class="isMenuExpanded ? 'el-icon-arrow-up' : 'el-icon-arrow-down'"></i>
<span>{{ isMenuExpanded ? '收起' : '展开' }}</span>
</el-button>
</div>
<!-- 绑定的 w 账号 -->
<bindUserList />
</div>
<div class="mobile-content">
<router-view></router-view>
</div>
</div>
</template>
<script>
import bindUserList from '@/views/components/bindGameAccount/bindUserList.vue'
import { getToken } from '@/utils/auth'
import { mapState, mapMutations, mapActions } from 'vuex'
import Cookies from 'js-cookie'
import { getParams } from '@/utils/index'
import Debug from '@/components/debug.vue'
import bindUserList from "@/views/components/bindGameAccount/bindUserList.vue";
import { getToken } from "@/utils/auth";
import { mapState, mapMutations, mapActions } from "vuex";
import Cookies from "js-cookie";
import { getParams } from "@/utils/index";
import Debug from "@/components/debug.vue";
export default {
name: 'App',
name: "App",
components: {
bindUserList,
Debug
Debug,
},
data() {
return {
menuList: [
{
label: '客户资料',
path: '/userInfo'
label: "客服",
path: "/agentStatusManagement",
icon: "gongnengicon-zaixiankefu",
},
{
label: "客户资料",
path: "/userInfo",
icon: "gongnengicon-kehuziliao",
},
// {
// label: '角色信息',
......@@ -66,16 +114,18 @@ export default {
// path: '/orderList'
// },
{
label: '快捷回复',
path: '/quickReply'
label: "快捷回复",
path: "/quickReply",
icon: "gongnengicon-kuaijiehuifu",
},
// {
// label: '违规记录',
// path: '/violationRecord'
// },
{
label: '礼包记录',
path: '/giftRecord'
label: "礼包记录",
path: "/giftRecord",
icon: "gongnengicon-libaojilu",
},
// {
......@@ -83,188 +133,217 @@ export default {
// path: '/taskRecord'
// },
{
label: '申请记录',
path: '/applyRecord'
label: "申请记录",
path: "/applyRecord",
icon: "gongnengicon-shenqingjilu",
},
{
label: '通讯录',
path: '/addressBook'
label: "通讯录",
path: "/addressBook",
icon: "gongnengicon-tongxunlu",
},
{
label: '快捷发送',
path: '/quickSendGame'
label: "快捷发送",
path: "/quickSendGame",
icon: "gongnengicon-kuaijiefasong",
},
{
label: '任务列表',
path: '/taskList',
hasRedDot: false // 红点状态
label: "任务列表",
path: "/taskList",
hasRedDot: false, // 红点状态
icon: "gongnengicon-renwuliebiao",
},
{
label: '微言助手',
path: '/aiChat'
label: "微言助手",
path: "/aiChat",
icon: "gongnengicon-weiyanzhushou",
},
// {
// label: '通讯录',
// path: '/addressBook'
// },
],
selectedPath: '/userInfo',
selectedPath: "/userInfo",
showMemberId: false,
isMenuExpanded: false, // 菜单展开状态
shouldShowToggle: false, // 是否显示展开收起按钮
}
};
},
computed: {
...mapState('user', ['external_userid', 'token', 'userInfo']),
...mapState('game', ['taskData']),
...mapState("user", [
"external_userid",
"token",
"userInfo",
"client_online_status",
]),
...mapState("game", ["taskData"]),
// 计算任务列表是否需要显示红点
hasTaskRedDot() {
return this.taskData.user_task > 0 || this.taskData.account_task > 0
}
return this.taskData.user_task > 0 || this.taskData.account_task > 0;
},
//客服状态信息
csStatusInfo() {
const statusMap = {
online: {
text: "在线",
type: "success",
},
offline: {
text: "离线",
type: "danger",
},
rest: {
text: "休息中",
type: "warning",
},
};
return statusMap[this.client_online_status] || "未知";
},
},
watch: {
'$route.path'(val) {
"$route.path"(val) {
// 处理各种可能的路径情况
if (val === '/' || val === '/index.html') {
this.selectedPath = '/userInfo'
if (val === "/" || val === "/index.html") {
this.selectedPath = "/userInfo";
} else {
this.selectedPath = val
this.selectedPath = val;
}
console.log('路由变化:', val, '选中路径:', this.selectedPath)
console.log("路由变化:", val, "选中路径:", this.selectedPath);
},
// 监听用户信息变化,只在初始化时获取一次任务数据
userInfo: {
handler(newVal, oldVal) {
if (newVal && newVal.id && (!oldVal || !oldVal.id) && this.token) {
console.log('用户信息初始化完成,获取任务数据:', newVal)
console.log("用户信息初始化完成,获取任务数据:", newVal);
// 只在用户信息第一次设置时获取任务数据
this.getTaskUnReadData()
this.getTaskUnReadData();
}
},
deep: true,
immediate: true
immediate: true,
},
// 监听 external_userid 的变化,确保界面及时更新
external_userid: {
handler(newVal) {
if (newVal) {
this.$nextTick(() => {
this.showMemberId = true
console.log('external_userid 已设置:', newVal, window.location.href, this.token, Cookies.get('token'))
this.showMemberId = true;
console.log(
"external_userid 已设置:",
newVal,
window.location.href,
this.token,
Cookies.get("token")
);
// 强制更新组件
this.$forceUpdate()
this.$forceUpdate();
// 检查是否需要显示展开收起按钮
this.checkMenuOverflow()
})
this.checkMenuOverflow();
});
}
},
immediate: true
}
immediate: true,
},
},
created() {
const urlParams = getParams();
// 每次进入页面都缓存corp_id
if (urlParams.corp_id) {
this.cacheCorp_id(urlParams.corp_id) // 缓存 corp_id
this.cacheCorp_id(urlParams.corp_id); // 缓存 corp_id
}
},
mounted() {
// 页面刷新时从 Cookie 恢复 token 到 store
const cookieToken = Cookies.get('token')
const cookieToken = Cookies.get("token");
if (cookieToken && !this.token) {
this.set_token(cookieToken)
console.log('从 Cookie 恢复 token:', cookieToken)
this.set_token(cookieToken);
console.log("从 Cookie 恢复 token:", cookieToken);
}
// 初始化时处理路径
const currentPath = this.$route.path
if (currentPath === '/' || currentPath === '' || currentPath === '/index.html') {
this.selectedPath = '/userInfo'
const currentPath = this.$route.path;
if (
currentPath === "/" ||
currentPath === "" ||
currentPath === "/index.html"
) {
this.selectedPath = "/userInfo";
} else {
this.selectedPath = currentPath
this.selectedPath = currentPath;
}
console.log('创建时路径:', currentPath, '选中路径:', this.selectedPath)
console.log("创建时路径:", currentPath, "选中路径:", this.selectedPath);
// 监听窗口大小变化
window.addEventListener('resize', this.handleResize)
window.addEventListener("resize", this.handleResize);
// 初始检查
this.$nextTick(() => {
this.checkMenuOverflow()
this.initVuexValue()
})
this.checkMenuOverflow();
this.initVuexValue();
});
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize)
window.removeEventListener("resize", this.handleResize);
},
methods: {
...mapMutations('user', ['set_userid', 'set_corp_id', 'set_token', 'set_cser_info', 'set_cser_id', 'set_cser_name', 'set_userInfo']),
...mapMutations('game', ['set_accountSelect']),
...mapActions('game', ['getTaskUnReadData']),
...mapMutations("user", [
"set_userid",
"set_corp_id",
"set_token",
"set_cser_info",
"set_cser_id",
"set_cser_name",
"set_userInfo",
]),
...mapMutations("game", ["set_accountSelect"]),
...mapActions("game", ["getTaskUnReadData"]),
// 设置缓存
cacheCorp_id(corp_id) {
Cookies.set('corp_id', corp_id, { expires: 30 })
this.set_corp_id(corp_id)
Cookies.set("corp_id", corp_id, { expires: 30 });
this.set_corp_id(corp_id);
},
initVuexValue() {
this.set_userid(Cookies.get('userid'))
this.set_corp_id(Cookies.get('corp_id'))
this.set_token(Cookies.get('token'))
this.set_cser_id(Cookies.get('cser_id'))
this.set_cser_name(Cookies.get('cser_name'))
this.set_userid(Cookies.get("userid"));
this.set_corp_id(Cookies.get("corp_id"));
this.set_token(Cookies.get("token"));
this.set_cser_id(Cookies.get("cser_id"));
this.set_cser_name(Cookies.get("cser_name"));
const userinfo = {
cser_id: Cookies.get('cser_id'),
cser_name: Cookies.get('cser_name'),
username: Cookies.get('cser_name'),
id: Cookies.get('cser_id'),
}
this.set_userInfo(userinfo)
const cser_info = Cookies.get('cser_info')
cser_info ? this.set_cser_info(JSON.parse(cser_info)) : this.set_cser_info({})
cser_id: Cookies.get("cser_id"),
cser_name: Cookies.get("cser_name"),
username: Cookies.get("cser_name"),
id: Cookies.get("cser_id"),
};
this.set_userInfo(userinfo);
const cser_info = Cookies.get("cser_info");
cser_info
? this.set_cser_info(JSON.parse(cser_info))
: this.set_cser_info({});
},
handleSelect(key, keyPath) {
console.log('菜单选择:', key, keyPath, window.location.href)
},
// 切换菜单展开收起状态
toggleMenu() {
this.isMenuExpanded = !this.isMenuExpanded
console.log("菜单选择:", key, keyPath, window.location.href);
},
// 检查菜单是否需要换行
checkMenuOverflow() {
this.$nextTick(() => {
const menuElement = this.$refs.menuRef?.$el
if (!menuElement) return
const menuElement = this.$refs.menuRef?.$el;
if (!menuElement) return;
// 临时展开菜单来检查实际高度
const wasCollapsed = !this.isMenuExpanded && this.shouldShowToggle
if (wasCollapsed) {
menuElement.classList.remove('collapsed')
}
const menuHeight = menuElement.scrollHeight
const singleLineHeight = 50 // 单行高度
const menuHeight = menuElement.scrollHeight;
const singleLineHeight = 50; // 单行高度
// 如果菜单高度超过单行,说明需要换行
this.shouldShowToggle = menuHeight > singleLineHeight + 10 // 加10px容错
// 如果不需要展开收起按钮,则直接展开
if (!this.shouldShowToggle) {
this.isMenuExpanded = true
} else if (wasCollapsed) {
// 恢复收起状态
menuElement.classList.add('collapsed')
}
})
this.shouldShowToggle = menuHeight > singleLineHeight + 10; // 加10px容错
});
},
// 窗口大小变化处理
handleResize() {
clearTimeout(this.resizeTimer)
clearTimeout(this.resizeTimer);
this.resizeTimer = setTimeout(() => {
this.checkMenuOverflow()
}, 150)
}
this.checkMenuOverflow();
}, 150);
},
}
},
};
</script>
<style scoped>
......@@ -280,7 +359,6 @@ export default {
background: #f0f2f5;
min-height: 100vh;
display: flex;
flex-direction: column;
box-shadow: 0 0 12px rgba(0, 0, 0, 0.04);
}
......@@ -293,37 +371,43 @@ export default {
.menu-container {
position: relative;
padding-right: 30px;
}
.mobile-el-menu {
border: none;
background: #fff;
display: flex;
justify-content: flex-start;
padding-left: 16px;
justify-content: center;
/* padding-left: 16px; */
flex-wrap: wrap;
transition: all 0.3s ease;
overflow: hidden;
}
/* 收起状态:只显示第一行 */
.mobile-el-menu.collapsed {
/* .mobile-el-menu.collapsed {
max-height: 35px;
overflow: hidden;
}
} */
.mobile-menu-item {
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
padding: 0 16px !important;
/* padding: 0 16px !important; */
min-width: 0;
flex: none;
width: auto;
height: 40px;
max-width: 120px;
text-align: center;
margin-right: 8px;
transition: all 0.3s ease;
}
.el-menu-item,
.el-submenu__title {
line-height: auto;
}
.mobile-el-menu .el-menu-item.is-active {
font-weight: bold;
......@@ -342,7 +426,10 @@ export default {
transition: all 0.3s ease;
z-index: 20;
}
.el-dropdown-menu__item,
.el-menu-item {
padding: 0;
}
.menu-toggle-btn i {
font-size: 12px;
}
......@@ -352,14 +439,14 @@ export default {
overflow-y: auto;
}
.mobile-content>div {
.mobile-content > div {
background: #fff;
border-radius: 8px;
min-height: 60vh;
padding: 10px;
}
.el-menu--horizontal>.el-menu-item {
.el-menu--horizontal > .el-menu-item {
height: 35px;
line-height: 35px;
}
......@@ -370,16 +457,27 @@ body {
/* 任务列表菜单项红点样式 */
.menu-item-with-badge {
display: inline-block;
/* display: inline-block; */
}
.mobile-el-menu .el-menu-item.is-active > div {
background: #267ef0;
}
.task-badge {
::v-deep .el-badge__content.is-dot {
top: 8px !important;
right: -5px !important;
top: 16px !important;
right: 3px !important;
}
}
.agent-status-management {
::v-deep .el-badge__content.is-dot {
top: 38px !important;
right: 2px !important;
}
}
.el-badge {
display: block;
}
/* 确保菜单项内容居中 */
.mobile-menu-item .menu-item-with-badge {
display: flex;
......
import Vue from 'vue'
import VueRouter from 'vue-router'
import userInfo from '../views/userInfo/userInfo.vue'
import agentStatusManagement from '../views/agentStatusManagement/index.vue'
import quickReply from '../views/quickReply.vue'
import giftRecord from '../views/giftRecord.vue'
import applyRecord from '../views/applyRecord.vue'
......@@ -27,6 +28,11 @@ const routes = [
redirect: '/userInfo'
},
{
path: '/agentStatusManagement',
name: 'agentStatusManagement',
component: agentStatusManagement
},
{
path: '/userInfo',
name: 'userInfo',
component: userInfo
......
......@@ -11,8 +11,10 @@ const state = {
"agent_signature": "05a47ef848266c48ff28f52e7749ba8b70adcc14",
"corp_signature": "29e61720786b3c6dac31f1041c70878b4819842e",
"time": 1747726636,
external_userid:''
external_userid:'',
},
avatar:'',//客服头像
userid:Cookies.get('userid'),
corp_id:'',
external_userid:'',
......@@ -40,6 +42,9 @@ const mutations = {
set_userInfo(state,userInfo){
state.userInfo = userInfo
},
set_avatar(state,avatar){
state.avatar = avatar
},
set_userid(state,userid){
state.userid = userid
},
......
......@@ -14,6 +14,33 @@ body {
font-size: 14px;
margin: 0 ;
padding: 0;
// 设置全局滚动条不占位置
overflow: overlay;
}
// 全局滚动条样式
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: transparent;
}
::-webkit-scrollbar-thumb {
background-color: rgba(144, 147, 153, 0.3);
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background-color: rgba(144, 147, 153, 0.5);
}
// Firefox 滚动条样式
* {
scrollbar-width: thin;
scrollbar-color: rgba(144, 147, 153, 0.3) transparent;
}
label {
......
<template>
<div class="p-3 h-full">
<div class="text-[13px] text-[#131920] font-medium">在线客服</div>
<div class="px-[9px] pt-[60px] flex flex-col items-center justify-center">
<div class="h-[80px] w-[80px]">
<img :src="avatar" alt="" class="h-full w-full rounded-[6px]" />
</div>
<div class="text-[14px] mt-[8px] text-black">
{{ userInfo.username }}
</div>
<div
v-if="clientStatus === 'rest'"
class="mt-[8px] text-[12px] flex items-center justify-center text-[#FA8F25] bg-[rgba(250,143,37,0.1)] px-[8px] rounded-full"
>
<div class="h-[6px] w-[6px] rounded-full bg-[#FA8F25] mr-[4px]"></div>
{{ clientStatusText }}
</div>
<div
v-else
class="mt-[8px] text-[12px] flex items-center justify-center text-[#26BF4C] bg-[rgba(38,191,76,0.1)] px-[8px] rounded-full"
>
<div class="h-[6px] w-[6px] rounded-full bg-[#26BF4C] mr-[4px]"></div>
{{ clientStatusText }}
</div>
<div class="mt-[32px] w-full space-y-[12px] space-x-0">
<el-button
plain
class="!w-full !h-[40px]"
@click="logout"
v-if="clientStatus !== 'offline'"
:loading="logoutLoading"
>
<div class="flex items-center justify-center">
<iconpark-icon
name="kefucaozuo-xiaxian"
class="mr-[8px] text-[14px]"
></iconpark-icon>
下线
</div>
</el-button>
<el-tooltip
effect="dark"
:content="'午休或者临时有事可点击休息'"
placement="top"
v-if="clientStatus === 'online'"
>
<el-button
plain
class="!w-full !h-[40px]"
@click="handleStartRest"
:loading="startRestLoading"
>
<div class="flex items-center justify-center">
<iconpark-icon
name="kefucaozuo-xiuxi"
class="mr-[8px] text-[14px]"
></iconpark-icon>
开始休息
</div>
</el-button>
</el-tooltip>
<el-button
type="primary"
class="!w-full !h-[40px]"
@click="handleFinishRest"
v-if="clientStatus === 'rest'"
:loading="finishRestLoading"
>
<div class="flex items-center justify-center">
<iconpark-icon
name="kefucaozuo-xiuxi"
class="mr-[8px] font-bold text-[14px]"
></iconpark-icon>
结束休息
</div>
</el-button>
<el-button
plain
class="!w-full !h-[40px]"
@click="handleSendComment"
:loading="sendCommentLoading"
>
<div class="flex items-center justify-center">
<iconpark-icon
name="kefucaozuo-pingjia"
class="mr-[8px] text-[14px]"
></iconpark-icon>
发送评价
</div>
</el-button>
</div>
</div>
</div>
</template>
<script>
import { mapState, mapMutations, mapActions } from "vuex";
import {
getClientStatus,
finishRest,
client_session_rest,
sendComment,
logout,
} from "@/api/user.js";
import { sendChatMessage } from "@/utils/index.js";
import { getToken, removeToken } from "@/utils/auth";
import Cookies from "js-cookie";
export default {
name: "AgentStatusManagement",
data() {
return {
logoutLoading: false,
startRestLoading: false,
finishRestLoading: false,
sendCommentLoading: false,
};
},
computed: {
...mapState("user", [
"userInfo",
"avatar",
"client_online_status",
"corp_id",
"external_userid",
"userid",
"token",
]),
// 客服状态文本
clientStatusText() {
const statusMap = {
online: "在线",
offline: "离线",
rest: "休息中",
};
return statusMap[this.client_online_status] || "未知";
},
// 客服状态
clientStatus() {
console.log(this.client_online_status, 123131231);
return this.client_online_status;
},
},
mounted() {
// 获取客服状态和相关信息
this.$nextTick(() => {
if (this.userid && this.token) {
this.getInitialData();
}
});
},
methods: {
...mapMutations("user", ["set_client_online_status"]),
...mapActions("user", ["initWecom"]),
// 获取初始数据
async getInitialData() {
console.log(this.userInfo, 1111111);
// 如果userInfo.id存在,说明已经获取过客服状态,直接返回
if (this.userInfo.id) return;
try {
// 获取客服休息状态
const statusRes = await getClientStatus();
if (statusRes.status_code === 1) {
if (statusRes.data.client_online_status === "offline") {
removeToken();
window.location.href =
window.location.origin +
"/company_app/index.html?corp_id=" +
this.corp_id;
}
console.log(statusRes.data, 123131231);
this.set_client_online_status(statusRes.data.client_online_status);
}
} catch (error) {
console.error("获取初始数据失败:", error);
}
},
// 下线确认
logout() {
if (this.client_online_status === "rest") {
this.$message({
type: "error",
message: "当前客服号处于休息状态,不能下线",
});
return;
}
this.$confirm("确定下线吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.userLogout();
})
.catch(() => {
this.$message({
type: "info",
message: "已取消",
});
});
},
// 执行下线
async userLogout() {
this.logoutLoading = true;
try {
const data = {
userid: this.userid,
};
const res = await logout(data);
if (res.status_code === 1) {
this.$message({
type: "success",
message: "下线成功",
});
removeToken();
Cookies.remove("external_userid");
Cookies.remove("userid");
window.location.href =
window.location.origin +
"/company_app/index.html?corp_id=" +
this.corp_id;
} else {
this.$message({
type: "error",
message: "下线失败",
});
}
} finally {
this.logoutLoading = false;
}
},
// 开始休息
async handleStartRest() {
this.startRestLoading = true;
try {
const res = await client_session_rest();
if (res.status_code === 1) {
this.set_client_online_status("rest");
this.$message.success("已开始休息");
} else {
this.$message.error(res.msg || "开始休息失败");
}
} catch (error) {
console.error("开始休息失败:", error);
this.$message.error("开始休息失败");
} finally {
this.startRestLoading = false;
}
},
// 结束休息
async handleFinishRest() {
this.finishRestLoading = true;
try {
const res = await finishRest();
if (res.status_code === 1) {
this.set_client_online_status("online");
this.$message.success("已结束休息");
} else {
this.$message.error(res.msg || "结束休息失败");
}
} catch (error) {
console.error("结束休息失败:", error);
this.$message.error("结束休息失败");
} finally {
this.finishRestLoading = false;
}
},
// 发送评价
async handleSendComment() {
this.sendCommentLoading = true;
try {
const res = await sendComment({
corp_id: this.corp_id,
external_userid: this.external_userid,
userid: this.userid,
});
if (res.status_code === 1 && res.data.news) {
// 使用企业微信JSSDK发送评价
const result = await sendChatMessage(res.data.news, "link");
if (result.success) {
this.$message.success("评价已发送");
} else {
this.$message.error("评价发送失败");
}
}
} catch (error) {
console.error("发送评价失败:", error);
this.$message.error("发送评价失败");
} finally {
this.sendCommentLoading = false;
}
},
},
};
</script>
<template>
<div class="bindUserList rowFlex columnCenter">
<div class="select">
<el-select v-model="bindAccount" placeholder="请选择关联账号" :clearable="false" @change="handleChange">
<el-select
v-model="bindAccount"
placeholder="请选择关联账号"
:clearable="false"
@change="handleChange"
>
<el-option label="新增关联账号" value="add" @click="addNewUser">
</el-option>
<el-option v-for="(item, index) in bindGameUserList" :key="index" :label="item.username"
:value="item.member_id">
<el-option
v-for="(item, index) in bindGameUserList"
:key="index"
:label="item.username"
:value="item.member_id"
>
<div class="rowFlex columnCenter">
<p class="text">{{ item.status_name
<p class="text">
{{
item.status_name
? `${item.username}/${item.status_name}`
: item.username}}</p>
<span v-if="item.account_type==2" class="account_type" style="color: red;font-weight: bold;"> ()</span>
: item.username
}}
</p>
<span
v-if="item.account_type == 2"
class="account_type"
style="color: red; font-weight: bold"
>
()</span
>
</div>
</el-option>
</el-select>
</div>
<p v-if="bindGameUserList.length > 0" class="num">
......@@ -29,116 +47,122 @@
</template>
<script type="text/javascript">
import { detailsInfoRequest } from '@/api/works'
import {memberView} from '@/api/game'
import { logout } from '@/api/user'
import { mapState, mapMutations, mapActions } from 'vuex'
import addUser from './addUser.vue'
import { getToken,removeToken } from '@/utils/auth'
import { detailsInfoRequest } from "@/api/works";
import { memberView } from "@/api/game";
import { logout } from "@/api/user";
import { mapState, mapMutations, mapActions } from "vuex";
import addUser from "./addUser.vue";
import { getToken, removeToken } from "@/utils/auth";
// 更新代码
export default {
name: 'bindUserList',
name: "bindUserList",
components: {
addUser
addUser,
},
data() {
return {
showLayer: false,
accountValue: '',
bindAccount:'',
memberCheckList:[], // 自定义列
}
accountValue: "",
bindAccount: "",
memberCheckList: [], // 自定义列
};
},
computed: {
...mapState('game', [
'bindGameUserList',
'accountSelect',
'gameUserInfo'
]),
...mapState('user', [
'userid',
'corp_id',
'external_userid',
'client_online_status'
...mapState("game", ["bindGameUserList", "accountSelect", "gameUserInfo"]),
...mapState("user", [
"userid",
"corp_id",
"external_userid",
"client_online_status",
]),
},
watch: {
accountSelect(newVal,oldVal) {
if(newVal){
console.log(newVal,'hahhaha')
this.gameMemberView()
this.bindAccount = newVal
}
accountSelect(newVal, oldVal) {
if (newVal) {
console.log(newVal, "hahhaha");
this.gameMemberView();
this.bindAccount = newVal;
}
},
},
async mounted() {
this.bindUserList()
this.requestDetails()
this.gameMemberView()
this.bindUserList();
this.requestDetails();
this.gameMemberView();
},
methods: {
...mapMutations('game', [
'set_accountSelect',
'set_chatUserInfo',
'set_gameUserInfo',
'set_viewLoading'
...mapMutations("game", [
"set_accountSelect",
"set_chatUserInfo",
"set_gameUserInfo",
"set_viewLoading",
"set_avatar",
]),
...mapActions('game', ['gameBindUser']),
...mapMutations("user", ["set_avatar"]),
...mapActions("game", ["gameBindUser"]),
handleChange(value) {
if (value == 'add') {
this.showLayer = true
if (value == "add") {
this.showLayer = true;
} else {
this.set_accountSelect(value)
this.set_accountSelect(value);
}
},
close(){
this.bindAccount = this.accountSelect
close() {
this.bindAccount = this.accountSelect;
},
async gameMemberView(item) {
if (this.accountSelect && this.accountSelect !== '') {
this.set_viewLoading(true)
this.set_gameUserInfo({})
await this.$nextTick()
const data = { member_id: this.accountSelect, need_channel: 1, need_roleInfo: 1, need_banned: 1 }
if (this.accountSelect && this.accountSelect !== "") {
this.set_viewLoading(true);
this.set_gameUserInfo({});
await this.$nextTick();
const data = {
member_id: this.accountSelect,
need_channel: 1,
need_roleInfo: 1,
need_banned: 1,
};
try {
const res = await memberView(data)
this.set_viewLoading(false)
const res = await memberView(data);
this.set_viewLoading(false);
if (res.status_code === 1) {
this.set_gameUserInfo(res.data)
this.set_gameUserInfo(res.data);
} else {
this.set_gameUserInfo({})
this.set_gameUserInfo({});
}
} catch (error) {
this.set_viewLoading(false)
this.set_gameUserInfo({})
this.set_viewLoading(false);
this.set_gameUserInfo({});
}
}
},
addNewUser() {
console.log(11)
console.log(11);
},
async requestDetails() {
const data = {
userid: this.userid,
external_userid: this.external_userid
}
const res = await detailsInfoRequest(data)
external_userid: this.external_userid,
};
const res = await detailsInfoRequest(data);
if (res.data && res.data.userid) {
console.log(res.data,'1231')
this.chatUserDetails = res.data
this.set_chatUserInfo(this.chatUserDetails) // 设置云端信息
console.log(this.chatUserDetails,'哈哈哈')
if (this.chatUserDetails.self_defined_columns && this.chatUserDetails.self_defined_columns.length > 0) {
this.memberCheckList =
this.chatUserDetails.self_defined_columns.map(
this.set_avatar(res.data.user.avatar);
console.log(res.data, "1231");
this.chatUserDetails = res.data;
this.set_chatUserInfo(this.chatUserDetails); // 设置云端信息
console.log(this.chatUserDetails, "哈哈哈");
if (
this.chatUserDetails.self_defined_columns &&
this.chatUserDetails.self_defined_columns.length > 0
) {
this.memberCheckList = this.chatUserDetails.self_defined_columns.map(
(item) => item.name
)
);
} else {
this.memberCheckList = []
this.memberCheckList = [];
}
} else {
this.chatUserDetails = {}
this.chatUserDetails = {};
}
// 获取到用户的详情 储存在缓存里面
},
......@@ -146,20 +170,19 @@ export default {
async bindUserList() {
const data = {
userid: this.userid,
external_userid: this.external_userid
}
const res = await this.gameBindUser(data)
external_userid: this.external_userid,
};
const res = await this.gameBindUser(data);
if (res.length > 0) {
this.set_accountSelect(res[0].member_id)
this.bindAccount = res[0].member_id
this.set_accountSelect(res[0].member_id);
this.bindAccount = res[0].member_id;
} else {
this.set_accountSelect('')
this.bindAccount = ''
}
}
this.set_accountSelect("");
this.bindAccount = "";
}
}
},
},
};
</script>
<style lang="scss" scoped>
......@@ -173,21 +196,22 @@ export default {
min-width: 200px;
height: 32px;
line-height: 32px;
background-color: #E8F7FF;
color: #3491FA;
background-color: #e8f7ff;
color: #3491fa;
font-size: 14px;
&:hover, &:focus {
border-color: #3491FA;
&:hover,
&:focus {
border-color: #3491fa;
}
}
::v-deep .el-input__suffix {
color: #3491FA;
color: #3491fa;
}
::v-deep .el-select-dropdown__item.selected {
color: #3491FA;
color: #3491fa;
}
}
......@@ -197,14 +221,15 @@ export default {
font-weight: 600;
margin-left: 10px;
white-space: nowrap;
color: #F53F3F;
color: #f53f3f;
}
::v-deep .el-button--danger {
background-color: #F56C6C;
border-color: #F56C6C;
background-color: #f56c6c;
border-color: #f56c6c;
&:hover, &:focus {
&:hover,
&:focus {
background-color: #f78989;
border-color: #f78989;
}
......
......@@ -15,50 +15,7 @@
<div v-else-if="gameUserInfo.exp_ip" class="warnText">
<p>高风险用户,禁止转端 !!!</p>
</div>
<div class="cser_info">
<div class="cser_name">
<span>当前客服:{{ `${cser_name}(${clientStatusText})` }}</span>
</div>
<!-- 添加客服状态显示及按钮 -->
<div class="cser_status">
<div class="status-actions">
<el-button
type="danger"
v-if="clientStatus !== 'offline'"
style="margin-left: 0px"
size="mini"
@click="logout"
>下线</el-button
>
<!-- 休息中状态显示结束休息按钮 -->
<el-button
v-if="clientStatus === 'rest'"
type="primary"
size="mini"
@click="handleFinishRest"
>结束休息</el-button
>
<!-- 在线状态显示开始休息按钮 -->
<el-tooltip
v-if="clientStatus === 'online'"
content="午休或者临时有事可点击休息"
placement="top"
>
<el-button type="warning" size="mini" @click="handleStartRest"
>开始休息</el-button
>
</el-tooltip>
<!-- 发送评价按钮 -->
<el-button
type="primary"
style="margin-left: 0px"
size="mini"
@click="handleSendComment"
>发送评价</el-button
>
</div>
</div>
</div>
<!-- 会话内容存档状态 -->
<div
class="archive-status"
......@@ -118,7 +75,7 @@
<span class="label" style="min-width: 45px">备注:</span>
<p v-if="!showInputRemark" class="text" style="max-width: 170px">
{{
chatUserDetails.remark && chatUserDetails.remark != ''
chatUserDetails.remark && chatUserDetails.remark != ""
? chatUserDetails.remark
: chatUserDetails.name
}}
......@@ -268,19 +225,19 @@
</div>
</template>
<script>
import { mapState, mapMutations, mapActions } from 'vuex';
import gameDetails from './gameInfo/gameUserInfo.vue';
import shareInfo from './shareInfo.vue';
import changePhone from './changePhone.vue';
import watchMember from '@/mixins/watchMember';
import { autoResetPassword, bindUserSelfAdd } from '@/api/game';
import { mapState, mapMutations, mapActions } from "vuex";
import gameDetails from "./gameInfo/gameUserInfo.vue";
import shareInfo from "./shareInfo.vue";
import changePhone from "./changePhone.vue";
import watchMember from "@/mixins/watchMember";
import { autoResetPassword, bindUserSelfAdd } from "@/api/game";
import {
memberBindCser,
editUser,
zyouUnBind,
getMemberInfoApi,
} from '@/api/works';
import selectTag from '@/components/selectTag.vue';
} from "@/api/works";
import selectTag from "@/components/selectTag.vue";
import {
getClientStatus,
remarkSessionIntelTag,
......@@ -290,13 +247,13 @@ import {
checkUserPermit,
sendComment,
logout,
} from '@/api/user.js';
import { sendChatMessage } from '@/utils/index.js';
import { getToken, removeToken } from '@/utils/auth';
import vipLevel from './gameInfo/vipLevel.vue';
import Cookies from 'js-cookie';
} from "@/api/user.js";
import { sendChatMessage } from "@/utils/index.js";
import { getToken, removeToken } from "@/utils/auth";
import vipLevel from "./gameInfo/vipLevel.vue";
import Cookies from "js-cookie";
export default {
name: 'info',
name: "info",
components: {
gameDetails,
changePhone,
......@@ -315,50 +272,38 @@ export default {
return {
// 备注相关
showInputRemark: false,
showInputRemarkValue: '',
showInputRemarkValue: "",
change_appraisal: false,
// 自定义列相关
showInput: false,
showInputValue: '',
showInputValue: "",
inputIndex: -1,
changePhone: false,
showTag: false,
// 新增状态数据
agreeStatus: '', // 用户是否同意聊天内容存档:Agreen同意 Disagree不同意
agreeStatus: "", // 用户是否同意聊天内容存档:Agreen同意 Disagree不同意
hasPermit: false, // 客服号是否开启会话内容存档权限
lastTime: '', //上一次交互时间
lastTime: "", //上一次交互时间
};
},
computed: {
...mapState('game', [
'accountSelect',
'gameUserInfo',
'bindGameUserList',
'viewLoading',
...mapState("game", [
"accountSelect",
"gameUserInfo",
"bindGameUserList",
"viewLoading",
]),
...mapState('user', [
'cser_info',
'cser_id',
'cser_name',
'corp_id',
'external_userid',
'userid',
'client_online_status',
'token',
...mapState("user", [
"cser_info",
"cser_id",
"cser_name",
"corp_id",
"external_userid",
"userid",
"client_online_status",
"token",
]),
// 客服状态文本
clientStatusText() {
const statusMap = {
online: '在线',
offline: '离线',
rest: '休息中',
};
return statusMap[this.client_online_status] || '未知';
},
// 客服休息状态:online上线 offline下线 rest休息中
clientStatus() {
return this.client_online_status;
},
getDaysDifference() {
// 解析目标日期字符串
const targetDate = new Date(this.lastTime);
......@@ -389,64 +334,17 @@ export default {
},
methods: {
...mapMutations('game', ['set_accountSelect', 'accountSelect']),
...mapActions('user', ['initWecom']),
...mapMutations("game", ["set_accountSelect", "accountSelect"]),
...mapActions("user", ["initWecom"]),
// 初始化企业微信SDK
async initializeWecom() {
try {
console.log('🚀 开始初始化企业微信 SDK');
console.log("🚀 开始初始化企业微信 SDK");
const result = await this.initWecom();
console.log('✅ 企业微信 SDK 初始化成功', result);
console.log("✅ 企业微信 SDK 初始化成功", result);
} catch (error) {
console.error('❌ 企业微信 SDK 初始化失败:', error);
}
},
logout() {
if (this.client_online_status === 'rest') {
this.$message({
type: 'error',
message: '当前客服号处于休息状态,不能下线',
});
return;
}
this.$confirm('确定下线吗?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
})
.then(() => {
this.userLogout();
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消',
});
});
},
async userLogout() {
const data = {
userid: this.userid,
};
const res = await logout(data);
if (res.status_code === 1) {
this.$message({
type: 'success',
message: '下线成功',
});
removeToken();
Cookies.remove('external_userid');
Cookies.remove('userid');
window.location.href =
window.location.origin +
'/company_app/index.html?corp_id=' +
this.corp_id;
} else {
this.$message({
type: 'error',
message: '下线失败',
});
console.error("❌ 企业微信 SDK 初始化失败:", error);
}
},
......@@ -456,15 +354,15 @@ export default {
// 1. 获取客服休息状态
const statusRes = await getClientStatus();
if (statusRes.status_code === 1) {
if (statusRes.data.client_online_status === 'offline') {
if (statusRes.data.client_online_status === "offline") {
removeToken();
window.location.href =
window.location.origin +
'/company_app/index.html?corp_id=' +
"/company_app/index.html?corp_id=" +
this.corp_id;
}
this.$store.commit(
'user/set_client_online_status',
"user/set_client_online_status",
statusRes.data.client_online_status
);
}
......@@ -478,7 +376,7 @@ export default {
// 4. 检查客服号是否开启会话内容存档
this.checkPermitStatus();
} catch (error) {
console.error('获取初始数据失败:', error);
console.error("获取初始数据失败:", error);
}
},
......@@ -490,9 +388,9 @@ export default {
external_userid: this.external_userid,
userid: this.userid,
});
console.log('智能标签同步成功');
console.log("智能标签同步成功");
} catch (error) {
console.error('智能标签同步失败:', error);
console.error("智能标签同步失败:", error);
}
},
......@@ -507,7 +405,7 @@ export default {
this.agreeStatus = res.data.agree_status;
}
} catch (error) {
console.error('检查用户同意状态失败:', error);
console.error("检查用户同意状态失败:", error);
}
},
......@@ -521,65 +419,7 @@ export default {
this.hasPermit = res.data.has_permit;
}
} catch (error) {
console.error('检查客服权限失败:', error);
}
},
// 开始休息
async handleStartRest() {
try {
const res = await client_session_rest();
if (res.status_code === 1) {
this.$store.commit('user/set_client_online_status', 'rest');
this.$message.success('已开始休息');
} else {
this.$message.error(res.msg || '开始休息失败');
}
} catch (error) {
console.error('开始休息失败:', error);
this.$message.error('开始休息失败');
}
},
// 结束休息
async handleFinishRest() {
try {
const res = await finishRest();
if (res.status_code === 1) {
this.$store.commit('user/set_client_online_status', 'online');
this.$message.success('已结束休息');
} else {
this.$message.error(res.msg || '结束休息失败');
}
} catch (error) {
console.error('结束休息失败:', error);
this.$message.error('结束休息失败');
}
},
// 发送评价
async handleSendComment() {
try {
const res = await sendComment({
corp_id: this.corp_id,
external_userid: this.external_userid,
userid: this.userid,
});
if (res.status_code === 1 && res.data.news) {
// 使用企业微信JSSDK发送评价
const result = await sendChatMessage(res.data.news, 'link');
if (result.success) {
this.$message.success('评价已发送');
} else {
this.$message.error('评价发送失败');
}
} else {
this.$message.error(res.msg || '获取评价内容失败');
}
} catch (error) {
console.error('发送评价失败:', error);
this.$message.error('发送评价失败');
console.error("检查客服权限失败:", error);
}
},
......@@ -590,18 +430,18 @@ export default {
},
// 解绑确认
zyouUnBindConfirm() {
this.$confirm('确定要解绑当前账号么?', '确认提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
this.$confirm("确定要解绑当前账号么?", "确认提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
this.zyouUnBind();
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消',
type: "info",
message: "已取消",
});
});
},
......@@ -610,11 +450,11 @@ export default {
member_id: this.accountSelect,
};
memberBindCser(data).then((res) => {
console.log(res.data.cser_name, 'cser_namecser_namecser_namecser_name');
console.log(res.data.cser_name, "cser_namecser_namecser_namecser_name");
if (res.data.cser_name) {
this.$set(this.chatUserDetails, 'bind_cser', res.data.cser_name);
this.$set(this.chatUserDetails, "bind_cser", res.data.cser_name);
} else {
this.$set(this.chatUserDetails, 'bind_cser', '');
this.$set(this.chatUserDetails, "bind_cser", "");
}
});
},
......@@ -638,12 +478,12 @@ export default {
// 修改密码 之前是客服手动设置密码 现在改成系统自动设置密码
autoResetPassword() {
this.$confirm(
'确认重置密码吗?密码重置后玩家将无法登录,请谨慎操作!',
'重置密码',
"确认重置密码吗?密码重置后玩家将无法登录,请谨慎操作!",
"重置密码",
{
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
}
)
.then((res) => {
......@@ -653,14 +493,14 @@ export default {
};
autoResetPassword(data).then((res) => {
if (res.status_code == 1) {
this.$message.success('密码重置成功');
this.$message.success("密码重置成功");
}
});
})
.catch(() => {
this.$message({
type: 'info',
message: '已取消',
type: "info",
message: "已取消",
});
});
},
......@@ -681,14 +521,14 @@ export default {
};
bindUserSelfAdd(params).then((res) => {
if (res.status_code == 1) {
this.$set(this.chatUserDetails, 'bind_cser', 1);
this.$set(this.chatUserDetails, "bind_cser", 1);
this.$message.success(res.msg);
}
});
},
// 误操作处理
errorHandle() {
this.$emit('error-handle');
this.$emit("error-handle");
},
// 编辑备注
editRemark() {
......@@ -716,7 +556,7 @@ export default {
editUser(data).then((res) => {
if (res.status_code == 1) {
this.$message({
type: 'success',
type: "success",
message: res.msg,
});
}
......@@ -728,12 +568,12 @@ export default {
this.inputIndex = index;
this.showInputValue = item.value;
this.$nextTick(() => {
document.querySelectorAll('input')[0].focus();
document.querySelectorAll("input")[0].focus();
});
},
// 处理自定义列输入
handleInput(item, index) {
this.$emit('update-custom-column', {
this.$emit("update-custom-column", {
item,
index,
value: this.showInputValue,
......@@ -797,24 +637,6 @@ export default {
}
}
.cser_name {
font-size: 14px;
margin-bottom: 10px;
}
.cser_status {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 10px;
font-size: 14px;
.status-actions {
display: flex;
gap: 10px;
}
}
.archive-status {
margin-bottom: 15px;
padding: 8px;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论