273 lines
7.6 KiB
Vue
273 lines
7.6 KiB
Vue
<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>
|