feat: 新增客服备用金台账与后台用户中文显示名
- 新增备用金账户、流水、支出、对账与权限 - 后台用户支持中文显示名并同步备用金姓名 - 审计、登录记录、任务及工单操作人优先展示中文名 - 补充备用金与人工支出方案文档
This commit is contained in:
@@ -27,12 +27,13 @@ import {
|
||||
fetchAdminRolePermissions,
|
||||
fetchAdminUsers,
|
||||
resetAdminUserPassword,
|
||||
updateAdminUserDisplayName,
|
||||
updateAdminUserRole,
|
||||
updateAdminUserStatus,
|
||||
updateAdminRolePermissions,
|
||||
} from '@/services/admin'
|
||||
import type { AdminRole, AdminUserListItem } from '@/types/admin'
|
||||
import { getAdminUserId, hasAdminRole } from '@/utils/admin-auth'
|
||||
import { getAdminUserId, hasAdminRole, setAdminDisplayName } from '@/utils/admin-auth'
|
||||
import { adminUserRoleOptions, adminUserStatusOptions } from '@/utils/admin-options'
|
||||
import {
|
||||
ADMIN_DEFAULT_PAGE_SIZE,
|
||||
@@ -48,11 +49,16 @@ type UserFilterForm = {
|
||||
}
|
||||
|
||||
type CreateUserForm = {
|
||||
displayName?: string
|
||||
username: string
|
||||
password: string
|
||||
role: AdminRole
|
||||
}
|
||||
|
||||
type DisplayNameForm = {
|
||||
displayName: string
|
||||
}
|
||||
|
||||
const USER_ROLE_OPTIONS = [
|
||||
{ label: '普通运营', value: 'operator' },
|
||||
{ label: '客服', value: 'support' },
|
||||
@@ -119,6 +125,7 @@ export default function AdminUsersPage() {
|
||||
try {
|
||||
await createAdminUser({
|
||||
username: values.username.trim(),
|
||||
displayName: values.displayName?.trim() || '',
|
||||
password: values.password.trim(),
|
||||
role: values.role,
|
||||
status: 'active',
|
||||
@@ -139,7 +146,7 @@ export default function AdminUsersPage() {
|
||||
|
||||
modal.confirm({
|
||||
title: '确认操作',
|
||||
content: `确认将 ${item.username} 调整为${formatRoleLabel(nextRole)}吗?`,
|
||||
content: `确认将 ${displayNameOf(item)} 调整为${formatRoleLabel(nextRole)}吗?`,
|
||||
okText: '继续执行',
|
||||
cancelText: '取消',
|
||||
centered: true,
|
||||
@@ -163,7 +170,7 @@ export default function AdminUsersPage() {
|
||||
|
||||
modal.confirm({
|
||||
title: '确认操作',
|
||||
content: `确认将 ${item.username}${nextStatus === 'active' ? '启用' : '停用'}吗?`,
|
||||
content: `确认将 ${displayNameOf(item)}${nextStatus === 'active' ? '启用' : '停用'}吗?`,
|
||||
okText: '继续执行',
|
||||
cancelText: '取消',
|
||||
centered: true,
|
||||
@@ -182,6 +189,66 @@ export default function AdminUsersPage() {
|
||||
})
|
||||
}
|
||||
|
||||
function openEditDisplayNameModal(item: AdminUserListItem) {
|
||||
const form = ModalFormStore.create<DisplayNameForm>()
|
||||
|
||||
modal.confirm({
|
||||
title: '修改姓名',
|
||||
content: (
|
||||
<Form
|
||||
layout="vertical"
|
||||
preserve={false}
|
||||
initialValues={{ displayName: item.displayName || item.username }}
|
||||
ref={form.bind}
|
||||
className="modal-form"
|
||||
>
|
||||
<Form.Item
|
||||
name="displayName"
|
||||
label="姓名(展示用,可输入中文)"
|
||||
rules={[{ max: 32, message: '姓名最多 32 个字符' }]}
|
||||
extra="留空时回退显示登录账号"
|
||||
>
|
||||
<Input placeholder="例如:张三" maxLength={32} showCount />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
),
|
||||
okText: '保存',
|
||||
cancelText: '取消',
|
||||
centered: true,
|
||||
onOk: async () => {
|
||||
const values = await form.validate()
|
||||
setActionLoadingId(item.userId)
|
||||
try {
|
||||
const response = await updateAdminUserDisplayName(item.userId, {
|
||||
displayName: values.displayName?.trim() || '',
|
||||
})
|
||||
if (item.userId === currentUserId) {
|
||||
setAdminDisplayName(response.data.user.displayName)
|
||||
}
|
||||
message.success('用户姓名已更新')
|
||||
await Promise.all([
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-session'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-users'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-audit-logs'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-login-logs'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-fund-overview'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-fund-accounts'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-fund-expenses'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-fund-reconciliations'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-fund-account-detail'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-fund-options'] }),
|
||||
queryClient.invalidateQueries({ queryKey: ['admin-fund-my'] }),
|
||||
])
|
||||
await reloadUsers()
|
||||
} catch (error) {
|
||||
message.error(error instanceof Error ? error.message : '更新用户姓名失败')
|
||||
} finally {
|
||||
setActionLoadingId(null)
|
||||
}
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function openResetPasswordModal(item: AdminUserListItem) {
|
||||
const form = ModalFormStore.create<{ password: string }>()
|
||||
|
||||
@@ -197,7 +264,7 @@ export default function AdminUsersPage() {
|
||||
>
|
||||
<Form.Item
|
||||
name="password"
|
||||
label={`请输入 ${item.username} 的新密码`}
|
||||
label={`请输入 ${displayNameOf(item)} 的新密码`}
|
||||
rules={[
|
||||
{ required: true, message: '请输入新密码' },
|
||||
{ min: 8, message: '密码至少 8 位' },
|
||||
@@ -251,16 +318,22 @@ export default function AdminUsersPage() {
|
||||
align: 'center',
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'username',
|
||||
minWidth: 150,
|
||||
render: (value, row) => (
|
||||
title: '姓名',
|
||||
dataIndex: 'displayName',
|
||||
minWidth: 160,
|
||||
render: (value: string, row) => (
|
||||
<Space>
|
||||
<Typography.Text strong>{value}</Typography.Text>
|
||||
<Typography.Text strong>{value || row.username}</Typography.Text>
|
||||
{row.userId === currentUserId ? <Tag>当前账号</Tag> : null}
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: '账号',
|
||||
dataIndex: 'username',
|
||||
minWidth: 140,
|
||||
render: (value: string) => <Typography.Text type="secondary">{value}</Typography.Text>,
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'role',
|
||||
@@ -287,7 +360,7 @@ export default function AdminUsersPage() {
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
width: 310,
|
||||
width: 390,
|
||||
render: (_, row) => {
|
||||
const isCurrent = row.userId === currentUserId
|
||||
const roleMenuItems: MenuProps['items'] = USER_ROLE_OPTIONS.map((item) => ({
|
||||
@@ -298,6 +371,12 @@ export default function AdminUsersPage() {
|
||||
|
||||
return (
|
||||
<Space wrap>
|
||||
<Button
|
||||
loading={actionLoadingId === row.userId}
|
||||
onClick={() => openEditDisplayNameModal(row)}
|
||||
>
|
||||
改名
|
||||
</Button>
|
||||
<Dropdown
|
||||
disabled={isCurrent}
|
||||
menu={{
|
||||
@@ -333,7 +412,7 @@ export default function AdminUsersPage() {
|
||||
<section className="page-stack">
|
||||
<PageHeader
|
||||
title="后台用户"
|
||||
description="管理员可维护后台账号、角色和启停状态,避免继续使用单一口令。"
|
||||
description="管理员可维护后台账号、姓名、角色和启停状态,避免继续使用单一口令。"
|
||||
/>
|
||||
|
||||
<Card className="users-action-card">
|
||||
@@ -347,7 +426,7 @@ export default function AdminUsersPage() {
|
||||
className="filter-form users-action-form"
|
||||
>
|
||||
<Form.Item name="username">
|
||||
<Input allowClear placeholder="账号筛选" className="users-input-md" />
|
||||
<Input allowClear placeholder="姓名/账号筛选" className="users-input-md" />
|
||||
</Form.Item>
|
||||
<Form.Item name="role">
|
||||
<Select
|
||||
@@ -387,8 +466,11 @@ export default function AdminUsersPage() {
|
||||
onFinish={submitCreate}
|
||||
className="filter-form users-action-form"
|
||||
>
|
||||
<Form.Item name="displayName" rules={[{ max: 32, message: '姓名最多 32 个字符' }]}>
|
||||
<Input placeholder="姓名(可中文)" className="users-input-md" maxLength={32} />
|
||||
</Form.Item>
|
||||
<Form.Item name="username" rules={[{ required: true, message: '请输入新账号' }]}>
|
||||
<Input placeholder="新账号" className="users-input-md" />
|
||||
<Input placeholder="登录账号(英文/数字)" className="users-input-md" />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="password"
|
||||
@@ -424,7 +506,7 @@ export default function AdminUsersPage() {
|
||||
title="用户列表"
|
||||
extra={
|
||||
<Typography.Text type="secondary">
|
||||
共 {data?.pagination.total || 0} 个账号
|
||||
共 {data?.pagination.total || 0} 个用户
|
||||
</Typography.Text>
|
||||
}
|
||||
>
|
||||
@@ -434,7 +516,7 @@ export default function AdminUsersPage() {
|
||||
columns={columns}
|
||||
dataSource={data?.items || []}
|
||||
locale={{ emptyText: <Empty description="暂无用户" /> }}
|
||||
scroll={{ x: 1060 }}
|
||||
scroll={{ x: 1220 }}
|
||||
pagination={buildAdminTablePagination({
|
||||
current: data?.pagination.page || page,
|
||||
pageSize: data?.pagination.pageSize || pageSize,
|
||||
@@ -487,6 +569,10 @@ function formatRoleLabel(role: AdminRole) {
|
||||
return '普通运营'
|
||||
}
|
||||
|
||||
function displayNameOf(user: AdminUserListItem) {
|
||||
return user.displayName || user.username
|
||||
}
|
||||
|
||||
function cleanParams(params: Record<string, unknown>) {
|
||||
const next = new URLSearchParams()
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
|
||||
Reference in New Issue
Block a user