Files
hfb_sys/frontend/src/features/admin/views/AdminSupportGroupsView.vue
T
2026-06-18 02:09:02 +08:00

273 lines
7.6 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup lang="ts">
import { ElMessage, ElMessageBox } from 'element-plus'
import { onMounted, reactive, ref } from 'vue'
import {
createSupportGroup,
deleteSupportGroup,
fetchSupportGroupAdmins,
fetchSupportGroups,
updateSupportGroup,
type SaveSupportGroupRequest,
type SupportGroup,
type SupportGroupMember,
} from '@/features/admin/api/supportGroups'
import { readError } from '@/shared/utils/error'
const protectedCodes = new Set(['owner_onboarding', 'renter_handoff'])
const loading = ref(false)
const saving = ref(false)
const groups = ref<SupportGroup[]>([])
const supportAdmins = ref<SupportGroupMember[]>([])
const dialogVisible = ref(false)
const editingGroup = ref<SupportGroup | null>(null)
const form = reactive<SaveSupportGroupRequest>({
code: '',
name: '',
description: '',
status: 'active',
sort_order: 0,
member_ids: [],
})
onMounted(() => {
void Promise.all([loadGroups(), loadSupportAdmins()])
})
async function loadGroups() {
loading.value = true
try {
groups.value = await fetchSupportGroups()
} catch (error) {
ElMessage.error(readError(error, '客服分组加载失败'))
} finally {
loading.value = false
}
}
async function loadSupportAdmins() {
try {
supportAdmins.value = await fetchSupportGroupAdmins()
} catch (error) {
ElMessage.error(readError(error, '客服列表加载失败'))
}
}
function openCreate() {
editingGroup.value = null
Object.assign(form, {
code: '',
name: '',
description: '',
status: 'active',
sort_order: 0,
member_ids: [],
})
dialogVisible.value = true
}
function openEdit(row: SupportGroup) {
editingGroup.value = row
Object.assign(form, {
code: row.code,
name: row.name,
description: row.description,
status: row.status,
sort_order: row.sort_order,
member_ids: row.members.map(item => item.id),
})
dialogVisible.value = true
}
async function saveGroup() {
if (!form.name.trim()) {
ElMessage.warning('请填写分组名称')
return
}
saving.value = true
try {
const payload = {
...form,
name: form.name.trim(),
description: form.description?.trim(),
member_ids: form.member_ids || [],
}
if (editingGroup.value) {
await updateSupportGroup(editingGroup.value.id, payload)
ElMessage.success('客服分组已更新')
} else {
await createSupportGroup(payload)
ElMessage.success('客服分组已创建')
}
dialogVisible.value = false
await loadGroups()
} catch (error) {
ElMessage.error(readError(error, '保存失败'))
} finally {
saving.value = false
}
}
async function handleDelete(row: SupportGroup) {
try {
await ElMessageBox.confirm(`确定删除客服分组「${row.name}」吗?`, '删除确认', {
confirmButtonText: '确认删除',
cancelButtonText: '取消',
type: 'warning',
})
await deleteSupportGroup(row.id)
ElMessage.success('客服分组已删除')
await loadGroups()
} catch {
// 用户取消或接口已提示
}
}
function statusLabel(status: string) {
return status === 'disabled' ? '停用' : '启用'
}
function supportName(item: SupportGroupMember) {
return item.nickname || item.username
}
function supportStatusLabel(status: string) {
const map: Record<string, string> = {
online: '在线',
busy: '忙碌',
offline: '离线',
}
return map[status] || status
}
</script>
<template>
<section class="page">
<div class="page-header-row">
<div class="page-header">
<p class="eyebrow">Support Groups</p>
<h1>客服分组</h1>
<p>配置收号卖号等业务阶段的客服成员系统会按分组自动分配群聊客服</p>
</div>
<div class="toolbar-actions">
<el-button @click="loadGroups">刷新</el-button>
<el-button type="primary" @click="openCreate">新建分组</el-button>
</div>
</div>
<el-table v-loading="loading" class="table-panel" :data="groups">
<el-table-column prop="name" label="分组名称" min-width="150" />
<el-table-column prop="code" label="业务编码" min-width="150" />
<el-table-column prop="description" label="说明" min-width="240" />
<el-table-column label="成员" min-width="260">
<template #default="{ row }">
<div class="member-tags">
<el-tag v-for="member in row.members" :key="member.id" size="small">
{{ supportName(member) }}
</el-tag>
<span v-if="!row.members?.length" class="muted">未配置</span>
</div>
</template>
</el-table-column>
<el-table-column label="状态" width="90">
<template #default="{ row }">
<el-tag :type="row.status === 'disabled' ? 'info' : 'success'" size="small">
{{ statusLabel(row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="sort_order" label="排序" width="80" align="center" />
<el-table-column label="操作" width="170" fixed="right">
<template #default="{ row }">
<el-button size="small" @click="openEdit(row)">编辑</el-button>
<el-button
v-if="!protectedCodes.has(row.code)"
size="small"
type="danger"
@click="handleDelete(row)"
>
删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog
v-model="dialogVisible"
:title="editingGroup ? '编辑客服分组' : '新建客服分组'"
width="560px"
>
<el-form label-position="top">
<el-form-item label="分组名称">
<el-input v-model="form.name" placeholder="例如:收号组-客服" />
</el-form-item>
<el-form-item label="业务编码">
<el-input
v-model="form.code"
:disabled="Boolean(editingGroup)"
placeholder="留空自动生成;默认分组编码不可修改"
/>
</el-form-item>
<el-form-item label="说明">
<el-input v-model="form.description" type="textarea" :rows="2" />
</el-form-item>
<div class="form-grid">
<el-form-item label="状态">
<el-select v-model="form.status">
<el-option label="启用" value="active" />
<el-option label="停用" value="disabled" />
</el-select>
</el-form-item>
<el-form-item label="排序">
<el-input-number v-model="form.sort_order" :min="0" :step="10" />
</el-form-item>
</div>
<el-form-item label="客服成员">
<el-select
v-model="form.member_ids"
multiple
filterable
collapse-tags
collapse-tags-tooltip
placeholder="选择客服人员"
>
<el-option
v-for="admin in supportAdmins"
:key="admin.id"
:label="`${supportName(admin)}${supportStatusLabel(admin.support_status)}`"
:value="admin.id"
/>
</el-select>
</el-form-item>
</el-form>
<template #footer>
<el-button @click="dialogVisible = false">取消</el-button>
<el-button type="primary" :loading="saving" @click="saveGroup">保存</el-button>
</template>
</el-dialog>
</section>
</template>
<style scoped>
.toolbar-actions,
.member-tags {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.muted {
color: #8f9bba;
}
.form-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 16px;
}
:deep(.el-select) {
width: 100%;
}
</style>