feat: 添加客服在线状态和聊天粘贴图片功能

**客服在线状态:**
- 后端:添加 admin_users.support_status 字段(online/offline/busy)
- 接口:PUT /admin/me/support-status 更新状态
- 前端:管理员顶部导航显示状态切换器(仅客服权限可见)
- 转接对话框显示客服在线状态和智能排序(在线优先)

**聊天粘贴图片:**
- 用户端、移动端、管理端聊天输入框支持 Ctrl+V/Cmd+V 直接粘贴图片
- 自动调用图片压缩和上传逻辑,与文件选择上传保持一致
- 最多支持粘贴9张图片

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
yml
2026-06-05 19:23:34 +08:00
co-authored by Claude Opus 4.6
parent b380817940
commit 59093f8fd5
18 changed files with 308 additions and 36 deletions
@@ -20,11 +20,37 @@ const submitting = ref(false)
const admins = ref<SupportAdmin[]>([])
const selectedAdminId = ref<number | null>(null)
// 按会话数排序,空闲客服优先
// 按在线状态和会话数排序:在线客服优先,会话数少的优先
const sortedAdmins = computed(() => {
return [...admins.value].sort((a, b) => a.chat_count - b.chat_count)
return [...admins.value].sort((a, b) => {
// 在线状态优先级:online > busy > offline
const statusPriority: Record<string, number> = { online: 0, busy: 1, offline: 2 }
const aPriority = statusPriority[a.support_status] ?? 2
const bPriority = statusPriority[b.support_status] ?? 2
if (aPriority !== bPriority) return aPriority - bPriority
// 同状态下按会话数排序
return a.chat_count - b.chat_count
})
})
function getSupportStatusLabel(status: string) {
const labels: Record<string, string> = {
online: '在线',
offline: '离线',
busy: '忙碌',
}
return labels[status] || '未知'
}
function getSupportStatusColor(status: string) {
const colors: Record<string, string> = {
online: '#10b981',
offline: '#9ca3af',
busy: '#f59e0b',
}
return colors[status] || '#9ca3af'
}
watch(() => props.modelValue, (val) => {
visible.value = val
if (val) {
@@ -84,16 +110,19 @@ function getStatusTag(count: number) {
class="admin-item"
>
<div class="admin-info">
<el-avatar :size="36" :icon="User" />
<div class="admin-avatar-wrap">
<el-avatar :size="36" :icon="User" />
<span class="status-indicator" :style="{ backgroundColor: getSupportStatusColor(admin.support_status) }"></span>
</div>
<div class="admin-detail">
<span class="admin-name">{{ admin.nickname }}</span>
<span class="admin-id">ID: {{ admin.id }}</span>
</div>
</div>
<div class="admin-status">
<el-tag :type="getStatusTag(admin.chat_count).type" size="small">
{{ getStatusTag(admin.chat_count).text }}
</el-tag>
<span class="support-status" :style="{ color: getSupportStatusColor(admin.support_status) }">
{{ getSupportStatusLabel(admin.support_status) }}
</span>
<span class="admin-count">{{ admin.chat_count }} 个会话</span>
</div>
</el-radio>
@@ -159,6 +188,21 @@ function getStatusTag(count: number) {
min-width: 0;
}
.admin-avatar-wrap {
position: relative;
flex-shrink: 0;
}
.status-indicator {
position: absolute;
right: 0;
bottom: 0;
width: 10px;
height: 10px;
border-radius: 50%;
border: 2px solid #fff;
}
.admin-detail {
display: flex;
flex-direction: column;
@@ -188,6 +232,11 @@ function getStatusTag(count: number) {
flex-shrink: 0;
}
.support-status {
font-size: 13px;
font-weight: 600;
}
.admin-count {
color: #9ca3af;
font-size: 12px;