import React, { useState, useEffect, useMemo } from 'react';
import {
Table,
Button,
Modal,
Form,
Input,
Select,
message,
Card,
Space,
InputNumber,
Switch,
Tag,
Statistic,
Tooltip,
} from 'antd';
import {
PlusOutlined,
EditOutlined,
DeleteOutlined,
AppstoreOutlined,
FontSizeOutlined,
NumberOutlined,
CheckCircleOutlined,
CalendarOutlined,
FileTextOutlined,
LockOutlined,
PlusCircleOutlined,
MinusCircleOutlined,
} from '@ant-design/icons';
import axios from 'axios';
import { designTokens } from '../config/theme';
import CloseButton from '../components/CloseButton';
const { Option = Select.Option } = Select;
const OptionsEditor = ({ value = [], onChange }) => {
const handleAdd = () => {
onChange([...value, { value: '', label: '' }]);
};
const handleRemove = index => {
onChange(value.filter((_, i) => i !== index));
};
const handleUpdate = (index, field, fieldValue) => {
const newOptions = value.map((opt, i) => (i === index ? { ...opt, [field]: fieldValue } : opt));
onChange(newOptions);
};
return (
{value.length === 0 ? (
暂无选项
}
onClick={handleAdd}
style={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
border: 'none',
borderRadius: '6px',
height: '36px',
}}
>
添加第一个选项
) : (
值(value)
标签(label)
{value.map((opt, index) => (
))}
)}
{value.length > 0 && (
}
onClick={handleAdd}
style={{
width: '100%',
height: '40px',
borderRadius: '8px',
borderColor: '#d9d9d9',
color: '#666',
}}
>
添加选项
)}
);
};
const pageContainerStyle = {
minHeight: '100vh',
background: designTokens.colors.background.secondary,
padding: designTokens.spacing.lg,
};
const titleRowStyle = {
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: designTokens.spacing.lg,
};
const titleStyle = {
display: 'flex',
alignItems: 'center',
gap: designTokens.spacing.sm,
fontSize: '20px',
fontWeight: '600',
color: designTokens.colors.text.primary,
};
const actionButtonStyle = {
height: '36px',
padding: `0 ${designTokens.spacing.md}px`,
borderRadius: designTokens.borderRadius.small,
fontSize: '13px',
display: 'flex',
alignItems: 'center',
gap: designTokens.spacing.xs,
};
const primaryActionStyle = {
...actionButtonStyle,
background: designTokens.colors.primary.gradient,
border: 'none',
color: '#ffffff',
boxShadow: designTokens.shadows.small,
};
const tableCardStyle = {
background: designTokens.colors.background.primary,
borderRadius: designTokens.borderRadius.large,
boxShadow: designTokens.shadows.small,
border: `1px solid ${designTokens.colors.border.light}`,
overflow: 'hidden',
};
const tableStyle = {
background: designTokens.colors.background.primary,
};
const titleIconStyle = {
color: designTokens.colors.primary.main,
};
const modalTitleStyle = {
fontWeight: '600',
};
const formLabelStyle = {
fontWeight: '500',
};
const tableCellStyle = {
fontWeight: '500',
color: designTokens.colors.text.primary,
};
const typeTagStyle = {
border: 'none',
borderRadius: designTokens.borderRadius.small,
fontWeight: '500',
};
const orderBadgeStyle = {
background: designTokens.colors.background.tertiary,
padding: '2px 8px',
borderRadius: designTokens.borderRadius.small,
fontSize: '12px',
fontWeight: '500',
};
const editButtonStyle = {
color: designTokens.colors.primary.main,
height: '28px',
padding: '0 8px',
};
const deleteButtonStyle = {
height: '28px',
padding: '0 8px',
};
const formRowStyle = {
display: 'flex',
gap: designTokens.spacing.md,
};
const formItemFlexStyle = {
flex: 1,
};
const textAreaStyle = {
fontFamily: 'monospace',
};
const modalBodyStyle = {
padding: designTokens.spacing.lg,
};
const formActionsStyle = {
marginBottom: 0,
textAlign: 'right',
};
const modalStyle = {
borderRadius: designTokens.borderRadius.large,
};
const FIELD_TYPE_MAP = {
string: { text: '文本', color: designTokens.colors.fieldType.string },
number: { text: '数字', color: designTokens.colors.fieldType.number },
boolean: { text: '布尔值', color: designTokens.colors.fieldType.boolean },
select: { text: '下拉选择', color: designTokens.colors.fieldType.select },
date: { text: '日期', color: designTokens.colors.fieldType.date },
textarea: { text: '多行文本', color: designTokens.colors.fieldType.textarea },
};
const FIELD_TYPE_OPTIONS = [
{ value: 'string', label: '文本' },
{ value: 'number', label: '数字' },
{ value: 'boolean', label: '布尔值' },
{ value: 'select', label: '下拉选择' },
{ value: 'date', label: '日期' },
{ value: 'textarea', label: '多行文本' },
];
function DeviceFieldManagement() {
const [fields, setFields] = useState([]);
const [loading, setLoading] = useState(true);
const [modalVisible, setModalVisible] = useState(false);
const [editingField, setEditingField] = useState(null);
const [selectedFieldType, setSelectedFieldType] = useState('string');
const [form] = Form.useForm();
const [pagination, setPagination] = useState({
current: 1,
pageSize: 10,
pageSizeOptions: ['10', '20', '50', '100'],
showSizeChanger: true,
showQuickJumper: true,
showTotal: (total, range) => `第 ${range[0]}-${range[1]} 条 / 共 ${total} 条`,
});
const fetchFields = async () => {
try {
setLoading(true);
const response = await axios.get('/api/deviceFields');
setFields(response.data.sort((a, b) => a.order - b.order));
} catch (error) {
message.error('获取字段列表失败');
console.error('获取字段列表失败:', error);
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchFields();
}, []);
const showModal = (field = null) => {
setEditingField(field);
if (field) {
const fieldData = {
...field,
options: field.options || [],
};
setSelectedFieldType(field.fieldType || 'string');
form.setFieldsValue(fieldData);
} else {
form.resetFields();
setSelectedFieldType('string');
}
setModalVisible(true);
};
const handleCancel = () => {
setModalVisible(false);
setEditingField(null);
setSelectedFieldType('string');
};
const handleFieldTypeChange = value => {
setSelectedFieldType(value);
if (value !== 'select') {
form.setFieldsValue({ options: [] });
}
};
const handleSubmit = async values => {
try {
const fieldData = {
...values,
options: values.options && values.options.length > 0 ? values.options : null,
};
if (editingField) {
await axios.put(`/api/deviceFields/${editingField.fieldId}`, fieldData);
message.success('字段更新成功');
} else {
await axios.post('/api/deviceFields', fieldData);
message.success('字段创建成功');
}
setModalVisible(false);
fetchFields();
setEditingField(null);
setSelectedFieldType('string');
} catch (error) {
message.error(editingField ? '字段更新失败' : '字段创建失败');
console.error(editingField ? '字段更新失败:' : '字段创建失败:', error);
}
};
const handleDelete = async fieldId => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这个字段吗?',
okText: '删除',
okType: 'danger',
cancelText: '取消',
onOk: async () => {
try {
await axios.delete(`/api/deviceFields/${fieldId}`);
message.success('字段删除成功');
fetchFields();
} catch (error) {
message.error('字段删除失败');
console.error('字段删除失败:', error);
}
},
});
};
const getFieldTypeIcon = type => {
const iconMap = {
string: ,
number: ,
boolean: ,
select: ,
date: ,
textarea: ,
};
return iconMap[type] || ;
};
const columns = useMemo(
() => [
{
title: '字段名称',
dataIndex: 'fieldName',
key: 'fieldName',
width: 150,
render: (text, record) => (
{text}
{record.isSystem && (
)}
),
},
{
title: '显示名称',
dataIndex: 'displayName',
key: 'displayName',
width: 120,
},
{
title: '字段类型',
dataIndex: 'fieldType',
key: 'fieldType',
width: 110,
render: type => {
const config = FIELD_TYPE_MAP[type] || {
text: type,
color: designTokens.colors.text.tertiary,
};
return (
{getFieldTypeIcon(type)}
{config.text}
);
},
},
{
title: '必填',
dataIndex: 'required',
key: 'required',
width: 80,
render: required => (
{required ? '是' : '否'}
),
},
{
title: '可见',
dataIndex: 'visible',
key: 'visible',
width: 80,
render: visible => (
{visible ? '是' : '否'}
),
},
{
title: '顺序',
dataIndex: 'order',
key: 'order',
width: 80,
render: order => {order},
},
{
title: '操作',
key: 'action',
width: 160,
fixed: 'right',
render: (_, record) => (
}
onClick={() => showModal(record)}
className="table-action-btn"
>
编辑
{record.isSystem ? (
}
disabled
className="table-action-btn-disabled"
>
删除
) : (
}
onClick={() => handleDelete(record.fieldId)}
className="table-action-btn"
>
删除
)}
),
},
],
[]
);
return (
}
onClick={() => showModal()}
className="page-primary-btn"
>
添加字段