支持客服入口按业务场景分流到客服分组

联系客服支持 scene(general/mohong),按用户与场景复用会话;摸大红入口走摸大红分组选人。
This commit is contained in:
yml2213
2026-07-16 20:05:10 +08:00
parent e07532a188
commit 66ca2693b3
17 changed files with 224 additions and 21 deletions
+20 -2
View File
@@ -14,6 +14,8 @@ export interface ChatParticipant {
joined_at: string
}
export type SupportScene = 'general' | 'mohong'
export interface ChatConversation {
id: number
order_id: number | null
@@ -24,6 +26,7 @@ export interface ChatConversation {
latest_handoff_status?: string
latest_refund_status?: string
type: string
support_scene?: string
title: string
status: string
role: 'renter' | 'owner' | 'support' | 'customer' | 'admin'
@@ -39,6 +42,19 @@ export interface ChatConversation {
updated_at: string
}
/** 根据当前路由推断客服场景:摸大红页 → mohong,其它 → general */
export function resolveSupportScene(path: string): SupportScene {
if (
path === '/mohong' ||
path.startsWith('/mohong/') ||
path === '/m/mohong' ||
path.startsWith('/m/mohong/')
) {
return 'mohong'
}
return 'general'
}
export interface AdminChatCounts {
ownership?: Record<string, number>
stages?: Record<string, number>
@@ -97,8 +113,10 @@ export async function fetchOrderChat(orderId: number) {
return data.data
}
export async function ensureSupportChat() {
const { data } = await apiClient.post<ApiResponse<ChatConversation>>('/chats/support')
export async function ensureSupportChat(scene: SupportScene | string = 'general') {
const { data } = await apiClient.post<ApiResponse<ChatConversation>>('/chats/support', {
scene: scene || 'general',
})
return data.data
}
@@ -3,7 +3,7 @@ import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { onBeforeRouteLeave, RouterLink, useRoute, useRouter } from 'vue-router'
import { showToast } from 'vant'
import MobileBottomNav from '@/components/MobileBottomNav.vue'
import { ensureSupportChat } from '@/features/chats/api/chats'
import { ensureSupportChat, resolveSupportScene } from '@/features/chats/api/chats'
import { formatCent, formatMoney } from '@/shared/utils/money'
import {
emptyListingPublishOptions,
@@ -158,7 +158,8 @@ async function handleSupportClick() {
if (supportLoading.value) return
supportLoading.value = true
try {
const chat = await ensureSupportChat()
const scene = resolveSupportScene(route.path)
const chat = await ensureSupportChat(scene)
router.push(`/m/chats/${chat.id}`)
} catch {
showToast({ message: '联系客服失败,请稍后重试', icon: 'cross' })
@@ -3,17 +3,21 @@ import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { showToast } from 'vant'
import MobileBottomNav from '@/components/MobileBottomNav.vue'
import { ensureSupportChat } from '@/features/chats/api/chats'
import {
fetchMohongCategories,
fetchMohongProducts,
type MohongCategory,
type MohongProduct,
} from '@/features/mohong/api/mohong'
import { useSessionStore } from '@/stores/session'
import { formatCent } from '@/shared/utils/money'
import { readError } from '@/shared/utils/error'
const router = useRouter()
const route = useRoute()
const session = useSessionStore()
const supportLoading = ref(false)
const loading = ref(false)
const products = ref<MohongProduct[]>([])
const categories = ref<MohongCategory[]>([])
@@ -153,6 +157,23 @@ function markCoverFailed(id: number) {
function showCover(item: MohongProduct) {
return Boolean(item.cover_url) && !failedCover.value[item.id]
}
async function handleSupportClick() {
if (!session.isLoggedIn) {
router.push({ path: '/m/login', query: { redirect: route.fullPath } })
return
}
if (supportLoading.value) return
supportLoading.value = true
try {
const chat = await ensureSupportChat('mohong')
router.push(`/m/chats/${chat.id}`)
} catch {
showToast({ message: '联系客服失败,请稍后重试', icon: 'cross' })
} finally {
supportLoading.value = false
}
}
</script>
<template>
@@ -170,6 +191,14 @@ function showCover(item: MohongProduct) {
@input="onSearchInput"
/>
</div>
<button
type="button"
class="orders-btn"
:disabled="supportLoading"
@click="handleSupportClick"
>
{{ supportLoading ? '...' : '客服' }}
</button>
<button type="button" class="orders-btn" @click="router.push('/mohong/orders')">订单</button>
</header>
@@ -2,18 +2,22 @@
import { nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Search, Tickets } from '@element-plus/icons-vue'
import { Search, Service, Tickets } from '@element-plus/icons-vue'
import MohongProductCard from '@/features/mohong/components/MohongProductCard.vue'
import { ensureSupportChat } from '@/features/chats/api/chats'
import {
fetchMohongCategories,
fetchMohongProducts,
type MohongCategory,
type MohongProduct,
} from '@/features/mohong/api/mohong'
import { useSessionStore } from '@/stores/session'
import { readError } from '@/shared/utils/error'
const router = useRouter()
const route = useRoute()
const session = useSessionStore()
const supportLoading = ref(false)
const loading = ref(false)
const loadingMore = ref(false)
const products = ref<MohongProduct[]>([])
@@ -147,6 +151,23 @@ const activeCategoryName = () => {
if (!activeCategoryId.value) return '全部'
return categories.value.find(c => c.id === activeCategoryId.value)?.name || '全部'
}
async function handleSupportClick() {
if (!session.isLoggedIn) {
router.push({ path: '/login', query: { redirect: route.fullPath } })
return
}
if (supportLoading.value) return
supportLoading.value = true
try {
const chat = await ensureSupportChat('mohong')
router.push(`/messages/${chat.id}`)
} catch {
ElMessage.error('联系客服失败,请稍后重试')
} finally {
supportLoading.value = false
}
}
</script>
<template>
@@ -171,6 +192,15 @@ const activeCategoryName = () => {
<el-icon><Tickets /></el-icon>
<span>我的订单</span>
</button>
<button
type="button"
class="toolbar-btn outline"
:disabled="supportLoading"
@click="handleSupportClick"
>
<el-icon><Service /></el-icon>
<span>{{ supportLoading ? '接入中' : '联系客服' }}</span>
</button>
<button type="button" class="toolbar-btn outline" @click="router.push('/')">
租号大厅
</button>
@@ -35,7 +35,7 @@ async function loadOrders() {
<header class="page-header">
<div>
<h1>摸大红订单</h1>
<p class="sub">查看支付状态复制订单信息进入客服</p>
<p class="sub">查看支付状态复制订单信息扫码联系客服</p>
</div>
<button type="button" class="toolbar-btn outline" @click="router.push('/mohong')">
返回商品列表
+3 -2
View File
@@ -24,7 +24,7 @@ import {
VideoPlay,
Wallet,
} from '@element-plus/icons-vue'
import { ensureSupportChat } from '@/features/chats/api/chats'
import { ensureSupportChat, resolveSupportScene } from '@/features/chats/api/chats'
import { useChatUnreadCount } from '@/features/chats/composables/useChatUnreadCount'
import { useSessionStore } from '@/stores/session'
@@ -111,7 +111,8 @@ async function handleSupportClick() {
if (supportLoading.value) return
supportLoading.value = true
try {
const chat = await ensureSupportChat()
const scene = resolveSupportScene(route.path)
const chat = await ensureSupportChat(scene)
router.push(`/messages/${chat.id}`)
} catch {
ElMessage.error('联系客服失败,请稍后重试')