1. 新增操作日志记录功能,记录关键操作 2. 实现危险操作确认对话框,防止误删 3. 添加业务和库房管理模块 4. 支持设备标记为空闲状态 5. 完善API文档和健康检查 6. 优化前端删除操作的确认流程 7. 添加Swagger API文档支持 8. 实现设备与业务的关联功能 9. 改进设备模型,添加空闲相关字段 10. 优化用户、角色管理操作日志
278 lines
8.0 KiB
React
278 lines
8.0 KiB
React
import React, { useState, useEffect } from 'react';
|
|
import { useDebounce } from '../hooks/useDebounce';
|
|
import {
|
|
Table,
|
|
Button,
|
|
Modal,
|
|
Form,
|
|
Input,
|
|
InputNumber,
|
|
Select,
|
|
message,
|
|
Card,
|
|
Space,
|
|
Popconfirm,
|
|
Tag,
|
|
} from 'antd';
|
|
import { PlusOutlined, EditOutlined, DeleteOutlined, SearchOutlined } from '@ant-design/icons';
|
|
import axios from 'axios';
|
|
import CloseButton from '../components/CloseButton';
|
|
import {
|
|
inputStyles,
|
|
selectStyles,
|
|
textAreaStyles,
|
|
inputNumberStyles,
|
|
filterInputStyles,
|
|
inputPlaceholders,
|
|
inputValidationRules,
|
|
} from '../styles/deviceManagementStyles';
|
|
|
|
const { Option } = Select;
|
|
|
|
function CategoryManagement() {
|
|
const [categories, setCategories] = useState([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [modalVisible, setModalVisible] = useState(false);
|
|
const [editingCategory, setEditingCategory] = useState(null);
|
|
const [form] = Form.useForm();
|
|
const [pagination, setPagination] = useState({
|
|
current: 1,
|
|
pageSize: 10,
|
|
total: 0,
|
|
showTotal: total => `共 ${total} 条记录`,
|
|
});
|
|
const [keyword, setKeyword] = useState('');
|
|
const debouncedKeyword = useDebounce(keyword, 300);
|
|
const [status, setStatus] = useState('all');
|
|
|
|
const fetchCategories = async (page = 1, pageSize = 10) => {
|
|
try {
|
|
setLoading(true);
|
|
const response = await axios.get('/api/consumable-categories', {
|
|
params: { page, pageSize, keyword: debouncedKeyword, status },
|
|
});
|
|
setCategories(response.data.categories);
|
|
setPagination(prev => ({ ...prev, current: page, pageSize, total: response.data.total }));
|
|
} catch (error) {
|
|
message.error('获取分类列表失败');
|
|
console.error('获取分类列表失败:', error);
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
useEffect(() => {
|
|
fetchCategories();
|
|
}, [debouncedKeyword, status]);
|
|
|
|
const showModal = (category = null) => {
|
|
setEditingCategory(category);
|
|
if (category) {
|
|
form.setFieldsValue(category);
|
|
} else {
|
|
form.resetFields();
|
|
form.setFieldsValue({ status: 'active', sortOrder: 0 });
|
|
}
|
|
setModalVisible(true);
|
|
};
|
|
|
|
const handleCancel = () => {
|
|
setModalVisible(false);
|
|
setEditingCategory(null);
|
|
};
|
|
|
|
const handleSubmit = async values => {
|
|
try {
|
|
if (editingCategory) {
|
|
await axios.put(`/api/consumable-categories/${editingCategory.id}`, values);
|
|
message.success('分类更新成功');
|
|
} else {
|
|
await axios.post('/api/consumable-categories', values);
|
|
message.success('分类创建成功');
|
|
}
|
|
setModalVisible(false);
|
|
fetchCategories();
|
|
setEditingCategory(null);
|
|
} catch (error) {
|
|
message.error(
|
|
error.response?.data?.error || (editingCategory ? '分类更新失败' : '分类创建失败')
|
|
);
|
|
console.error('提交失败:', error);
|
|
}
|
|
};
|
|
|
|
const handleDelete = async id => {
|
|
Modal.confirm({
|
|
title: '确认删除',
|
|
content: '确定要删除这个耗材分类吗?此操作不可恢复!',
|
|
okText: '删除',
|
|
okType: 'danger',
|
|
cancelText: '取消',
|
|
onOk: async () => {
|
|
try {
|
|
await axios.delete(`/api/consumable-categories/${id}`);
|
|
message.success('删除成功');
|
|
fetchCategories();
|
|
} catch (error) {
|
|
message.error(error.response?.data?.error || '删除失败');
|
|
console.error('删除失败:', error);
|
|
}
|
|
},
|
|
});
|
|
};
|
|
|
|
const columns = [
|
|
{
|
|
title: 'ID',
|
|
dataIndex: 'id',
|
|
key: 'id',
|
|
width: 80,
|
|
},
|
|
{
|
|
title: '分类名称',
|
|
dataIndex: 'name',
|
|
key: 'name',
|
|
width: 150,
|
|
},
|
|
{
|
|
title: '描述',
|
|
dataIndex: 'description',
|
|
key: 'description',
|
|
width: 200,
|
|
render: value => value || '-',
|
|
},
|
|
{
|
|
title: '排序',
|
|
dataIndex: 'sortOrder',
|
|
key: 'sortOrder',
|
|
width: 80,
|
|
},
|
|
{
|
|
title: '状态',
|
|
dataIndex: 'status',
|
|
key: 'status',
|
|
width: 100,
|
|
render: value => (
|
|
<Tag color={value === 'active' ? 'green' : 'red'}>
|
|
{value === 'active' ? '启用' : '停用'}
|
|
</Tag>
|
|
),
|
|
},
|
|
{
|
|
title: '创建时间',
|
|
dataIndex: 'createdAt',
|
|
key: 'createdAt',
|
|
width: 180,
|
|
render: value => (value ? new Date(value).toLocaleString('zh-CN') : '-'),
|
|
},
|
|
{
|
|
title: '操作',
|
|
key: 'action',
|
|
width: 150,
|
|
render: (_, record) => (
|
|
<Space>
|
|
<Button
|
|
type="primary"
|
|
icon={<EditOutlined />}
|
|
size="small"
|
|
onClick={() => showModal(record)}
|
|
>
|
|
编辑
|
|
</Button>
|
|
<Popconfirm title="确定删除?" onConfirm={() => handleDelete(record.id)}>
|
|
<Button danger icon={<DeleteOutlined />} size="small">
|
|
删除
|
|
</Button>
|
|
</Popconfirm>
|
|
</Space>
|
|
),
|
|
},
|
|
];
|
|
|
|
return (
|
|
<div>
|
|
<Card
|
|
title="耗材分类管理"
|
|
extra={
|
|
<Button type="primary" icon={<PlusOutlined />} onClick={() => showModal()}>
|
|
添加分类
|
|
</Button>
|
|
}
|
|
>
|
|
<Card size="small" style={{ marginBottom: 16 }}>
|
|
<Space>
|
|
<Input
|
|
placeholder={inputPlaceholders.searchCategory}
|
|
style={{ ...inputStyles.search, width: 300 }}
|
|
value={keyword}
|
|
onChange={e => setKeyword(e.target.value)}
|
|
allowClear
|
|
prefix={<SearchOutlined />}
|
|
/>
|
|
<Select value={status} onChange={setStatus} style={{ ...selectStyles.base, width: 120 }}>
|
|
<Option value="all">所有状态</Option>
|
|
<Option value="active">启用</Option>
|
|
<Option value="inactive">停用</Option>
|
|
</Select>
|
|
<Button onClick={() => fetchCategories()} style={{ height: '40px', borderRadius: '10px' }}>刷新</Button>
|
|
</Space>
|
|
</Card>
|
|
|
|
<Table
|
|
columns={columns}
|
|
dataSource={categories}
|
|
rowKey="id"
|
|
loading={loading}
|
|
pagination={pagination}
|
|
onChange={pagination => fetchCategories(pagination.current, pagination.pageSize)}
|
|
scroll={{ x: 1000 }}
|
|
/>
|
|
</Card>
|
|
|
|
<Modal
|
|
title={editingCategory ? '编辑分类' : '添加分类'}
|
|
open={modalVisible}
|
|
closeIcon={<CloseButton />}
|
|
onCancel={handleCancel}
|
|
footer={null}
|
|
width={500}
|
|
>
|
|
<Form form={form} layout="vertical" onFinish={handleSubmit}>
|
|
<Form.Item
|
|
name="name"
|
|
label="分类名称"
|
|
rules={[
|
|
inputValidationRules.required('请输入分类名称'),
|
|
inputValidationRules.maxLength(50, '分类名称不能超过50个字符'),
|
|
]}
|
|
>
|
|
<Input placeholder="请输入分类名称" style={inputStyles.form} />
|
|
</Form.Item>
|
|
<Form.Item name="description" label="描述">
|
|
<Input.TextArea rows={3} placeholder={inputPlaceholders.description} maxLength={200} showCount style={textAreaStyles.base} />
|
|
</Form.Item>
|
|
<Form.Item name="sortOrder" label="排序">
|
|
<InputNumber min={0} placeholder="数值越小越靠前" style={inputNumberStyles.base} />
|
|
</Form.Item>
|
|
<Form.Item name="status" label="状态" rules={[inputValidationRules.required('请选择状态')]}>
|
|
<Select style={selectStyles.base}>
|
|
<Option value="active">启用</Option>
|
|
<Option value="inactive">停用</Option>
|
|
</Select>
|
|
</Form.Item>
|
|
<Form.Item>
|
|
<Space>
|
|
<Button type="primary" htmlType="submit" style={{ height: '40px', borderRadius: '10px' }}>
|
|
{editingCategory ? '更新' : '创建'}
|
|
</Button>
|
|
<Button onClick={handleCancel} style={{ height: '40px', borderRadius: '10px' }}>取消</Button>
|
|
</Space>
|
|
</Form.Item>
|
|
</Form>
|
|
</Modal>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export default CategoryManagement;
|