init
This commit is contained in:
482
frontend/src/pages/Users.tsx
Normal file
482
frontend/src/pages/Users.tsx
Normal file
@ -0,0 +1,482 @@
|
||||
import React, { useEffect, useMemo, useState } from 'react'
|
||||
import { Button, Form, Input, Modal, Popconfirm, Select, Space, Table, Tag, Typography, message, TreeSelect } from 'antd'
|
||||
import api from '../utils/axios'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { formatDateTime } from '../utils/datetime'
|
||||
import { EditOutlined, DeleteOutlined, KeyOutlined, UserOutlined } from '@ant-design/icons'
|
||||
import { usePermission } from '../utils/permission'
|
||||
import PageHeader from '../components/PageHeader'
|
||||
|
||||
interface UserItem {
|
||||
id: number
|
||||
username: string
|
||||
nickname?: string
|
||||
status?: number
|
||||
created_at?: string
|
||||
}
|
||||
|
||||
// 简单的 {id, name} 类型守卫,便于从未知数组安全映射
|
||||
type IdName = { id: number; name: string }
|
||||
const isIdName = (o: unknown): o is IdName => {
|
||||
if (typeof o !== 'object' || o === null) return false
|
||||
const rec = o as Record<string, unknown>
|
||||
return typeof rec.id === 'number' && typeof rec.name === 'string'
|
||||
}
|
||||
|
||||
// 部门基础类型与守卫(包含 parent_id 便于构建树)
|
||||
type DeptBasic = { id: number; name: string; parent_id?: number }
|
||||
const isDeptBasic = (o: unknown): o is DeptBasic => {
|
||||
if (typeof o !== 'object' || o === null) return false
|
||||
const rec = o as Record<string, unknown>
|
||||
const okId = typeof rec.id === 'number'
|
||||
const okName = typeof rec.name === 'string'
|
||||
const okPid = rec.parent_id === undefined || typeof rec.parent_id === 'number'
|
||||
return okId && okName && okPid
|
||||
}
|
||||
|
||||
export default function Users(){
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [data, setData] = useState([] as UserItem[])
|
||||
const [page, setPage] = useState(1)
|
||||
const [pageSize, setPageSize] = useState(10)
|
||||
const [total, setTotal] = useState(0)
|
||||
const [keyword, setKeyword] = useState('')
|
||||
|
||||
const [createOpen, setCreateOpen] = useState(false)
|
||||
const [editOpen, setEditOpen] = useState(false)
|
||||
const [pwdOpen, setPwdOpen] = useState(false)
|
||||
const [positionsOpen, setPositionsOpen] = useState(false)
|
||||
const [current, setCurrent] = useState(null as UserItem | null)
|
||||
const [currentUserId, setCurrentUserId] = useState(null as number | null)
|
||||
|
||||
const [form] = Form.useForm()
|
||||
const [editForm] = Form.useForm()
|
||||
const [pwdForm] = Form.useForm()
|
||||
const [searchForm] = Form.useForm()
|
||||
|
||||
// 分配角色(移到编辑弹窗内)
|
||||
const [selectedRoleIds, setSelectedRoleIds] = useState([] as number[])
|
||||
const [allRoles, setAllRoles] = useState([] as { id: number; name: string }[])
|
||||
|
||||
// 分配部门(移到编辑弹窗内)
|
||||
const [selectedDeptIds, setSelectedDeptIds] = useState([] as number[])
|
||||
const [allDepts, setAllDepts] = useState([] as DeptBasic[])
|
||||
|
||||
// 岗位分配相关状态
|
||||
const [positionOptions, setPositionOptions] = useState([] as { label: string; value: number }[])
|
||||
const [userPositions, setUserPositions] = useState([] as number[])
|
||||
// 新增/编辑弹窗内的岗位选择
|
||||
const [createPositionIds, setCreatePositionIds] = useState([] as number[])
|
||||
const [editPositionIds, setEditPositionIds] = useState([] as number[])
|
||||
|
||||
// 权限判断
|
||||
const { has } = usePermission()
|
||||
|
||||
// 根据 allDepts 构建部门树(用于选择多个部门)
|
||||
type DeptTreeNode = { title: string; value: number; key: number; children?: DeptTreeNode[] }
|
||||
const deptTreeData: DeptTreeNode[] = useMemo(() => {
|
||||
const map = new Map<number, DeptTreeNode & { children: DeptTreeNode[] }>()
|
||||
allDepts.forEach((d: DeptBasic) => map.set(d.id, { title: d.name, value: d.id, key: d.id, children: [] }))
|
||||
const roots: (DeptTreeNode & { children: DeptTreeNode[] })[] = []
|
||||
allDepts.forEach((d: DeptBasic) => {
|
||||
const node = map.get(d.id)!
|
||||
if (d.parent_id && map.has(d.parent_id)) {
|
||||
const parent = map.get(d.parent_id)
|
||||
if (parent) parent.children.push(node)
|
||||
} else {
|
||||
roots.push(node)
|
||||
}
|
||||
})
|
||||
return roots
|
||||
}, [allDepts])
|
||||
|
||||
// 获取岗位选项
|
||||
const fetchPositionOptions = async () => {
|
||||
try {
|
||||
const { data } = await api.get('/positions', { params: { page: 1, page_size: 1000 } })
|
||||
if (data?.code === 0) {
|
||||
setPositionOptions((data.data.items || []).map((it: any) => ({ label: it.name, value: it.id })))
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('获取岗位列表失败:', e)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
fetchPositionOptions()
|
||||
}, [])
|
||||
|
||||
const fetchUsers = async (p: number = page, ps: number = pageSize, kw: string = keyword) => {
|
||||
setLoading(true)
|
||||
try{
|
||||
const { data } = await api.get(`/users`, { params: { page: p, page_size: ps, keyword: kw } })
|
||||
if(data?.code === 0){
|
||||
const resp = data.data
|
||||
setData(resp.items || [])
|
||||
setTotal(resp.total || 0)
|
||||
setPage(resp.page || p)
|
||||
setPageSize(resp.page_size || ps)
|
||||
}else{
|
||||
throw new Error(data?.message || '获取用户失败')
|
||||
}
|
||||
}catch(e: any){
|
||||
message.error(e.message || '获取用户失败')
|
||||
}finally{
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => { fetchUsers(1, pageSize, keyword) }, [])
|
||||
|
||||
const onCreate = async () => {
|
||||
form.resetFields()
|
||||
// 新增用户:预置清空角色与部门选择,并加载候选数据
|
||||
setSelectedRoleIds([])
|
||||
setSelectedDeptIds([])
|
||||
setCreatePositionIds([])
|
||||
setCreateOpen(true)
|
||||
try {
|
||||
await fetchPositionOptions()
|
||||
const [rolesRes, deptsRes] = await Promise.all([
|
||||
api.get('/roles', { params: { page: 1, page_size: 1000 } }),
|
||||
api.get('/departments', { params: { keyword: '' } })
|
||||
])
|
||||
if (rolesRes.data?.code === 0) {
|
||||
const rolesItemsSrc = Array.isArray(rolesRes.data?.data?.items) ? (rolesRes.data.data.items as unknown[]) : []
|
||||
const roleItems = rolesItemsSrc.filter(isIdName).map(({ id, name }) => ({ id, name }))
|
||||
setAllRoles(roleItems)
|
||||
}
|
||||
if (deptsRes.data?.code === 0) {
|
||||
const deptsSrc = Array.isArray(deptsRes.data?.data) ? (deptsRes.data.data as unknown[]) : []
|
||||
const deptBasics = deptsSrc.filter(isDeptBasic)
|
||||
setAllDepts(deptBasics)
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '加载角色/部门失败')
|
||||
}
|
||||
}
|
||||
|
||||
const handleCreate = async () => {
|
||||
try{
|
||||
const values = await form.validateFields()
|
||||
const { data } = await api.post('/users', values)
|
||||
if(data?.code !== 0){ throw new Error(data?.message || '创建失败') }
|
||||
const uid = typeof data?.data?.id === 'number' ? data.data.id : undefined
|
||||
if (uid) {
|
||||
const [rolesSave, deptsSave, posSave] = await Promise.all([
|
||||
api.put(`/users/${uid}/roles`, { ids: selectedRoleIds }),
|
||||
api.put(`/users/${uid}/departments`, { ids: selectedDeptIds }),
|
||||
api.put(`/users/${uid}/positions`, { ids: createPositionIds })
|
||||
])
|
||||
if (rolesSave.data?.code !== 0) throw new Error(rolesSave.data?.message || '保存角色失败')
|
||||
if (deptsSave.data?.code !== 0) throw new Error(deptsSave.data?.message || '保存部门失败')
|
||||
if (posSave.data?.code !== 0) throw new Error(posSave.data?.message || '保存岗位失败')
|
||||
} else {
|
||||
message.warning('创建成功,但未获取到用户ID,未能分配角色/部门/岗位')
|
||||
}
|
||||
message.success('创建成功')
|
||||
setCreateOpen(false)
|
||||
fetchUsers(1, pageSize)
|
||||
}catch(e: any){ if(e?.errorFields) return; message.error(e.message || '创建失败') }
|
||||
}
|
||||
|
||||
const onEdit = async (record: UserItem) => {
|
||||
setCurrent(record)
|
||||
editForm.setFieldsValue({ nickname: record.nickname, status: record.status })
|
||||
setEditOpen(true)
|
||||
// 加载该用户已分配的角色/部门/岗位及候选列表
|
||||
try{
|
||||
await fetchPositionOptions()
|
||||
const [roleIdsRes, rolesRes, deptIdsRes, deptsRes, posIdsRes] = await Promise.all([
|
||||
api.get(`/users/${record.id}/roles`),
|
||||
api.get('/roles', { params: { page: 1, page_size: 1000 } }),
|
||||
api.get(`/users/${record.id}/departments`),
|
||||
api.get('/departments', { params: { keyword: '' } }),
|
||||
api.get(`/users/${record.id}/positions`)
|
||||
])
|
||||
if(roleIdsRes.data?.code !== 0) throw new Error(roleIdsRes.data?.message || '获取用户角色失败')
|
||||
if(rolesRes.data?.code !== 0) throw new Error(rolesRes.data?.message || '获取角色列表失败')
|
||||
if(deptIdsRes.data?.code !== 0) throw new Error(deptIdsRes.data?.message || '获取用户部门失败')
|
||||
if(deptsRes.data?.code !== 0) throw new Error(deptsRes.data?.message || '获取部门列表失败')
|
||||
if(posIdsRes.data?.code !== 0) throw new Error(posIdsRes.data?.message || '获取用户岗位失败')
|
||||
const roleIds = Array.isArray(roleIdsRes.data?.data) ? (roleIdsRes.data.data as unknown[]).filter((n): n is number => typeof n === 'number') : []
|
||||
setSelectedRoleIds(roleIds)
|
||||
const rolesItemsSrc = Array.isArray(rolesRes.data?.data?.items) ? rolesRes.data.data.items as unknown[] : []
|
||||
const roleItems = rolesItemsSrc.filter(isIdName).map(({ id, name }) => ({ id, name }))
|
||||
setAllRoles(roleItems)
|
||||
const deptIds = Array.isArray(deptIdsRes.data?.data) ? (deptIdsRes.data.data as unknown[]).filter((n): n is number => typeof n === 'number') : []
|
||||
setSelectedDeptIds(deptIds)
|
||||
const deptsSrc = Array.isArray(deptsRes.data?.data) ? (deptsRes.data.data as unknown[]) : []
|
||||
const deptBasics = deptsSrc.filter(isDeptBasic)
|
||||
setAllDepts(deptBasics)
|
||||
const posIds = Array.isArray(posIdsRes.data?.data) ? (posIdsRes.data.data as unknown[]).filter((n): n is number => typeof n === 'number') : []
|
||||
setEditPositionIds(posIds)
|
||||
}catch(e: any){ message.error(e.message || '加载编辑数据失败') }
|
||||
}
|
||||
|
||||
const handleEdit = async () => {
|
||||
if(!current) return
|
||||
try{
|
||||
const values = await editForm.validateFields()
|
||||
// 先保存基础信息
|
||||
const { data: upd } = await api.put(`/users/${current!.id}`, values)
|
||||
if(upd?.code !== 0) throw new Error(upd?.message || '更新失败')
|
||||
// 再保存角色与部门
|
||||
const [rolesSave, deptsSave] = await Promise.all([
|
||||
api.put(`/users/${current.id}/roles`, { ids: selectedRoleIds }),
|
||||
api.put(`/users/${current.id}/departments`, { ids: selectedDeptIds }),
|
||||
api.put(`/users/${current.id}/positions`, { ids: editPositionIds })
|
||||
])
|
||||
if(rolesSave.data?.code !== 0) throw new Error(rolesSave.data?.message || '保存角色失败')
|
||||
if(deptsSave.data?.code !== 0) throw new Error(deptsSave.data?.message || '保存部门失败')
|
||||
message.success('更新成功')
|
||||
setEditOpen(false)
|
||||
fetchUsers(page, pageSize)
|
||||
}catch(e: any){ if(e?.errorFields) return; message.error(e.message || '更新失败') }
|
||||
}
|
||||
|
||||
const onResetPwd = (record: UserItem) => {
|
||||
setCurrent(record)
|
||||
pwdForm.resetFields()
|
||||
setPwdOpen(true)
|
||||
}
|
||||
|
||||
const handleResetPwd = async () => {
|
||||
try{
|
||||
const values = await pwdForm.validateFields()
|
||||
const { data } = await api.post(`/users/${current!.id}/reset_password`, values)
|
||||
if(data?.code === 0){
|
||||
message.success('密码已重置')
|
||||
setPwdOpen(false)
|
||||
}else{ throw new Error(data?.message || '重置失败') }
|
||||
}catch(e: any){ if(e?.errorFields) return; message.error(e.message || '重置失败') }
|
||||
}
|
||||
|
||||
// 岗位分配相关方法
|
||||
const openPositions = async (userId: number) => {
|
||||
setCurrentUserId(userId)
|
||||
try {
|
||||
const { data } = await api.get(`/users/${userId}/positions`)
|
||||
if (data?.code === 0) {
|
||||
setUserPositions(data.data || [])
|
||||
} else {
|
||||
throw new Error(data?.message || '获取用户岗位失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '获取用户岗位失败')
|
||||
setUserPositions([])
|
||||
}
|
||||
setPositionsOpen(true)
|
||||
}
|
||||
|
||||
const savePositions = async () => {
|
||||
if (!currentUserId) return
|
||||
try {
|
||||
const { data } = await api.put(`/users/${currentUserId}/positions`, { ids: userPositions })
|
||||
if (data?.code === 0) {
|
||||
message.success('岗位分配成功')
|
||||
setPositionsOpen(false)
|
||||
} else {
|
||||
throw new Error(data?.message || '保存岗位失败')
|
||||
}
|
||||
} catch (e: any) {
|
||||
message.error(e.message || '保存岗位失败')
|
||||
}
|
||||
}
|
||||
|
||||
const columns: ColumnsType<UserItem> = useMemo(() => [
|
||||
{ title: 'ID', dataIndex: 'id', width: 80 },
|
||||
{ title: '用户名', dataIndex: 'username' },
|
||||
{ title: '昵称', dataIndex: 'nickname' },
|
||||
{ title: '状态', dataIndex: 'status', render: (v: number) => v === 1 ? <Tag color="green">启用</Tag> : <Tag>禁用</Tag> },
|
||||
{ title: '创建时间', dataIndex: 'created_at', render: (v: any) => formatDateTime(v) },
|
||||
{ title: '操作', key: 'actions', width: 360, render: (_: any, r: UserItem) => (
|
||||
<Space>
|
||||
{has('system:user:update') && (
|
||||
<a className="action-link" onClick={() => onEdit(r)}>
|
||||
<EditOutlined />
|
||||
<span>编辑</span>
|
||||
</a>
|
||||
)}
|
||||
{has('system:user:reset') && (
|
||||
<a className="action-link" onClick={() => onResetPwd(r)}>
|
||||
<KeyOutlined />
|
||||
<span>重置密码</span>
|
||||
</a>
|
||||
)}
|
||||
{has('system:user:assignPosition') && (
|
||||
<a className="action-link" onClick={() => openPositions(r.id)}>
|
||||
<UserOutlined />
|
||||
<span>分配岗位</span>
|
||||
</a>
|
||||
)}
|
||||
{has('system:user:delete') && (
|
||||
<Popconfirm title={`确认删除用户「${r.username}」?`} onConfirm={async ()=>{ try{ const { data } = await api.delete(`/users/${r.id}`); if(data?.code===0){ message.success('删除成功'); fetchUsers(page, pageSize, keyword) } else { throw new Error(data?.message||'删除失败') } }catch(e:any){ message.error(e.message||'删除失败') } }}>
|
||||
<a className="action-link action-danger">
|
||||
<DeleteOutlined />
|
||||
<span>删除</span>
|
||||
</a>
|
||||
</Popconfirm>
|
||||
)}
|
||||
</Space>
|
||||
)},
|
||||
], [page, pageSize, keyword, has])
|
||||
|
||||
return (
|
||||
<div>
|
||||
<PageHeader items={["系统管理","用户管理"]} title="" />
|
||||
<Form form={searchForm} layout="inline" onFinish={(vals: any)=>{ const kw = String(vals?.keyword ?? '').trim(); setKeyword(kw); fetchUsers(1, pageSize, kw) }} style={{ marginBottom: 12 }}>
|
||||
<Form.Item name="keyword">
|
||||
<Input allowClear placeholder="搜索用户名/昵称" style={{ width: 320 }} />
|
||||
</Form.Item>
|
||||
<Form.Item>
|
||||
<Space>
|
||||
<Button type="primary" htmlType="submit">搜索</Button>
|
||||
<Button onClick={()=>{ searchForm.resetFields(); setKeyword(''); fetchUsers(1, pageSize, '') }}>重置</Button>
|
||||
{has('system:user:create') && (
|
||||
<Button type="primary" onClick={onCreate}>新增</Button>
|
||||
)}
|
||||
</Space>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
<Table<UserItem>
|
||||
rowKey="id"
|
||||
loading={loading}
|
||||
dataSource={data}
|
||||
columns={columns}
|
||||
pagination={{ current: page, pageSize, total, showSizeChanger: true, onChange: (p: number, ps?: number) => fetchUsers(p, ps ?? pageSize, keyword) }}
|
||||
/>
|
||||
<Typography.Paragraph type="secondary" style={{ marginTop: 12 }}>
|
||||
提示:此页面已支持分页、创建、编辑与重置密码。
|
||||
</Typography.Paragraph>
|
||||
|
||||
<Modal title="新增用户" open={createOpen} onOk={handleCreate} onCancel={() => setCreateOpen(false)} okText="创建" width={840}>
|
||||
<Form form={form} layout="horizontal" labelCol={{ span: 2 }} wrapperCol={{ span: 22 }} labelAlign="right">
|
||||
<Form.Item name="username" label="用户名" rules={[{ required: true }]}>
|
||||
<Input placeholder="请输入用户名" />
|
||||
</Form.Item>
|
||||
<Form.Item name="password" label="密码" rules={[{ required: true }]}>
|
||||
<Input.Password placeholder="请输入密码" />
|
||||
</Form.Item>
|
||||
<Form.Item name="nickname" label="昵称">
|
||||
<Input placeholder="请输入昵称" />
|
||||
</Form.Item>
|
||||
<Form.Item name="status" label="状态" initialValue={1}>
|
||||
<Select options={[{label:'启用', value:1},{label:'禁用', value:0}]} />
|
||||
</Form.Item>
|
||||
<Form.Item label="角色">
|
||||
<Select
|
||||
mode="multiple"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择角色"
|
||||
value={selectedRoleIds}
|
||||
options={allRoles.map((r: { id: number; name: string }) => ({ label: r.name, value: r.id }))}
|
||||
onChange={(vals: number[]) => setSelectedRoleIds(vals)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="部门">
|
||||
<TreeSelect
|
||||
allowClear
|
||||
multiple
|
||||
treeCheckable
|
||||
placeholder="选择部门(支持多选)"
|
||||
style={{ width: '100%' }}
|
||||
treeData={deptTreeData}
|
||||
treeDefaultExpandAll
|
||||
showSearch
|
||||
filterTreeNode={(input: string, node: any) => String(node?.title ?? '').toLowerCase().includes(String(input).toLowerCase())}
|
||||
value={selectedDeptIds}
|
||||
onChange={(vals) => setSelectedDeptIds(Array.isArray(vals) ? (vals as (string | number)[]).map(v => Number(v)) : [])}
|
||||
/>
|
||||
</Form.Item>
|
||||
{has('system:user:assignPosition') && (
|
||||
<Form.Item label="岗位">
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
placeholder="选择岗位"
|
||||
value={createPositionIds}
|
||||
options={positionOptions}
|
||||
style={{ width: '100%' }}
|
||||
onChange={setCreatePositionIds}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal title="编辑用户" open={editOpen} onOk={handleEdit} onCancel={() => setEditOpen(false)} okText="保存" width={840}>
|
||||
<Form form={editForm} layout="horizontal" labelCol={{ span: 2 }} wrapperCol={{ span: 22 }} labelAlign="right">
|
||||
<Form.Item name="nickname" label="昵称">
|
||||
<Input placeholder="请输入昵称" />
|
||||
</Form.Item>
|
||||
<Form.Item name="status" label="状态">
|
||||
<Select options={[{label:'启用', value:1},{label:'禁用', value:0}]} />
|
||||
</Form.Item>
|
||||
<Form.Item label="角色">
|
||||
<Select
|
||||
mode="multiple"
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择角色"
|
||||
value={selectedRoleIds}
|
||||
options={allRoles.map((r: { id: number; name: string }) => ({ label: r.name, value: r.id }))}
|
||||
onChange={(vals: number[]) => setSelectedRoleIds(vals)}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="部门">
|
||||
<TreeSelect
|
||||
allowClear
|
||||
multiple
|
||||
treeCheckable
|
||||
placeholder="选择部门(支持多选)"
|
||||
style={{ width: '100%' }}
|
||||
treeData={deptTreeData}
|
||||
treeDefaultExpandAll
|
||||
showSearch
|
||||
filterTreeNode={(input: string, node: any) => String(node?.title ?? '').toLowerCase().includes(String(input).toLowerCase())}
|
||||
value={selectedDeptIds}
|
||||
onChange={(vals) => setSelectedDeptIds(Array.isArray(vals) ? (vals as (string | number)[]).map(v => Number(v)) : [])}
|
||||
/>
|
||||
</Form.Item>
|
||||
{has('system:user:assignPosition') && (
|
||||
<Form.Item label="岗位">
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
placeholder="选择岗位"
|
||||
value={editPositionIds}
|
||||
options={positionOptions}
|
||||
style={{ width: '100%' }}
|
||||
onChange={setEditPositionIds}
|
||||
/>
|
||||
</Form.Item>
|
||||
)}
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal title={`重置密码${current ? `(${current.username})` : ''}`} open={pwdOpen} onOk={handleResetPwd} onCancel={() => setPwdOpen(false)} okText="重置">
|
||||
<Form form={pwdForm} layout="vertical">
|
||||
<Form.Item name="password" label="新密码" rules={[{ required: true }]}>
|
||||
<Input.Password placeholder="请输入新密码" />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
|
||||
<Modal title="分配岗位" open={positionsOpen} onOk={savePositions} onCancel={() => setPositionsOpen(false)} okText="保存">
|
||||
<Form layout="vertical">
|
||||
<Form.Item label="选择岗位">
|
||||
<Select
|
||||
mode="multiple"
|
||||
allowClear
|
||||
value={userPositions}
|
||||
onChange={setUserPositions}
|
||||
options={positionOptions}
|
||||
style={{ width: '100%' }}
|
||||
placeholder="选择岗位"
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user