backup: kefu state before examiner-add feature on ExaminerManage
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -19,27 +19,27 @@
|
||||
|
||||
|
||||
<!-- 板块与财务 -->
|
||||
<el-sub-menu index="/finance">
|
||||
<el-sub-menu v-if="canShow('finance')" index="/finance">
|
||||
<template #title>
|
||||
<el-icon><DataAnalysis /></el-icon>
|
||||
<span>板块与财务</span>
|
||||
</template>
|
||||
<el-menu-item index="/finance/bankuai">板块配置</el-menu-item>
|
||||
<el-menu-item index="/finance/data">财务数据</el-menu-item>
|
||||
<el-menu-item v-if="canShow('finance.bankuai') && isAllClubScope" index="/finance/bankuai">板块配置</el-menu-item>
|
||||
<el-menu-item v-if="canShow('finance.data')" index="/finance/data">财务数据</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
|
||||
|
||||
|
||||
<!-- 考核管理 -->
|
||||
<el-sub-menu index="/assessment">
|
||||
<el-sub-menu v-if="canShow('assessment')" index="/assessment">
|
||||
<template #title>
|
||||
<el-icon><Medal /></el-icon>
|
||||
<span>考核管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/assessment/examiner">考核官管理</el-menu-item>
|
||||
<el-menu-item index="/assessment/config">考核配置</el-menu-item>
|
||||
<el-menu-item index="/assessment/record">考核记录</el-menu-item>
|
||||
<el-menu-item v-if="canShow('assessment.examiner')" index="/assessment/examiner">考核官管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('assessment.config') && isAllClubScope" index="/assessment/config">考核配置</el-menu-item>
|
||||
<el-menu-item v-if="canShow('assessment.record')" index="/assessment/record">考核记录</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
|
||||
@@ -47,109 +47,119 @@
|
||||
|
||||
|
||||
<!-- 订单管理 -->
|
||||
<el-sub-menu index="/order">
|
||||
<el-sub-menu v-if="canShow('order')" index="/order">
|
||||
<template #title>
|
||||
<el-icon><List /></el-icon>
|
||||
<span>订单管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/order/platform">平台订单</el-menu-item>
|
||||
<el-menu-item index="/order/merchant">商家订单</el-menu-item>
|
||||
<el-menu-item v-if="canShow('order.platform')" index="/order/platform">平台订单</el-menu-item>
|
||||
<el-menu-item v-if="canShow('order.merchant')" index="/order/merchant">商家订单</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 跨平台管理 -->
|
||||
<el-sub-menu index="/cross-platform">
|
||||
<el-sub-menu v-if="canShow('cross-platform')" index="/cross-platform">
|
||||
<template #title>
|
||||
<el-icon><Connection /></el-icon>
|
||||
<span>跨平台管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/cross-order">跨平台订单</el-menu-item>
|
||||
<el-menu-item index="/cross-settings">跨平台设置</el-menu-item>
|
||||
<el-menu-item index="/cross-data">跨平台数据</el-menu-item>
|
||||
<el-menu-item index="/cross-pre">预结算管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('cross-platform.order')" index="/cross-order">跨平台订单</el-menu-item>
|
||||
<el-menu-item v-if="canShow('cross-platform.settings')" index="/cross-settings">跨平台设置</el-menu-item>
|
||||
<el-menu-item v-if="canShow('cross-platform.data')" index="/cross-data">跨平台数据</el-menu-item>
|
||||
<el-menu-item v-if="canShow('cross-platform.pre')" index="/cross-pre">预结算管理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 用户管理 -->
|
||||
<el-sub-menu index="/user">
|
||||
<el-sub-menu v-if="canShow('user')" index="/user">
|
||||
<template #title>
|
||||
<el-icon><User /></el-icon>
|
||||
<span>用户管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/user/player">打手管理</el-menu-item>
|
||||
<el-menu-item index="/user/guanli">管事管理</el-menu-item>
|
||||
<el-menu-item index="/user/shangjia">商家管理</el-menu-item>
|
||||
<el-menu-item index="/user/zuzhang">组长管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('user.player')" index="/user/player">打手管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('user.guanli')" index="/user/guanli">管事管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('user.shangjia')" index="/user/shangjia">商家管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('user.zuzhang')" index="/user/zuzhang">组长管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('user.identity-tag')" index="/user/identity-tag">身份装饰标签</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 提现管理 -->
|
||||
<el-sub-menu index="/withdraw">
|
||||
<el-sub-menu v-if="canShow('withdraw')" index="/withdraw">
|
||||
<template #title>
|
||||
<el-icon><Money /></el-icon>
|
||||
<span>提现管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/withdraw/audit">提现审核</el-menu-item>
|
||||
<el-menu-item index="/withdraw/data">提现数据</el-menu-item>
|
||||
<el-menu-item index="/withdraw/settings">提现设置</el-menu-item>
|
||||
<el-menu-item v-if="canShow('withdraw.audit')" index="/withdraw/audit">提现审核</el-menu-item>
|
||||
<el-menu-item v-if="canShow('withdraw.data')" index="/withdraw/data">提现数据</el-menu-item>
|
||||
<el-menu-item v-if="canShow('withdraw.settings')" index="/withdraw/settings">提现设置</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 管理员管理 -->
|
||||
<el-sub-menu index="/admin">
|
||||
<el-sub-menu v-if="canShow('admin')" index="/admin">
|
||||
<template #title>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>管理员管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/admin/role">角色管理</el-menu-item>
|
||||
<el-menu-item index="/admin/user">后台用户</el-menu-item>
|
||||
<el-menu-item v-if="canShow('admin.role')" index="/admin/role">角色管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('admin.data-scope')" index="/admin/data-scope">数据范围配置</el-menu-item>
|
||||
<el-menu-item v-if="canShow('admin.club-config')" index="/admin/club-config">俱乐部密钥配置</el-menu-item>
|
||||
<el-menu-item v-if="canShow('admin.user')" index="/admin/user">后台用户</el-menu-item>
|
||||
<el-menu-item v-if="canShow('admin.operation-log')" index="/admin/operation-log">操作日志</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 处罚管理 -->
|
||||
<el-menu-item index="/punishment">
|
||||
<el-menu-item v-if="canShow('punishment')" index="/punishment">
|
||||
<el-icon><Warning /></el-icon>
|
||||
<span>处罚管理</span>
|
||||
</el-menu-item>
|
||||
|
||||
<!-- 商品管理 -->
|
||||
<el-sub-menu index="/product">
|
||||
<el-sub-menu v-if="canShow('product')" index="/product">
|
||||
<template #title>
|
||||
<el-icon><Goods /></el-icon>
|
||||
<span>商品管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/product/list">商品列表</el-menu-item>
|
||||
<el-menu-item index="/product/type-zone">商品类型专区管理</el-menu-item>
|
||||
<el-menu-item index="/product/data">商品数据分析</el-menu-item>
|
||||
<el-menu-item v-if="canShow('product.list') && isAllClubScope" index="/product/list">商品列表</el-menu-item>
|
||||
<el-menu-item v-if="canShow('product.type-zone') && isAllClubScope" index="/product/type-zone">商品类型专区管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('product.data') && isAllClubScope" index="/product/data">商品数据分析</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 会员管理 -->
|
||||
<el-sub-menu index="/member">
|
||||
<el-sub-menu v-if="canShow('member')" index="/member">
|
||||
<template #title>
|
||||
<el-icon><UserFilled /></el-icon>
|
||||
<span>会员管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/member/list">会员管理</el-menu-item>
|
||||
<el-menu-item index="/member/data">会员数据分析</el-menu-item>
|
||||
<el-menu-item v-if="canShow('member.list')" index="/member/list">会员管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('member.data')" index="/member/data">会员数据分析</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 小程序配置 -->
|
||||
<el-sub-menu index="/miniapp">
|
||||
<el-sub-menu v-if="canShow('miniapp')" index="/miniapp">
|
||||
<template #title>
|
||||
<el-icon><Setting /></el-icon>
|
||||
<span>小程序配置</span>
|
||||
</template>
|
||||
<el-menu-item index="/miniapp/popup-notice">弹窗公告管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('miniapp.carousel')" index="/miniapp/carousel">轮播图与公告</el-menu-item>
|
||||
<el-menu-item v-if="canShow('miniapp.carousel') || canShow('miniapp.assets')" index="/miniapp/assets">图标与频道</el-menu-item>
|
||||
<el-menu-item v-if="canShow('miniapp.popup')" index="/miniapp/popup-notice">弹窗公告管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('miniapp.leixing')" index="/miniapp/leixing">抢单商品类型</el-menu-item>
|
||||
<el-menu-item v-if="canShow('miniapp.kaohe-tags')" index="/miniapp/kaohe-tags">抢单考核标签</el-menu-item>
|
||||
<el-menu-item v-if="canShow('miniapp.dashou-exam')" index="/miniapp/dashou-exam">打手接单考试</el-menu-item>
|
||||
<el-menu-item v-if="canShow('miniapp.withdraw-settings')" index="/withdraw/settings">提现与收款设置</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 店铺管理 -->
|
||||
<el-sub-menu index="/shop">
|
||||
<el-sub-menu v-if="canShow('shop')" index="/shop">
|
||||
<template #title>
|
||||
<el-icon><Shop /></el-icon>
|
||||
<span>店铺管理</span>
|
||||
</template>
|
||||
<el-menu-item index="/shop/list">店铺列表</el-menu-item>
|
||||
<el-menu-item index="/shop/withdraw-apply">店铺提现申请</el-menu-item>
|
||||
<el-menu-item index="/shop/products">店铺商品管理</el-menu-item>
|
||||
<el-menu-item v-if="canShow('shop.list')" index="/shop/list">店铺列表</el-menu-item>
|
||||
<el-menu-item v-if="canShow('shop.withdraw')" index="/shop/withdraw-apply">店铺提现申请</el-menu-item>
|
||||
<el-menu-item v-if="canShow('shop.products')" index="/shop/products">店铺商品管理</el-menu-item>
|
||||
</el-sub-menu>
|
||||
|
||||
<!-- 会话管理(客服专用) -->
|
||||
<el-menu-item index="/chat/agent" :class="{ 'menu-disabled': !hasAnyChatPerm }">
|
||||
<el-menu-item v-if="canShow('chat.agent')" index="/chat/agent" :class="{ 'menu-disabled': !hasAnyChatPerm }">
|
||||
<el-icon><Comment /></el-icon>
|
||||
<span>会话管理</span>
|
||||
<el-badge
|
||||
@@ -170,21 +180,46 @@
|
||||
|
||||
<el-container>
|
||||
<el-header class="header">
|
||||
<div class="header-left">
|
||||
<span class="page-title">{{ currentTitle }}</span>
|
||||
</div>
|
||||
<div class="stats-mini">
|
||||
<div v-for="stat in statsList" :key="stat.label" class="stat-mini-card">
|
||||
<div class="stat-mini-icon">{{ stat.icon }}</div>
|
||||
<div class="stat-mini-content">
|
||||
<div class="stat-mini-label">{{ stat.label }}</div>
|
||||
<div class="stat-mini-value">{{ stat.value }}</div>
|
||||
</div>
|
||||
<div class="header-row header-row-main">
|
||||
<div class="header-left">
|
||||
<span class="page-title">{{ currentTitle }}</span>
|
||||
<el-select
|
||||
v-if="showScopeSelector"
|
||||
v-model="scopeSelectValue"
|
||||
size="small"
|
||||
class="club-select"
|
||||
@change="onScopeSelectChange"
|
||||
>
|
||||
<el-option
|
||||
v-if="canGroupScope"
|
||||
label="集团汇总(全部子公司)"
|
||||
value="__all__"
|
||||
/>
|
||||
<el-option
|
||||
v-for="item in clubOptions"
|
||||
:key="item.club_id"
|
||||
:label="item.name"
|
||||
:value="item.club_id"
|
||||
/>
|
||||
</el-select>
|
||||
<span v-else-if="scopeBadgeText" class="club-badge">{{ scopeBadgeText }}</span>
|
||||
<span v-if="adminRoleName" class="role-badge">{{ adminRoleName }}</span>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<span class="user-badge">{{ userPhone }}</span>
|
||||
<el-button class="logout-btn" size="small" @click="handleLogout" plain>退出</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="header-right">
|
||||
<span class="user-badge">{{ userPhone }}</span>
|
||||
<el-button class="logout-btn" size="small" @click="handleLogout" plain>退出</el-button>
|
||||
<div class="header-row header-row-stats">
|
||||
<div class="stats-mini">
|
||||
<div v-for="stat in statsList" :key="stat.label" class="stat-mini-card">
|
||||
<div class="stat-mini-icon">{{ stat.icon }}</div>
|
||||
<div class="stat-mini-content">
|
||||
<div class="stat-mini-label">{{ stat.label }}</div>
|
||||
<div class="stat-mini-value">{{ stat.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</el-header>
|
||||
|
||||
@@ -196,18 +231,43 @@
|
||||
</router-view>
|
||||
</el-main>
|
||||
</el-container>
|
||||
<KefuConnect v-if="hasAnyChatPerm" />
|
||||
</el-container>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, onMounted, onUnmounted, getCurrentInstance, provide } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { ElMessage, ElNotification } from 'element-plus'
|
||||
import {
|
||||
List, User, Warning, Money, Fold, Expand, Connection, Setting,
|
||||
Goods, UserFilled, Shop, Comment,DataAnalysis, Medal // 新增
|
||||
} from '@element-plus/icons-vue'
|
||||
import request from '@/utils/request'
|
||||
import KefuConnect from '@/components/KefuConnect.vue'
|
||||
import { disconnectKefuChat } from '@/utils/kefuChat'
|
||||
import {
|
||||
getAdminClubContext,
|
||||
getAdminClubId,
|
||||
getAdminClubScope,
|
||||
setAdminClubContext,
|
||||
mergeServerClubContext,
|
||||
JITUAN_API,
|
||||
setMenuAccess,
|
||||
getMenuAccess,
|
||||
canSwitchClubByMenu,
|
||||
shouldShowAllMenus,
|
||||
ensureMenuAccess,
|
||||
} from '@/utils/club-context'
|
||||
|
||||
const menuAccessState = ref(getMenuAccess())
|
||||
|
||||
const canShow = (pageId) => {
|
||||
const access = menuAccessState.value ?? getMenuAccess()
|
||||
if (shouldShowAllMenus(access)) return true
|
||||
if (access.visible_page_ids.length === 0) return false
|
||||
return access.visible_page_ids.includes(pageId)
|
||||
}
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
const route = useRoute()
|
||||
@@ -221,6 +281,90 @@ const toggleCollapse = () => {
|
||||
const currentTitle = computed(() => route.meta?.title || '客服终端')
|
||||
const userPhone = ref(localStorage.getItem('username') || '未知账号')
|
||||
|
||||
const clubOptions = ref([])
|
||||
const scopeSelectValue = ref('')
|
||||
|
||||
const adminCtx = computed(() => getAdminClubContext())
|
||||
const isGroupAdmin = computed(() => !!adminCtx.value?.is_group_admin)
|
||||
const canGroupScope = computed(() => isGroupAdmin.value)
|
||||
const isAllClubScope = computed(() => getAdminClubScope() === 'all')
|
||||
const isSingleClubScope = computed(() => getAdminClubScope() !== 'all')
|
||||
const showScopeSelector = computed(() => canSwitchClubByMenu())
|
||||
|
||||
const scopeBadgeText = computed(() => {
|
||||
const scope = getAdminClubScope()
|
||||
if (scope === 'all') return '集团汇总(全部子公司)'
|
||||
const cid = getAdminClubId()
|
||||
const hit = clubOptions.value.find((c) => c.club_id === cid)
|
||||
return hit ? `${hit.name}(${cid})` : cid
|
||||
})
|
||||
|
||||
const adminRoleName = computed(() => adminCtx.value?.role_name || '')
|
||||
|
||||
const syncScopeSelectValue = () => {
|
||||
const scope = getAdminClubScope()
|
||||
scopeSelectValue.value = scope === 'all' ? '__all__' : getAdminClubId()
|
||||
}
|
||||
|
||||
const loadMenuAccess = async () => {
|
||||
try {
|
||||
await ensureMenuAccess(async () => {
|
||||
const res = await request.get(JITUAN_API.menuAccess)
|
||||
return res.code === 0 ? res.data : null
|
||||
})
|
||||
const data = getMenuAccess()
|
||||
if (data) {
|
||||
menuAccessState.value = data
|
||||
const ctx = getAdminClubContext()
|
||||
if (ctx) {
|
||||
setAdminClubContext({ ...ctx, can_switch_club: data.can_switch_club })
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载菜单权限失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const loadClubContext = async () => {
|
||||
try {
|
||||
const res = await request.get(JITUAN_API.meContext)
|
||||
if (res.code === 0 && res.data) {
|
||||
mergeServerClubContext(res.data)
|
||||
clubOptions.value = res.data.clubs || []
|
||||
syncScopeSelectValue()
|
||||
return
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载俱乐部上下文失败:', error)
|
||||
}
|
||||
const ctx = getAdminClubContext()
|
||||
if (ctx && ctx.clubs) {
|
||||
clubOptions.value = ctx.clubs
|
||||
syncScopeSelectValue()
|
||||
}
|
||||
}
|
||||
|
||||
const onScopeSelectChange = (val) => {
|
||||
const ctx = getAdminClubContext() || {}
|
||||
if (val === '__all__') {
|
||||
setAdminClubContext({
|
||||
...ctx,
|
||||
scope: 'ALL_CLUBS',
|
||||
club_id: ctx.club_id || 'xq',
|
||||
})
|
||||
ElMessage.success('已切换至:集团汇总(全部子公司)')
|
||||
} else {
|
||||
setAdminClubContext({
|
||||
...ctx,
|
||||
scope: 'SINGLE_CLUB',
|
||||
club_id: val,
|
||||
})
|
||||
const hit = clubOptions.value.find((c) => c.club_id === val)
|
||||
ElMessage.success(`已切换至:${hit?.name || val}`)
|
||||
}
|
||||
window.location.reload()
|
||||
}
|
||||
|
||||
const stats = ref({
|
||||
jinrichuli: 0,
|
||||
jinyuechuli: 0,
|
||||
@@ -256,7 +400,9 @@ const fetchStats = async () => {
|
||||
}
|
||||
|
||||
const handleLogout = () => {
|
||||
disconnectKefuChat()
|
||||
localStorage.clear()
|
||||
sessionStorage.clear()
|
||||
router.push('/login')
|
||||
ElMessage.success('已退出登录')
|
||||
}
|
||||
@@ -286,63 +432,86 @@ const fetchKefuPermission = async () => {
|
||||
const { permissions, goeasy_appkey } = res.data
|
||||
kefuPermissions.value = permissions || []
|
||||
goeasyAppkey.value = goeasy_appkey || ''
|
||||
|
||||
// 如果有权限且有appkey,初始化GoEasy(仅一次)
|
||||
if (hasAnyChatPerm.value && goeasyAppkey.value) {
|
||||
initGoEasy()
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('获取客服权限失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化GoEasy实例并连接
|
||||
const initGoEasy = () => {
|
||||
// 防止重复初始化
|
||||
if (window.goeasy) return
|
||||
|
||||
// 1. 初始化实例
|
||||
window.goeasy = GoEasy.getInstance({
|
||||
host: 'hangzhou.goeasy.io', // 根据实际区域调整
|
||||
appkey: goeasyAppkey.value,
|
||||
modules: ['im']
|
||||
})
|
||||
|
||||
// 2. 构建客服自己的userId = KF + 账号
|
||||
const agentId = 'KF' + userPhone.value
|
||||
const agentData = {
|
||||
name: userPhone.value, // 客服名称
|
||||
avatar: '' // 可后续加上头像
|
||||
}
|
||||
|
||||
// 3. 建立连接
|
||||
window.goeasy.connect({
|
||||
id: agentId,
|
||||
data: agentData,
|
||||
onSuccess: () => {
|
||||
console.log('客服GoEasy连接成功')
|
||||
// 连接成功后,通知Agent页面(通过provide)
|
||||
proxy.$emitter.emit('goeasy-connected')
|
||||
},
|
||||
onFailed: (error) => {
|
||||
console.error('GoEasy连接失败', error)
|
||||
ElMessage.error('消息服务连接失败,请刷新重试')
|
||||
},
|
||||
onProgress: (attempts) => {
|
||||
console.log('GoEasy正在重连,第' + attempts + '次')
|
||||
}
|
||||
})
|
||||
|
||||
// 4. 保存客服标识到全局
|
||||
window.__kefuAgentId = agentId
|
||||
}
|
||||
|
||||
// 监听子组件传来的未读消息更新
|
||||
const onAgentUnread = (count) => {
|
||||
agentUnread.value = count
|
||||
}
|
||||
|
||||
let lastUnreadForNotify = 0
|
||||
let unreadBaselineReady = false
|
||||
|
||||
const playNotifySound = () => {
|
||||
try {
|
||||
const AudioCtx = window.AudioContext || window.webkitAudioContext
|
||||
if (!AudioCtx) return
|
||||
const ctx = new AudioCtx()
|
||||
const playBeep = (freq, start, duration) => {
|
||||
const osc = ctx.createOscillator()
|
||||
const gain = ctx.createGain()
|
||||
osc.connect(gain)
|
||||
gain.connect(ctx.destination)
|
||||
osc.frequency.value = freq
|
||||
gain.gain.value = 0.25
|
||||
osc.start(start)
|
||||
osc.stop(start + duration)
|
||||
}
|
||||
playBeep(880, ctx.currentTime, 0.12)
|
||||
playBeep(1100, ctx.currentTime + 0.15, 0.12)
|
||||
setTimeout(() => ctx.close(), 500)
|
||||
} catch (e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
const showMessageNotification = (title, message) => {
|
||||
playNotifySound()
|
||||
ElNotification({
|
||||
title,
|
||||
message,
|
||||
type: 'info',
|
||||
duration: 0,
|
||||
showClose: true,
|
||||
position: 'top-right',
|
||||
customClass: 'kefu-msg-notify',
|
||||
onClick: () => {
|
||||
ElNotification.closeAll()
|
||||
router.push('/chat/agent')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const onMessageNotify = (payload) => {
|
||||
if (route.path === '/chat/agent') return
|
||||
const customerId = payload?.customerId || '用户'
|
||||
const preview = payload?.preview || '收到新消息'
|
||||
showMessageNotification('新会话消息', `${customerId}: ${preview}`)
|
||||
}
|
||||
|
||||
const onUnreadBumpNotify = (count) => {
|
||||
if (!unreadBaselineReady) {
|
||||
lastUnreadForNotify = count
|
||||
unreadBaselineReady = true
|
||||
return
|
||||
}
|
||||
if (route.path === '/chat/agent') {
|
||||
lastUnreadForNotify = count
|
||||
return
|
||||
}
|
||||
if (count <= 0) {
|
||||
lastUnreadForNotify = 0
|
||||
return
|
||||
}
|
||||
if (count > lastUnreadForNotify) {
|
||||
showMessageNotification('会话未读提醒', `您有 ${count} 条未读消息,点击查看`)
|
||||
}
|
||||
lastUnreadForNotify = count
|
||||
}
|
||||
|
||||
// 向子组件提供客服相关数据
|
||||
provide('kefuPermissions', kefuPermissions)
|
||||
provide('permPrefixMap', permPrefixMap)
|
||||
@@ -350,16 +519,22 @@ provide('goeasyAppkey', goeasyAppkey)
|
||||
provide('hasAnyChatPerm', hasAnyChatPerm)
|
||||
|
||||
onMounted(() => {
|
||||
loadMenuAccess()
|
||||
loadClubContext()
|
||||
fetchStats()
|
||||
fetchKefuPermission() // 登录后先获取权限
|
||||
fetchKefuPermission()
|
||||
proxy.$emitter?.on('refresh-stats', fetchStats)
|
||||
proxy.$emitter?.on('agent-unread', onAgentUnread)
|
||||
proxy.$emitter?.on('agent-unread', onUnreadBumpNotify)
|
||||
proxy.$emitter?.on('kefu-message-notify', onMessageNotify)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
if (abortController) abortController.abort()
|
||||
proxy.$emitter?.off('refresh-stats', fetchStats)
|
||||
proxy.$emitter?.off('agent-unread', onAgentUnread)
|
||||
proxy.$emitter?.off('agent-unread', onUnreadBumpNotify)
|
||||
proxy.$emitter?.off('kefu-message-notify', onMessageNotify)
|
||||
})
|
||||
</script>
|
||||
|
||||
@@ -501,19 +676,67 @@ onUnmounted(() => {
|
||||
background: rgba(6, 12, 20, 0.7);
|
||||
backdrop-filter: blur(12px);
|
||||
border-bottom: 1px solid rgba(0, 242, 255, 0.3);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 10px 24px 12px;
|
||||
gap: 10px;
|
||||
height: auto;
|
||||
min-height: 80px;
|
||||
}
|
||||
|
||||
.header-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.header-row-main {
|
||||
justify-content: space-between;
|
||||
padding: 0 24px;
|
||||
height: 80px;
|
||||
gap: 20px;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.header-row-stats {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header-left {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
|
||||
.club-select {
|
||||
width: 200px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.club-badge {
|
||||
white-space: nowrap;
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
color: rgba(0, 242, 255, 0.9);
|
||||
border: 1px solid rgba(0, 242, 255, 0.35);
|
||||
background: rgba(0, 242, 255, 0.08);
|
||||
max-width: 240px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
white-space: nowrap;
|
||||
flex-shrink: 0;
|
||||
font-size: 12px;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
color: rgba(0, 242, 255, 0.85);
|
||||
border: 1px solid rgba(0, 242, 255, 0.35);
|
||||
background: rgba(0, 242, 255, 0.08);
|
||||
}
|
||||
|
||||
.page-title {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
@@ -529,9 +752,10 @@ onUnmounted(() => {
|
||||
.stats-mini {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
flex: 1;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: center;
|
||||
overflow-x: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.stat-mini-card {
|
||||
@@ -640,15 +864,13 @@ onUnmounted(() => {
|
||||
/* 响应式 */
|
||||
@media screen and (max-width: 1024px) {
|
||||
.header {
|
||||
height: auto;
|
||||
padding: 12px 20px;
|
||||
padding: 10px 16px;
|
||||
}
|
||||
.header-row-main {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.stats-mini {
|
||||
order: 3;
|
||||
width: 100%;
|
||||
justify-content: flex-start;
|
||||
margin-top: 8px;
|
||||
}
|
||||
.stat-mini-card {
|
||||
padding: 6px 16px;
|
||||
@@ -681,4 +903,22 @@ onUnmounted(() => {
|
||||
.agent-badge {
|
||||
margin-left: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style>
|
||||
.kefu-msg-notify {
|
||||
min-width: 320px;
|
||||
border-left: 4px solid #409eff !important;
|
||||
box-shadow: 0 4px 20px rgba(64, 158, 255, 0.35) !important;
|
||||
}
|
||||
.kefu-msg-notify .el-notification__title {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
color: #303133;
|
||||
}
|
||||
.kefu-msg-notify .el-notification__content {
|
||||
font-size: 14px;
|
||||
color: #606266;
|
||||
line-height: 1.5;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user