feat: manage merchant members

This commit is contained in:
yml2213
2026-08-13 11:45:55 +08:00
parent a4791decb0
commit aea65a2fd3
7 changed files with 251 additions and 1 deletions
+4
View File
@@ -106,6 +106,10 @@ export const merchantApi = {
request.get('/merchant/members').then((r) => r.data.data as MerchantMember[]),
addMember: (data: { user_id?: number; username?: string; password?: string; nickname?: string; role: string; is_default?: boolean }) =>
request.post('/merchant/members', data).then((r) => r.data.data as MerchantMember),
updateMember: (id: number, data: { role: string; status: number; is_default: boolean }) =>
request.patch(`/merchant/members/${id}`, data).then((r) => r.data.data as MerchantMember),
removeMember: (id: number) =>
request.delete(`/merchant/members/${id}`).then((r) => r.data.data),
roles: () => request.get('/merchant/roles').then((r) => r.data.data as MerchantRole[]),
createRole: (data: { code: string; name: string; permissions: string[] }) =>
request.post('/merchant/roles', data).then((r) => r.data.data as MerchantRole),
+67 -1
View File
@@ -7,6 +7,7 @@ import {
InputNumber,
Modal,
Select,
Switch,
Tabs,
Typography,
message,
@@ -53,7 +54,7 @@ import {
} from './merchantCenterTabs'
export default function MerchantCenter({ fixedTab, title = '商户中心' }: MerchantCenterProps = {}) {
const { isAdmin } = useAuth()
const { isAdmin, user } = useAuth()
const location = useLocation()
const navigate = useNavigate()
const routeTab = useMemo(() => fixedTab ?? tabFromSearch(location.search), [fixedTab, location.search])
@@ -75,6 +76,7 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
const [apiCredential, setApiCredential] = useState<ApiCredential | null>(null)
const [callbackCredential, setCallbackCredential] = useState<CallbackCredential | null>(null)
const [memberOpen, setMemberOpen] = useState(false)
const [editingMember, setEditingMember] = useState<MerchantMember | null>(null)
const [roleOpen, setRoleOpen] = useState(false)
const [editingRole, setEditingRole] = useState<MerchantRole | null>(null)
const [manualOrderOpen, setManualOrderOpen] = useState(false)
@@ -85,6 +87,7 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
const [apiClientForm] = Form.useForm()
const [callbackForm] = Form.useForm()
const [memberForm] = Form.useForm()
const [memberEditForm] = Form.useForm()
const [roleForm] = Form.useForm()
const [manualOrderForm] = Form.useForm()
@@ -420,6 +423,47 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
}
}
const submitMemberEdit = async () => {
if (!editingMember) return
try {
const values = await memberEditForm.validateFields()
await merchantApi.updateMember(editingMember.id, {
role: values.role,
status: values.status,
is_default: values.is_default || false,
})
setEditingMember(null)
message.success('成员已更新')
loadMembers()
} catch (e) {
message.error(e instanceof Error ? e.message : '更新失败')
}
}
const toggleMemberStatus = async (member: MerchantMember) => {
try {
await merchantApi.updateMember(member.id, {
role: member.role,
status: member.status === 1 ? 0 : 1,
is_default: member.is_default,
})
message.success(member.status === 1 ? '员工已停用' : '员工已启用')
loadMembers()
} catch (e) {
message.error(e instanceof Error ? e.message : '操作失败')
}
}
const removeMember = async (member: MerchantMember) => {
try {
await merchantApi.removeMember(member.id)
message.success('员工已从当前商户移除')
loadMembers()
} catch (e) {
message.error(e instanceof Error ? e.message : '移除失败')
}
}
const submitRole = async () => {
const values = await roleForm.validateFields()
try {
@@ -494,6 +538,13 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
})
setRoleOpen(true)
}}
onEditMember={(member) => {
setEditingMember(member)
memberEditForm.setFieldsValue({ role: member.role, status: member.status, is_default: member.is_default })
}}
onToggleMemberStatus={toggleMemberStatus}
onRemoveMember={removeMember}
currentUserID={user?.id}
/>
) },
]
@@ -614,6 +665,21 @@ export default function MerchantCenter({ fixedTab, title = '商户中心' }: Mer
</Form>
</Modal>
<Modal title={editingMember ? `编辑员工:${editingMember.user?.nickname || editingMember.user?.username || ''}` : '编辑员工'} open={!!editingMember} onOk={submitMemberEdit} onCancel={() => setEditingMember(null)} destroyOnClose>
<Form form={memberEditForm} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item label="账号"><Input value={editingMember?.user?.username} disabled /></Form.Item>
<Form.Item name="role" label="团队角色" rules={[{ required: true }]}>
<Select options={roles.map((item) => ({ value: item.code, label: item.name }))} />
</Form.Item>
<Form.Item name="status" label="状态" rules={[{ required: true }]}>
<Select options={[{ value: 1, label: '启用' }, { value: 0, label: '停用' }]} />
</Form.Item>
<Form.Item name="is_default" label="设为默认商户" valuePropName="checked">
<Switch />
</Form.Item>
</Form>
</Modal>
<Modal title={editingRole ? `编辑角色:${editingRole.name}` : '新建团队角色'} open={roleOpen} onOk={submitRole} onCancel={() => { setRoleOpen(false); setEditingRole(null) }} destroyOnClose>
<Form form={roleForm} layout="vertical" style={{ marginTop: 16 }}>
<Form.Item name="name" label="角色名称" rules={[{ required: true }]}><Input maxLength={64} placeholder="例如:客服" /></Form.Item>
+27
View File
@@ -668,6 +668,10 @@ export function MembersTab({
onOpenAdd,
onOpenCreateRole,
onEditRole,
onEditMember,
onToggleMemberStatus,
onRemoveMember,
currentUserID,
}: {
loading: boolean
members: MerchantMember[]
@@ -677,6 +681,10 @@ export function MembersTab({
onOpenAdd: () => void
onOpenCreateRole: () => void
onEditRole: (role: MerchantRole) => void
onEditMember: (member: MerchantMember) => void
onToggleMemberStatus: (member: MerchantMember) => void
onRemoveMember: (member: MerchantMember) => void
currentUserID?: number
}) {
const roleNames = new Map(roles.map((item) => [item.code, item.name]))
const memberColumns: ColumnsType<MerchantMember> = [
@@ -685,6 +693,25 @@ export function MembersTab({
{ title: '角色', dataIndex: 'role', width: 140, render: (value) => <Tag color="blue">{roleNames.get(value) || roleText(value)}</Tag> },
{ title: '默认', dataIndex: 'is_default', width: 80, render: (v) => (v ? <Tag color="blue"></Tag> : '-') },
{ title: '状态', dataIndex: 'status', width: 90, render: (v) => (v === 1 ? <Tag color="green"></Tag> : <Tag></Tag>) },
{
title: '操作',
width: 210,
render: (_, member) => {
const protectedMember = member.role === 'owner' || member.user_id === currentUserID
if (!canManage) return '-'
if (protectedMember) return <Typography.Text type="secondary"></Typography.Text>
const action = member.status === 1 ? '停用' : '启用'
return <Space size={0}>
<Button type="link" size="small" onClick={() => onEditMember(member)}></Button>
<Popconfirm title={`确认${action}该员工?`} onConfirm={() => onToggleMemberStatus(member)}>
<Button type="link" size="small">{action}</Button>
</Popconfirm>
<Popconfirm title="确认移除该员工?" description="仅移除当前商户成员关系,账号不会被删除。" onConfirm={() => onRemoveMember(member)} okButtonProps={{ danger: true }}>
<Button type="link" danger size="small"></Button>
</Popconfirm>
</Space>
},
},
]
return (