|
@@ -1,21 +1,78 @@
|
|
|
-import React, { useState } from 'react';
|
|
|
-import { Layout, Button, Checkbox, Typography, List } from 'antd';
|
|
|
-import { ArrowLeftOutlined } from '@ant-design/icons';
|
|
|
-import { useDispatch } from 'react-redux';
|
|
|
+import React, { useEffect } from 'react';
|
|
|
+import { Layout, Button, Checkbox, Typography, List, Spin, Alert, message } from 'antd';
|
|
|
+import { ArrowLeftOutlined, ReloadOutlined } from '@ant-design/icons';
|
|
|
+import { useDispatch, useSelector } from 'react-redux';
|
|
|
import { switchToOperationPanel } from '@/states/patient/worklist/slices/historyPanelSwitchSlice';
|
|
|
+import {
|
|
|
+ fetchPacsNodesThunk,
|
|
|
+ toggleNodeSelection,
|
|
|
+ selectAllNodes,
|
|
|
+ deselectAllNodes,
|
|
|
+} from '@/states/output/pacsNode/pacsNodeSlice';
|
|
|
+import { RootState, AppDispatch } from '@/states/store';
|
|
|
import Icon from '@/components/Icon';
|
|
|
|
|
|
const { Header, Content, Footer } = Layout;
|
|
|
const { Title } = Typography;
|
|
|
|
|
|
const SendImagePage = () => {
|
|
|
- const [checked, setChecked] = useState(false);
|
|
|
- const dispatch = useDispatch();
|
|
|
+ const dispatch = useDispatch<AppDispatch>();
|
|
|
|
|
|
+ // 从Redux获取PACS节点状态
|
|
|
+ const { nodes, loading, error, selectedNodeIds } = useSelector(
|
|
|
+ (state: RootState) => state.pacsNode
|
|
|
+ );
|
|
|
+
|
|
|
+ // 组件挂载时获取PACS节点列表
|
|
|
+ useEffect(() => {
|
|
|
+ dispatch(fetchPacsNodesThunk());
|
|
|
+ }, [dispatch]);
|
|
|
+
|
|
|
+ // 返回操作面板
|
|
|
const handleReturn = () => {
|
|
|
dispatch(switchToOperationPanel());
|
|
|
};
|
|
|
|
|
|
+ // 切换节点选中状态
|
|
|
+ const handleNodeToggle = (nodeId: number) => {
|
|
|
+ dispatch(toggleNodeSelection(nodeId));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 全选/取消全选
|
|
|
+ const handleSelectAll = (checked: boolean) => {
|
|
|
+ if (checked) {
|
|
|
+ dispatch(selectAllNodes());
|
|
|
+ } else {
|
|
|
+ dispatch(deselectAllNodes());
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 重新加载节点列表
|
|
|
+ const handleReload = () => {
|
|
|
+ dispatch(fetchPacsNodesThunk());
|
|
|
+ };
|
|
|
+
|
|
|
+ // 发送图像
|
|
|
+ const handleSend = () => {
|
|
|
+ if (selectedNodeIds.length === 0) {
|
|
|
+ message.warning('请至少选择一个PACS节点');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // TODO: 实现实际的发送图像功能
|
|
|
+ message.info(`准备发送到 ${selectedNodeIds.length} 个节点`);
|
|
|
+ console.log('Selected node IDs:', selectedNodeIds);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 是否全选
|
|
|
+ const isAllSelected = nodes.length > 0 &&
|
|
|
+ nodes.filter(node => node.is_enabled).every(node =>
|
|
|
+ selectedNodeIds.includes(node.id)
|
|
|
+ );
|
|
|
+
|
|
|
+ // 是否部分选中
|
|
|
+ const isIndeterminate = selectedNodeIds.length > 0 && !isAllSelected;
|
|
|
+
|
|
|
return (
|
|
|
<Layout className="h-full">
|
|
|
{/* 顶部导航栏 */}
|
|
@@ -24,40 +81,114 @@ const SendImagePage = () => {
|
|
|
display: 'flex',
|
|
|
alignItems: 'center',
|
|
|
padding: '0 16px',
|
|
|
+ justifyContent: 'space-between',
|
|
|
}}
|
|
|
>
|
|
|
+ <div style={{ display: 'flex', alignItems: 'center' }}>
|
|
|
+ <Button
|
|
|
+ type="text"
|
|
|
+ icon={<ArrowLeftOutlined />}
|
|
|
+ onClick={handleReturn}
|
|
|
+ />
|
|
|
+ <Title level={5} style={{ margin: 0, lineHeight: '48px' }}>
|
|
|
+ 发送图像
|
|
|
+ </Title>
|
|
|
+ </div>
|
|
|
<Button
|
|
|
type="text"
|
|
|
- icon={<ArrowLeftOutlined />}
|
|
|
- onClick={handleReturn}
|
|
|
+ icon={<ReloadOutlined />}
|
|
|
+ onClick={handleReload}
|
|
|
+ loading={loading}
|
|
|
/>
|
|
|
- <Title level={5} style={{ margin: 0, lineHeight: '48px' }}>
|
|
|
- 发送图像
|
|
|
- </Title>
|
|
|
</Header>
|
|
|
|
|
|
{/* 主体内容 */}
|
|
|
<Content
|
|
|
style={{ padding: '16px', maxHeight: '100%', overflowY: 'auto' }}
|
|
|
>
|
|
|
- <List
|
|
|
- dataSource={[
|
|
|
- { label: 'DVTKSTR SCP' },
|
|
|
- ...Array.from({ length: 20 }, (_, i) => ({
|
|
|
- label: `Checkbox ${i + 1}`,
|
|
|
- })),
|
|
|
- ]}
|
|
|
- renderItem={(item) => (
|
|
|
- <List.Item>
|
|
|
- <Checkbox
|
|
|
- checked={checked}
|
|
|
- onChange={(e) => setChecked(e.target.checked)}
|
|
|
- >
|
|
|
- {item.label}
|
|
|
- </Checkbox>
|
|
|
- </List.Item>
|
|
|
- )}
|
|
|
- />
|
|
|
+ {/* 错误提示 */}
|
|
|
+ {error && (
|
|
|
+ <Alert
|
|
|
+ message="加载失败"
|
|
|
+ description={error}
|
|
|
+ type="error"
|
|
|
+ showIcon
|
|
|
+ closable
|
|
|
+ style={{ marginBottom: 16 }}
|
|
|
+ action={
|
|
|
+ <Button size="small" onClick={handleReload}>
|
|
|
+ 重试
|
|
|
+ </Button>
|
|
|
+ }
|
|
|
+ />
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 加载状态 */}
|
|
|
+ {loading && (
|
|
|
+ <div style={{ textAlign: 'center', padding: '40px 0' }}>
|
|
|
+ <Spin size="large" tip="正在加载PACS节点..." />
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+
|
|
|
+ {/* 节点列表 */}
|
|
|
+ {!loading && !error && (
|
|
|
+ <>
|
|
|
+ {nodes.length === 0 ? (
|
|
|
+ <Alert
|
|
|
+ message="暂无PACS节点"
|
|
|
+ description="请先在系统管理中配置PACS节点"
|
|
|
+ type="info"
|
|
|
+ showIcon
|
|
|
+ />
|
|
|
+ ) : (
|
|
|
+ <>
|
|
|
+ {/* 全选复选框 */}
|
|
|
+ <div style={{ marginBottom: 16, borderBottom: '1px solid #f0f0f0', paddingBottom: 8 }}>
|
|
|
+ <Checkbox
|
|
|
+ indeterminate={isIndeterminate}
|
|
|
+ checked={isAllSelected}
|
|
|
+ onChange={(e) => handleSelectAll(e.target.checked)}
|
|
|
+ >
|
|
|
+ 全选 ({selectedNodeIds.length}/{nodes.filter(n => n.is_enabled).length})
|
|
|
+ </Checkbox>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* PACS节点列表 */}
|
|
|
+ <List
|
|
|
+ dataSource={nodes}
|
|
|
+ renderItem={(node) => (
|
|
|
+ <List.Item>
|
|
|
+ <Checkbox
|
|
|
+ checked={selectedNodeIds.includes(node.id)}
|
|
|
+ onChange={() => handleNodeToggle(node.id)}
|
|
|
+ disabled={!node.is_enabled}
|
|
|
+ >
|
|
|
+ <div>
|
|
|
+ <div style={{ fontWeight: 500 }}>
|
|
|
+ {node.name}
|
|
|
+ {node.is_default && (
|
|
|
+ <span style={{ marginLeft: 8, color: '#1890ff', fontSize: '12px' }}>
|
|
|
+ [默认]
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
+ {!node.is_enabled && (
|
|
|
+ <span style={{ marginLeft: 8, color: '#999', fontSize: '12px' }}>
|
|
|
+ [已禁用]
|
|
|
+ </span>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+ <div style={{ fontSize: '12px', color: '#666', marginTop: 4 }}>
|
|
|
+ {node.address}:{node.port} | AET: {node.aet}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </Checkbox>
|
|
|
+ </List.Item>
|
|
|
+ )}
|
|
|
+ />
|
|
|
+ </>
|
|
|
+ )}
|
|
|
+ </>
|
|
|
+ )}
|
|
|
</Content>
|
|
|
|
|
|
{/* 底部按钮 */}
|
|
@@ -81,9 +212,10 @@ const SendImagePage = () => {
|
|
|
/>
|
|
|
}
|
|
|
style={{ width: '100%', maxWidth: '400px' }}
|
|
|
- onClick={() => alert('发送图像')}
|
|
|
+ onClick={handleSend}
|
|
|
+ disabled={selectedNodeIds.length === 0 || loading}
|
|
|
>
|
|
|
- 发送图像
|
|
|
+ 发送图像 ({selectedNodeIds.length})
|
|
|
</Button>
|
|
|
</Footer>
|
|
|
</Layout>
|