This commit is contained in:
2026-08-14 16:06:30 +08:00
parent 1c1c48f8d3
commit d1b4a4d3f3
26 changed files with 187 additions and 29 deletions
@@ -113,6 +113,7 @@ function statusMeta(status: PlatformTenantStatus) {
if (status === 'PROFILE_REQUIRED') return { label: '待完善', type: 'info' as const }
if (status === 'PENDING') return { label: '待审核', type: 'warning' as const }
if (status === 'REJECTED') return { label: '已驳回', type: 'danger' as const }
if (status === 'ARCHIVED') return { label: '已归档', type: 'info' as const }
return { label: '已停用', type: 'info' as const }
}
function qualificationLabel(type: string) {
@@ -373,7 +374,7 @@ watch(orderPage, (page, previousPage) => {
<template #actions>
<div v-if="customer" class="header-actions">
<el-tag :type="statusMeta(customer.status).type" effect="light" round>{{ statusMeta(customer.status).label }}</el-tag>
<el-button v-if="access.can('platform:customer:edit')" @click="goEdit"><Pencil :size="16" />编辑信息</el-button>
<el-button v-if="access.can('platform:customer:edit') && customer.status !== 'ARCHIVED'" @click="goEdit"><Pencil :size="16" />编辑信息</el-button>
<el-button v-if="access.can('platform:customer:status') && ['ACTIVE', 'DISABLED'].includes(customer.status)" :type="customer.status === 'ACTIVE' ? 'danger' : 'success'" :loading="statusChanging" @click="changeStatus"><Power :size="16" />{{ customer.status === 'ACTIVE' ? '停用账号' : '启用账号' }}</el-button>
</div>
</template>
@@ -529,7 +529,7 @@ describe('CustomersView', () => {
const auth = useAuthStore()
auth.permissions = [...auth.permissions, 'platform:customer:delete']
useAccessPreviewStore().initializeAfterLogin()
vi.mocked(archivePlatformTenant).mockResolvedValue({ ...fullTenant, status: 'DISABLED' })
vi.mocked(archivePlatformTenant).mockResolvedValue({ ...fullTenant, status: 'ARCHIVED' })
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
await flushPromises()
@@ -578,6 +578,24 @@ describe('CustomersView', () => {
expect(document.body.textContent).toContain('代登录原因')
})
it('treats archived customers as read-only and terminal', async () => {
const auth = useAuthStore()
auth.permissions = [...auth.permissions, 'platform:customer:edit', 'platform:customer:status', 'platform:customer:delete']
useAccessPreviewStore().initializeAfterLogin()
vi.mocked(listPlatformTenants).mockResolvedValue({
records: [{ ...fullTenant, status: 'ARCHIVED' }], page: 1, size: 10, total: 1, pages: 1,
})
mount(CustomersView, { attachTo: document.body, global: { plugins: [pinia, ElementPlus] } })
await flushPromises()
expect(document.body.textContent).toContain('已归档')
expect(tableButtonByText('编辑')).toBeUndefined()
expect(tableButtonByText('启用')).toBeUndefined()
expect(tableButtonByText('停用')).toBeUndefined()
expect(tableButtonByText('删除')).toBeUndefined()
})
it('renders all permitted actions directly for multiple customers', async () => {
const auth = useAuthStore()
auth.permissions = [...auth.permissions, 'platform:customer:reset-password', 'platform:customer:impersonate', 'platform:customer:status', 'platform:customer:delete']
@@ -131,7 +131,7 @@ const queryFields: QueryField[] = [
{ key: 'status', type: 'select', label: '状态', placeholder: '全部状态', options: [
{ label: '待完善', value: 'PROFILE_REQUIRED' }, { label: '待审核', value: 'PENDING' },
{ label: '启用', value: 'ACTIVE' }, { label: '已驳回', value: 'REJECTED' },
{ label: '已停用', value: 'DISABLED' },
{ label: '已停用', value: 'DISABLED' }, { label: '已归档', value: 'ARCHIVED' },
] },
]
@@ -271,6 +271,7 @@ function statusMeta(status: PlatformTenantStatus) {
if (status === 'PROFILE_REQUIRED') return { label: '待完善', type: 'info' as const }
if (status === 'PENDING') return { label: '待审核', type: 'warning' as const }
if (status === 'REJECTED') return { label: '已驳回', type: 'danger' as const }
if (status === 'ARCHIVED') return { label: '已归档', type: 'info' as const }
return { label: '已停用', type: 'info' as const }
}
@@ -644,8 +645,8 @@ function canImpersonate(row: PlatformTenantResource) {
function canChangeStatus(row: PlatformTenantResource) {
return access.can('platform:customer:status') && ['ACTIVE', 'DISABLED'].includes(row.status)
}
function canArchive() {
return access.can('platform:customer:delete')
function canArchive(row: PlatformTenantResource) {
return access.can('platform:customer:delete') && ['PROFILE_REQUIRED', 'REJECTED'].includes(row.status)
}
type CustomerRowAction = 'reset-password' | 'impersonate' | 'status' | 'delete'
const customerRowActionIcons: Record<CustomerRowAction, Component> = {
@@ -664,7 +665,7 @@ function customerSecondaryActions(row: PlatformTenantResource): CustomerRowActio
...(canResetPassword(row) ? ['reset-password' as const] : []),
...(canImpersonate(row) ? ['impersonate' as const] : []),
...(canChangeStatus(row) ? ['status' as const] : []),
...(canArchive() ? ['delete' as const] : []),
...(canArchive(row) ? ['delete' as const] : []),
]
}
function customerActionLabel(row: PlatformTenantResource, action: CustomerRowAction) {
@@ -819,7 +820,7 @@ async function submitArchive() {
archiveError.value = null
try {
await archivePlatformTenant(archiveTenant.value.id)
ElMessage.success('客户已删除,历史数据已保留且账号已停用')
ElMessage.success('客户已归档,历史数据已保留且账号已停用')
archiveDialogOpen.value = false
archiveTenant.value = null
archiveConfirmCode.value = ''
@@ -1016,7 +1017,7 @@ onMounted(() => { void loadCustomers() })
<template #actions="{ row }">
<div class="table-actions">
<el-button v-if="access.can('platform:customer:list')" class="customer-action-button customer-action-detail" size="small" @click="showDetail(row as PlatformTenantResource)"><Eye :size="14" />详情</el-button>
<el-button v-if="access.can('platform:customer:edit')" class="customer-action-button customer-action-edit" size="small" @click="openEdit(row as PlatformTenantResource)"><Pencil :size="14" />编辑</el-button>
<el-button v-if="access.can('platform:customer:edit') && (row as PlatformTenantResource).status !== 'ARCHIVED'" class="customer-action-button customer-action-edit" size="small" @click="openEdit(row as PlatformTenantResource)"><Pencil :size="14" />编辑</el-button>
<el-button v-if="hasReviewAction(row as PlatformTenantResource)" class="customer-action-button customer-action-review" size="small" :disabled="reviewSubmitting" @click="openDecision(row as PlatformTenantResource, access.can('platform:customer:approve') ? 'approve' : 'reject')"><ShieldCheck :size="14" />审核</el-button>
<el-button v-for="action in customerSecondaryActions(row as PlatformTenantResource)" :key="action" :class="[`customer-action-button`, `customer-action-${action}`, { 'is-enable': action === 'status' && (row as PlatformTenantResource).status !== 'ACTIVE', 'is-disable': action === 'status' && (row as PlatformTenantResource).status === 'ACTIVE' }]" size="small" :disabled="customerActionDisabled(action)" @click="handleMoreAction(row as PlatformTenantResource, action)"><component :is="customerRowActionIcons[action]" :size="14" />{{ customerActionLabel(row as PlatformTenantResource, action) }}</el-button>
</div>