feat: polish reporting templates and shared controls

This commit is contained in:
hectorzhao
2026-07-15 16:31:49 +08:00
parent 28fb8e9038
commit a7a4e8d9f6
21 changed files with 488 additions and 119 deletions
+35 -30
View File
@@ -1,6 +1,6 @@
import { useEffect, useMemo, useState } from 'react';
import { Plus, Search, Trash2 } from 'lucide-react';
import { Breadcrumb, Button, Input, Modal, Select, Table, Textarea, Tag, type TableColumn } from '@/components/ui';
import { Database, FileCheck2, Link2, Plus, Search, Trash2 } from 'lucide-react';
import { Breadcrumb, Button, Input, Modal, Select, Textarea, Tag } from '@/components/ui';
import { adminApi, type CommonReportField, type DictionaryItem } from '@/api/adminApi';
type DrainageField = DictionaryItem & {
@@ -114,25 +114,9 @@ export function AdminDrainageFieldsPage() {
.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> },
{ key: 'usageCount', title: '使用通道数', width: '130px', render: (record) => <Tag tone={(record.usageCount ?? 0) > 0 ? 'warning' : 'neutral'}>{record.usageCount ?? 0}</Tag> },
{ key: 'commonUsageCount', title: '通用配置数', width: '130px', render: (record) => <Tag tone={(record.commonUsageCount ?? 0) > 0 ? 'info' : 'neutral'}>{record.commonUsageCount ?? 0}</Tag> },
{ key: 'actions', title: '操作', width: '120px', align: 'right', render: (record) => <Button disabled={(record.usageCount ?? 0) > 0 || (record.commonUsageCount ?? 0) > 0} icon={<Trash2 size={14} />} onClick={() => setDeleteTarget(record)} size="sm" variant="danger"></Button> },
], []);
const commonColumns = useMemo<Array<TableColumn<CommonReportField>>>(() => [
{ key: 'name', title: '字段名称', render: (record) => <strong>{String(record.drainageField.name ?? record.drainageField.code ?? '-')}</strong> },
{ key: 'code', title: '字段代码', width: '170px', render: (record) => String(record.drainageField.code ?? '-') },
{ key: 'reportType', title: '资料用途', width: '180px', render: (record) => <Tag tone={record.reportType === 'signature' ? 'info' : 'warning'}>{record.reportType === 'signature' ? '签名报备资料' : '引流信息报备资料'}</Tag> },
{ key: 'fieldType', title: '字段类型', width: '130px', render: (record) => typeLabels[String(record.drainageField.fieldType ?? '')] ?? String(record.drainageField.fieldType ?? '-') },
{ key: 'required', title: '是否必填', width: '120px', render: (record) => <Tag tone={record.required ? 'warning' : 'neutral'}>{record.required ? '必填' : '选填'}</Tag> },
{ key: 'actions', title: '操作', width: '120px', align: 'right', render: (record) => <Button icon={<Trash2 size={14} />} onClick={() => setCommonDeleteTarget(record)} size="sm" variant="danger"></Button> },
], []);
const signatureCommon = commonFields.filter((field) => field.reportType === 'signature');
const drainageCommon = commonFields.filter((field) => field.reportType === 'drainage');
const referencedCount = fields.filter((field) => (field.usageCount ?? 0) > 0 || (field.commonUsageCount ?? 0) > 0).length;
return (
<section className="page-stack admin-system-page admin-drainage-page">
@@ -140,10 +124,18 @@ export function AdminDrainageFieldsPage() {
<div>
<Breadcrumb items={['基础配置', '报备字段库']} />
<h1></h1>
<p></p>
</div>
<Button icon={<Plus size={16} />} onClick={() => setCreating(true)} size="sm"></Button>
</div>
{error ? <p className="form-error">{error}</p> : null}
<div className="admin-drainage-summary">
<article><span><Database size={18} /></span><div><strong>{fields.length}</strong><p></p></div></article>
<article><span><FileCheck2 size={18} /></span><div><strong>{commonFields.length}</strong><p></p></div></article>
<article><span><Link2 size={18} /></span><div><strong>{referencedCount}</strong><p></p></div></article>
</div>
<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} />
@@ -151,23 +143,32 @@ export function AdminDrainageFieldsPage() {
<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">
<div className="page-heading">
<div className="surface admin-drainage-section">
<div className="admin-drainage-section__heading">
<div>
<h2></h2>
<p></p>
<p></p>
</div>
<Button icon={<Plus size={18} />} onClick={() => setConfiguringCommon(true)}></Button>
<Button icon={<Plus size={16} />} onClick={() => setConfiguringCommon(true)} size="sm" variant="secondary"></Button>
</div>
<div className="admin-drainage-common-grid">
<CommonFieldGroup fields={signatureCommon} label="签名报备资料" onDelete={setCommonDeleteTarget} tone="info" />
<CommonFieldGroup fields={drainageCommon} label="引流信息报备资料" onDelete={setCommonDeleteTarget} tone="warning" />
</div>
<Table columns={commonColumns} data={commonFields} emptyText="暂无通用字段配置" rowKey="id" />
</div>
<div className="surface admin-system-table-card admin-drainage-table-card">
<div className="page-heading"><div><h2></h2><p></p></div></div>
<Table columns={columns} data={filteredFields} emptyText="暂无字段" rowKey="id" />
<div className="surface admin-drainage-section">
<div className="admin-drainage-section__heading"><div><h2></h2><p> {filteredFields.length} </p></div></div>
{filteredFields.length ? <div className="admin-drainage-field-grid">{filteredFields.map((field) => {
const locked = (field.usageCount ?? 0) > 0 || (field.commonUsageCount ?? 0) > 0;
return <article className="admin-drainage-field-card" key={field.id}>
<div className="admin-drainage-field-card__top"><span className="admin-drainage-type">{typeLabels[field.fieldType ?? ''] ?? field.fieldType}</span><Button aria-label={`删除${field.name}`} disabled={locked} icon={<Trash2 size={14} />} iconOnly onClick={() => setDeleteTarget(field)} size="sm" variant="ghost"></Button></div>
<h3>{field.name ?? '-'}</h3><code>{field.code}</code><p>{field.description || '暂无字段说明'}</p>
<div className="admin-drainage-field-card__meta"><span> <strong>{field.usageCount ?? 0}</strong></span><span> <strong>{field.commonUsageCount ?? 0}</strong></span></div>
</article>;
})}</div> : <div className="admin-drainage-empty"></div>}
</div>
<Modal
@@ -235,3 +236,7 @@ export function AdminDrainageFieldsPage() {
</section>
);
}
function CommonFieldGroup({ fields, label, onDelete, tone }: { fields: CommonReportField[]; label: string; onDelete: (field: CommonReportField) => void; tone: 'info' | 'warning' }) {
return <section className="admin-drainage-common-group"><div className="admin-drainage-common-group__title"><Tag tone={tone}>{label}</Tag><span>{fields.length} </span></div>{fields.length ? <div className="admin-drainage-common-list">{fields.map((field) => <div key={field.id}><div><strong>{String(field.drainageField.name ?? field.drainageField.code)}</strong><span>{String(field.drainageField.code)} · {typeLabels[String(field.drainageField.fieldType ?? '')] ?? '-'}</span></div><Tag tone={field.required ? 'warning' : 'neutral'}>{field.required ? '必填' : '选填'}</Tag><Button aria-label="删除通用字段" icon={<Trash2 size={14} />} iconOnly onClick={() => onDelete(field)} size="sm" variant="ghost"></Button></div>)}</div> : <p className="admin-drainage-common-empty"></p>}</section>;
}