137 lines
5.6 KiB
TypeScript
137 lines
5.6 KiB
TypeScript
import { useEffect, useMemo, useState } from 'react';
|
|
import { Plus, Search } from 'lucide-react';
|
|
import { Breadcrumb, Button, Input, Modal, Select, Table, Textarea, Tag, type TableColumn } from '@/components/ui';
|
|
import { adminApi, type DictionaryItem } from '@/api/adminApi';
|
|
|
|
type DrainageField = DictionaryItem & {
|
|
code?: string;
|
|
name?: string;
|
|
fieldType?: string;
|
|
required?: boolean;
|
|
description?: string | null;
|
|
};
|
|
|
|
type ReportFieldType = 'string' | 'image' | 'file';
|
|
|
|
const typeOptions = [
|
|
{ label: '全部类型', value: 'all' },
|
|
{ label: '字符串', value: 'string' },
|
|
{ label: '图片', value: 'image' },
|
|
{ label: '文件', value: 'file' },
|
|
];
|
|
|
|
const typeLabels: Record<string, string> = { string: '字符串', image: '图片', file: '文件' };
|
|
|
|
export function AdminDrainageFieldsPage() {
|
|
const [fields, setFields] = useState<DrainageField[]>([]);
|
|
const [keyword, setKeyword] = useState('');
|
|
const [appliedKeyword, setAppliedKeyword] = useState('');
|
|
const [type, setType] = useState('all');
|
|
const [appliedType, setAppliedType] = useState('all');
|
|
const [creating, setCreating] = useState(false);
|
|
const [code, setCode] = useState('');
|
|
const [name, setName] = useState('');
|
|
const [fieldType, setFieldType] = useState<ReportFieldType>('string');
|
|
const [description, setDescription] = useState('');
|
|
const [error, setError] = useState('');
|
|
|
|
function loadData() {
|
|
adminApi.listDrainageFields()
|
|
.then((items) => {
|
|
setFields(items as DrainageField[]);
|
|
setError('');
|
|
})
|
|
.catch((failure: Error) => setError(failure.message || '报备字段加载失败'));
|
|
}
|
|
|
|
useEffect(() => {
|
|
loadData();
|
|
}, []);
|
|
|
|
const filteredFields = useMemo(
|
|
() => fields.filter((field) => {
|
|
const matchesKeyword = !appliedKeyword || [field.code, field.name, field.fieldType, field.description].some((value) => String(value ?? '').includes(appliedKeyword));
|
|
const matchesType = appliedType === 'all' || field.fieldType === appliedType;
|
|
return matchesKeyword && matchesType;
|
|
}),
|
|
[appliedKeyword, appliedType, fields],
|
|
);
|
|
|
|
function createField() {
|
|
adminApi.createDrainageField({ code, name, fieldType, description, status: 'active' })
|
|
.then(() => {
|
|
setCode('');
|
|
setName('');
|
|
setFieldType('string');
|
|
setDescription('');
|
|
setCreating(false);
|
|
loadData();
|
|
})
|
|
.catch((failure: Error) => setError(failure.message || '报备字段新增失败'));
|
|
}
|
|
|
|
const columns = useMemo<Array<TableColumn<DrainageField>>>(() => [
|
|
{ key: 'code', title: '字段代码', width: '160px', render: (record) => <strong>{record.code}</strong> },
|
|
{ key: 'name', title: '字段名称', width: '190px', render: (record) => record.name ?? '-' },
|
|
{ key: 'type', title: '字段类型', width: '160px', render: (record) => <span className="admin-drainage-type">{typeLabels[record.fieldType ?? ''] ?? record.fieldType}</span> },
|
|
{ key: 'description', title: '描述', render: (record) => record.description ?? '-' },
|
|
{ key: 'required', title: '是否必填', width: '120px', render: (record) => <Tag tone={record.required ? 'warning' : 'info'}>{record.required ? '必填' : '选填'}</Tag> },
|
|
], []);
|
|
|
|
return (
|
|
<section className="page-stack admin-system-page admin-drainage-page">
|
|
<div className="page-heading">
|
|
<div>
|
|
<Breadcrumb items={['基础配置', '报备字段库']} />
|
|
<h1>报备字段库</h1>
|
|
</div>
|
|
</div>
|
|
{error ? <p className="form-error">{error}</p> : null}
|
|
|
|
<div className="surface admin-drainage-toolbar">
|
|
<Input onChange={(event) => setKeyword(event.target.value)} placeholder="搜索字段名称、代码或描述..." prefix={<Search size={16} />} value={keyword} />
|
|
<Select onChange={(event) => setType(event.target.value)} options={typeOptions} value={type} />
|
|
<div className="admin-system-toolbar__actions">
|
|
<Button icon={<Search size={16} />} onClick={() => { setAppliedKeyword(keyword.trim()); setAppliedType(type); }}>查询</Button>
|
|
<Button onClick={() => { setKeyword(''); setType('all'); setAppliedKeyword(''); setAppliedType('all'); }} variant="ghost">重置</Button>
|
|
</div>
|
|
<Button icon={<Plus size={18} />} onClick={() => setCreating(true)}>添加字段</Button>
|
|
</div>
|
|
|
|
<div className="surface admin-system-table-card admin-drainage-table-card">
|
|
<Table columns={columns} data={filteredFields} emptyText="暂无字段" rowKey="id" />
|
|
</div>
|
|
|
|
<Modal
|
|
footer={(
|
|
<>
|
|
<Button onClick={() => setCreating(false)} variant="ghost">取消</Button>
|
|
<Button disabled={!code || !name} onClick={createField}>保存</Button>
|
|
</>
|
|
)}
|
|
onClose={() => setCreating(false)}
|
|
open={creating}
|
|
title="添加报备字段"
|
|
>
|
|
<div className="admin-system-modal-form">
|
|
<Input label="字段代码" onChange={(event) => setCode(event.target.value)} value={code} />
|
|
<Input label="字段名称" onChange={(event) => setName(event.target.value)} value={name} />
|
|
<Select
|
|
label="字段类型"
|
|
onChange={(event) => setFieldType(event.target.value as ReportFieldType)}
|
|
options={typeOptions.filter((option) => option.value !== 'all')}
|
|
value={fieldType}
|
|
/>
|
|
<Textarea
|
|
className="admin-system-modal-form__wide"
|
|
label="描述"
|
|
onChange={(event) => setDescription(event.target.value)}
|
|
rows={4}
|
|
value={description}
|
|
/>
|
|
</div>
|
|
</Modal>
|
|
</section>
|
|
);
|
|
}
|