| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316 |
- /**
- * 站点信息配置页面
- * 用于配置和管理 DICOM 站点基本信息
- */
- import React, { useEffect, useState } from 'react';
- import {
- Form,
- Input,
- InputNumber,
- Button,
- Typography,
- Space,
- message,
- Spin,
- Modal,
- } from 'antd';
- import { CopyOutlined } from '@ant-design/icons';
- import { SPACING } from '../../constants';
- import { RootState, useAppSelector } from '@/states/store';
- const { Title } = Typography;
- const { TextArea } = Input;
- // 站点信息数据类型
- interface SiteInfoData {
- siteName: string;
- aeTitle: string;
- port: number;
- institutionName?: string;
- siteCode?: string;
- description?: string;
- }
- /**
- * 站点信息组件
- *
- * 布局结构:
- * - Layout Type: Vertical Stack Layout
- * - Form Layout: Horizontal
- * - Label Width: 120px
- */
- const SiteInfo: React.FC = () => {
- const [form] = Form.useForm<SiteInfoData>();
- const [loading, setLoading] = useState(false);
- const [saving, setSaving] = useState(false);
- const [initialValues, setInitialValues] = useState<SiteInfoData | null>(null);
- const sn = useAppSelector((state: RootState) => state.product.sn);
- const { Search } = Input;
- // 模拟加载数据
- useEffect(() => {
- loadSiteInfo();
- }, []);
- // 加载站点信息
- const loadSiteInfo = async () => {
- setLoading(true);
- try {
- // TODO: 替换为实际的 API 调用
- // const data = await getSiteInfo();
- // 模拟数据
- const mockData: SiteInfoData = {
- siteName: 'Medical Imaging Center',
- aeTitle: 'PACS_SERVER',
- port: 104,
- institutionName: 'General Hospital',
- siteCode: 'SITE001',
- description: 'Main PACS server for medical imaging',
- };
- setInitialValues(mockData);
- form.setFieldsValue(mockData);
- } catch (error) {
- message.error('加载站点信息失败');
- console.error('Failed to load site info:', error);
- } finally {
- setLoading(false);
- }
- };
- // 保存站点信息
- const handleSave = async () => {
- try {
- // 触发表单验证
- const values = await form.validateFields();
- setSaving(true);
- // TODO: 替换为实际的 API 调用
- // await updateSiteInfo(values);
- // 模拟保存延迟
- await new Promise((resolve) => setTimeout(resolve, 1000));
- message.success('保存成功');
- setInitialValues(values);
- // 提示用户可能需要重启服务
- Modal.info({
- title: '提示',
- content: '站点配置已保存,部分更改可能需要重启服务后生效。',
- });
- } catch (error) {
- if (error instanceof Error) {
- message.error(`保存失败: ${error.message}`);
- } else {
- message.error('表单验证失败,请检查输入');
- }
- console.error('Failed to save site info:', error);
- } finally {
- setSaving(false);
- }
- };
- // 重置表单
- const handleReset = (): void => {
- if (initialValues) {
- // 检查是否有未保存的更改
- const currentValues = form.getFieldsValue();
- const hasChanges =
- JSON.stringify(currentValues) !== JSON.stringify(initialValues);
- if (hasChanges) {
- Modal.confirm({
- title: '确认重置',
- content: '您有未保存的更改,确定要重置表单吗?',
- onOk: () => {
- form.setFieldsValue(initialValues);
- message.info('表单已重置');
- },
- });
- } else {
- form.setFieldsValue(initialValues);
- message.info('表单已重置');
- }
- }
- };
- // AE Title 输入转大写
- const handleAETitleChange = (
- e: React.ChangeEvent<HTMLInputElement>
- ): void => {
- const value = e.target.value.toUpperCase().replace(/[^A-Z0-9_]/g, '');
- form.setFieldsValue({ aeTitle: value });
- };
- // 复制SN号码
- const handleCopySN = async (): Promise<void> => {
- if (sn) {
- try {
- await navigator.clipboard.writeText(sn);
- message.success('SN码已复制到剪贴板');
- } catch (error) {
- message.error('复制失败,请手动复制');
- console.error('Failed to copy SN:', error);
- }
- } else {
- message.warning('SN码为空');
- }
- };
- if (loading) {
- return (
- <div
- style={{
- padding: SPACING.LG,
- display: 'flex',
- justifyContent: 'center',
- alignItems: 'center',
- minHeight: 400,
- }}
- >
- <Spin size="large" tip="加载中..." />
- </div>
- );
- }
- return (
- <div style={{ padding: SPACING.LG }}>
- <Title level={3}>站点信息</Title>
- <Form
- form={form}
- layout="horizontal"
- labelCol={{ span: 6 }}
- wrapperCol={{ span: 14 }}
- size="large"
- style={{ marginTop: SPACING.LG }}
- >
- {/* SN码 */}
- <Form.Item label="SN码">
- <Space.Compact style={{ width: '100%' }}>
- <Input style={{ width: '90%' }} disabled value={sn || ''} />
- <Button
- style={{ width: '10%', height: 'auto' }}
- icon={<CopyOutlined />}
- onClick={handleCopySN}
- title="复制SN码"
- />
- </Space.Compact>
- </Form.Item>
- {/* 站点名称 */}
- <Form.Item
- label="站点名称"
- name="siteName"
- rules={[
- { required: true, message: '请输入站点名称' },
- { max: 50, message: '站点名称最多50个字符' },
- ]}
- tooltip="用于标识此 DICOM 站点的名称"
- >
- <Input placeholder="请输入站点名称" allowClear />
- </Form.Item>
- {/* AE Title */}
- <Form.Item
- label="AE Title"
- name="aeTitle"
- rules={[
- { required: true, message: '请输入 AE Title' },
- { max: 16, message: 'AE Title 最多16个字符' },
- {
- pattern: /^[A-Z0-9_]+$/,
- message: 'AE Title 只能包含大写字母、数字和下划线',
- },
- ]}
- tooltip="DICOM 应用实体标题,必须唯一"
- >
- <Input
- placeholder="请输入 AE Title"
- onChange={handleAETitleChange}
- maxLength={16}
- allowClear
- />
- </Form.Item>
- {/* 端口 */}
- <Form.Item
- label="端口"
- name="port"
- rules={[
- { required: true, message: '请输入端口号' },
- {
- type: 'number',
- min: 1,
- max: 65535,
- message: '端口号范围:1-65535',
- },
- ]}
- tooltip="DICOM 通信端口,默认为 104"
- >
- <InputNumber
- placeholder="请输入端口号"
- style={{ width: '100%' }}
- min={1}
- max={65535}
- />
- </Form.Item>
- {/* 机构名称 */}
- <Form.Item
- label="机构名称"
- name="institutionName"
- rules={[{ max: 100, message: '机构名称最多100个字符' }]}
- >
- <Input placeholder="请输入机构名称(可选)" allowClear />
- </Form.Item>
- {/* 站点编码 */}
- <Form.Item
- label="站点编码"
- name="siteCode"
- rules={[{ max: 20, message: '站点编码最多20个字符' }]}
- tooltip="用于系统内部识别的唯一编码"
- >
- <Input placeholder="请输入站点编码(可选)" allowClear />
- </Form.Item>
- {/* 描述 */}
- <Form.Item
- label="描述"
- name="description"
- rules={[{ max: 500, message: '描述最多500个字符' }]}
- >
- <TextArea
- placeholder="请输入站点描述(可选)"
- rows={4}
- showCount
- maxLength={500}
- allowClear
- />
- </Form.Item>
- {/* 操作按钮 */}
- <Form.Item wrapperCol={{ offset: 6, span: 14 }}>
- <Space>
- <Button
- type="primary"
- onClick={handleSave}
- loading={saving}
- disabled={loading}
- >
- 保存
- </Button>
- <Button onClick={handleReset} disabled={loading || saving}>
- 重置
- </Button>
- </Space>
- </Form.Item>
- </Form>
- </div>
- );
- };
- export default SiteInfo;
|