feat(web): Phase 5 - 业务页面迁移完成
完成所有业务页面从 Vue 3 到 React 19 的迁移: 页面迁移: - Login: 表单验证 + 认证集成 - Dashboard: 统计卡片 + G2 图表占位 - UserManagement: 表格 + 分页 + CRUD + 权限控制 - RoleManagement: 表格 + 弹窗 + TreeSelect 权限分配 - MenuManagement: 树形表格 + 层级菜单管理 - ConfigManagement: 参数配置 CRUD - DictManagement: 字典类型/数据双面板管理 - FileManagement: 文件上传 + 图片预览 - NoticeManagement: 通知公告 CRUD - LoginLog/OpLog/ExLog: 审计日志只读查询 - 403: 权限拒绝页面 API 层补充: - loginLog.ts: 新增 LoginLog/OpLog/ExLog 接口与 API - status.ts: 新增 userStatusMap/roleStatusMap/menuStatusMap/noticeStatusMap 路由修正: - routes.ts: 日志页面路径对齐实际目录结构 验证:tsc --noEmit 零错误,dev server 正常启动
This commit is contained in:
@@ -1,3 +1,108 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { Table, Button, Modal, Form, Input, InputNumber, Select, Tag, Space, message, Popconfirm, Card, Row, Col } from 'antd'
|
||||
import { PlusOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons'
|
||||
import type { ColumnsType } from 'antd/es/table'
|
||||
import { dictApi } from '@/api/dict'
|
||||
import type { DictType, DictData, CreateDictTypeRequest, CreateDictDataRequest, UpdateDictTypeRequest, UpdateDictDataRequest } from '@/api/dict'
|
||||
|
||||
export default function DictManagement() {
|
||||
return <div>DictManagement Page (TODO)</div>
|
||||
const [dictTypes, setDictTypes] = useState<DictType[]>([])
|
||||
const [dictData, setDictData] = useState<DictData[]>([])
|
||||
const [selectedType, setSelectedType] = useState<string>('')
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [typeModalOpen, setTypeModalOpen] = useState(false)
|
||||
const [dataModalOpen, setDataModalOpen] = useState(false)
|
||||
const [editingType, setEditingType] = useState<DictType | null>(null)
|
||||
const [editingData, setEditingData] = useState<DictData | null>(null)
|
||||
const [typeForm] = Form.useForm()
|
||||
const [dataForm] = Form.useForm()
|
||||
|
||||
useEffect(() => { loadDictTypes() }, [])
|
||||
|
||||
async function loadDictTypes() {
|
||||
try { const res = await dictApi.getTypes(); setDictTypes(Array.isArray(res) ? res : []) } catch {}
|
||||
}
|
||||
async function loadDictData(dictType: string) {
|
||||
setLoading(true)
|
||||
try { const res = await dictApi.getDataByType(dictType); setDictData(Array.isArray(res) ? res : []) } catch {}
|
||||
finally { setLoading(false) }
|
||||
}
|
||||
|
||||
function handleSelectType(type: string) { setSelectedType(type); loadDictData(type) }
|
||||
|
||||
async function handleTypeSubmit() {
|
||||
try {
|
||||
const values = await typeForm.validateFields()
|
||||
if (editingType) { await dictApi.updateType(editingType.id, values as UpdateDictTypeRequest); message.success('更新成功') }
|
||||
else { await dictApi.createType(values as CreateDictTypeRequest); message.success('创建成功') }
|
||||
setTypeModalOpen(false); loadDictTypes()
|
||||
} catch {}
|
||||
}
|
||||
async function handleDataSubmit() {
|
||||
try {
|
||||
const values = await dataForm.validateFields()
|
||||
if (editingData) { await dictApi.updateData(editingData.id, { ...values, dictType: selectedType } as UpdateDictDataRequest); message.success('更新成功') }
|
||||
else { await dictApi.createData({ ...values, dictType: selectedType } as CreateDictDataRequest); message.success('创建成功') }
|
||||
setDataModalOpen(false); loadDictData(selectedType)
|
||||
} catch {}
|
||||
}
|
||||
|
||||
const typeColumns: ColumnsType<DictType> = [
|
||||
{ title: '字典名称', dataIndex: 'dictName', key: 'dictName' },
|
||||
{ title: '字典类型', dataIndex: 'dictType', key: 'dictType', render: (v: string) => <a onClick={() => handleSelectType(v)}>{v}</a> },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', render: (v: number) => <Tag color={v === 1 ? 'green' : 'red'}>{v === 1 ? '正常' : '停用'}</Tag> },
|
||||
{ title: '操作', key: 'action', render: (_, record) => (
|
||||
<Space>
|
||||
<Button type="link" icon={<EditOutlined />} onClick={() => { setEditingType(record); typeForm.setFieldsValue(record); setTypeModalOpen(true) }} />
|
||||
<Popconfirm title="确认删除?" onConfirm={async () => { await dictApi.deleteType(record.id); message.success('删除成功'); loadDictTypes() }}><Button type="link" danger icon={<DeleteOutlined />} /></Popconfirm>
|
||||
</Space>
|
||||
)},
|
||||
]
|
||||
const dataColumns: ColumnsType<DictData> = [
|
||||
{ title: '字典标签', dataIndex: 'dictLabel', key: 'dictLabel' },
|
||||
{ title: '字典值', dataIndex: 'dictValue', key: 'dictValue' },
|
||||
{ title: '排序', dataIndex: 'sort', key: 'sort', width: 80 },
|
||||
{ title: '状态', dataIndex: 'status', key: 'status', render: (v: number) => <Tag color={v === 1 ? 'green' : 'red'}>{v === 1 ? '正常' : '停用'}</Tag> },
|
||||
{ title: '操作', key: 'action', render: (_, record) => (
|
||||
<Space>
|
||||
<Button type="link" icon={<EditOutlined />} onClick={() => { setEditingData(record); dataForm.setFieldsValue(record); setDataModalOpen(true) }} />
|
||||
<Popconfirm title="确认删除?" onConfirm={async () => { await dictApi.deleteData(record.id); message.success('删除成功'); loadDictData(selectedType) }}><Button type="link" danger icon={<DeleteOutlined />} /></Popconfirm>
|
||||
</Space>
|
||||
)},
|
||||
]
|
||||
|
||||
return (
|
||||
<div style={{ padding: 24 }}>
|
||||
<Row gutter={16}>
|
||||
<Col span={10}>
|
||||
<Card title="字典类型" extra={<Button type="primary" icon={<PlusOutlined />} onClick={() => { setEditingType(null); typeForm.resetFields(); setTypeModalOpen(true) }}>新增</Button>}>
|
||||
<Table<DictType> rowKey="id" columns={typeColumns} dataSource={dictTypes} pagination={false} size="small" />
|
||||
</Card>
|
||||
</Col>
|
||||
<Col span={14}>
|
||||
<Card title={`字典数据 ${selectedType ? `- ${selectedType}` : ''}`} extra={selectedType ? <Button type="primary" icon={<PlusOutlined />} onClick={() => { setEditingData(null); dataForm.resetFields(); setDataModalOpen(true) }}>新增</Button> : null}>
|
||||
{selectedType ? <Table<DictData> rowKey="id" columns={dataColumns} dataSource={dictData} loading={loading} pagination={false} size="small" /> : <p style={{ color: '#999', textAlign: 'center' }}>请选择左侧字典类型</p>}
|
||||
</Card>
|
||||
</Col>
|
||||
</Row>
|
||||
|
||||
<Modal title={editingType ? '编辑字典类型' : '新增字典类型'} open={typeModalOpen} onOk={handleTypeSubmit} onCancel={() => setTypeModalOpen(false)} destroyOnClose>
|
||||
<Form form={typeForm} layout="vertical">
|
||||
<Form.Item name="dictName" label="字典名称" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="dictType" label="字典类型" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="status" label="状态" initialValue={1}><Select options={[{ label: '正常', value: 1 }, { label: '停用', value: 0 }]} /></Form.Item>
|
||||
<Form.Item name="remark" label="备注"><Input.TextArea /></Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
<Modal title={editingData ? '编辑字典数据' : '新增字典数据'} open={dataModalOpen} onOk={handleDataSubmit} onCancel={() => setDataModalOpen(false)} destroyOnClose>
|
||||
<Form form={dataForm} layout="vertical">
|
||||
<Form.Item name="dictLabel" label="字典标签" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="dictValue" label="字典值" rules={[{ required: true }]}><Input /></Form.Item>
|
||||
<Form.Item name="sort" label="排序" initialValue={0}><InputNumber min={0} style={{ width: '100%' }} /></Form.Item>
|
||||
<Form.Item name="status" label="状态" initialValue={1}><Select options={[{ label: '正常', value: 1 }, { label: '停用', value: 0 }]} /></Form.Item>
|
||||
<Form.Item name="remark" label="备注"><Input.TextArea /></Form.Item>
|
||||
</Form>
|
||||
</Modal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user