|
|
@@ -0,0 +1,439 @@
|
|
|
+import React, { useState, useEffect, useRef } from 'react';
|
|
|
+import { Plus, Search, RefreshCw } from 'lucide-react';
|
|
|
+import { segregationPointApi, SegregationPointVO, PageParam } from '../api/spm/index';
|
|
|
+import { marsDeptApi, MarsDeptVO } from '../api/marsdept/index';
|
|
|
+import { lotoStationApi, LotoStationVO } from '../api/lotoStation/index';
|
|
|
+import { technologyApi, TechnologyVO } from '../api/technology/index';
|
|
|
+import { toast } from 'sonner';
|
|
|
+import { Modal, Table, Input, Button, Select, TreeSelect, Space, Image, Switch } from 'antd';
|
|
|
+import { ExclamationCircleOutlined, EditOutlined, DeleteOutlined } from '@ant-design/icons';
|
|
|
+import type { ColumnsType } from 'antd/es/table';
|
|
|
+import { handleTree } from '../utils/tree';
|
|
|
+import { getStrDictOptions, DICT_TYPE } from '../utils/dict';
|
|
|
+import SegregationPointForm, { SegregationPointFormRef } from './SegregationPointForm';
|
|
|
+
|
|
|
+interface SegregationPointManagementProps {
|
|
|
+ subMenu?: string;
|
|
|
+}
|
|
|
+
|
|
|
+export default function SegregationPointManagement({ subMenu }: SegregationPointManagementProps) {
|
|
|
+ const [loading, setLoading] = useState(true);
|
|
|
+ const [list, setList] = useState<SegregationPointVO[]>([]);
|
|
|
+ const [total, setTotal] = useState(0);
|
|
|
+ const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
|
|
+ const [queryParams, setQueryParams] = useState<PageParam>({
|
|
|
+ current: 1,
|
|
|
+ size: 10,
|
|
|
+ pointName: undefined,
|
|
|
+ workstationId: undefined,
|
|
|
+ machineryId: undefined,
|
|
|
+ lotoId: undefined,
|
|
|
+ powerType: undefined,
|
|
|
+ });
|
|
|
+
|
|
|
+ // 下拉选项数据
|
|
|
+ const [deptOptions, setDeptOptions] = useState<any[]>([]);
|
|
|
+ const [machineryOptions, setMachineryOptions] = useState<any[]>([]);
|
|
|
+ const [lotoOptions, setLotoOptions] = useState<Array<{ label: string; value: number }>>([]);
|
|
|
+ const powerTypeOptions = getStrDictOptions(DICT_TYPE.POWER_TYPE);
|
|
|
+
|
|
|
+ const formRef = useRef<SegregationPointFormRef>(null);
|
|
|
+
|
|
|
+ // 获取隔离点列表
|
|
|
+ const getList = async (params?: PageParam) => {
|
|
|
+ const currentParams = params || queryParams;
|
|
|
+ setLoading(true);
|
|
|
+ try {
|
|
|
+ console.log('SegregationPointManagement: 开始获取隔离点列表', currentParams);
|
|
|
+ const response = await segregationPointApi.getIsIsolationPointPage(currentParams);
|
|
|
+ console.log('SegregationPointManagement: API 响应', response);
|
|
|
+
|
|
|
+ // 处理响应数据
|
|
|
+ let data;
|
|
|
+ if (response && typeof response === 'object') {
|
|
|
+ // 如果响应有 data 属性,使用 data;否则直接使用 response
|
|
|
+ if ('data' in response && response.data) {
|
|
|
+ data = response.data;
|
|
|
+ } else if ('list' in response || 'total' in response) {
|
|
|
+ data = response;
|
|
|
+ } else {
|
|
|
+ data = response;
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ data = response;
|
|
|
+ }
|
|
|
+
|
|
|
+ setList(data?.list || []);
|
|
|
+ setTotal(data?.total || 0);
|
|
|
+ console.log('SegregationPointManagement: 设置列表数据', { list: data?.list || [], total: data?.total || 0 });
|
|
|
+ } catch (error: any) {
|
|
|
+ console.error('SegregationPointManagement: 获取列表失败', error);
|
|
|
+ toast.error(error.message || '获取隔离点列表失败');
|
|
|
+ } finally {
|
|
|
+ setLoading(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ useEffect(() => {
|
|
|
+ getList();
|
|
|
+ // eslint-disable-next-line react-hooks/exhaustive-deps
|
|
|
+ }, [queryParams.current, queryParams.size]);
|
|
|
+
|
|
|
+ // 初始化数据
|
|
|
+ useEffect(() => {
|
|
|
+ const initData = async () => {
|
|
|
+ try {
|
|
|
+ // 获取岗位数据
|
|
|
+ const deptRes = await marsDeptApi.listMarsDept({ pageNo: 1, pageSize: -1 });
|
|
|
+ const deptTreeData = handleTree(deptRes.list, 'id', 'parentId');
|
|
|
+ setDeptOptions(convertToTreeSelectData(deptTreeData, 'workstationName'));
|
|
|
+
|
|
|
+ // 获取锁定站数据
|
|
|
+ const lotoRes = await lotoStationApi.listLoto({ pageNo: 1, pageSize: -1 });
|
|
|
+ setLotoOptions(lotoRes.list.map(item => ({
|
|
|
+ value: item.id!,
|
|
|
+ label: item.lotoName,
|
|
|
+ })));
|
|
|
+
|
|
|
+ // 获取设备/工艺数据
|
|
|
+ const techRes = await technologyApi.listTechnology({ pageNo: 1, pageSize: -1 });
|
|
|
+ const techData = techRes.list.filter(item => item.machineryType === '工艺');
|
|
|
+ const techTreeData = handleTree(techData, 'id', 'parentId');
|
|
|
+ setMachineryOptions(convertToTreeSelectData(techTreeData, 'machineryName'));
|
|
|
+ } catch (error) {
|
|
|
+ console.error('初始化数据失败:', error);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ initData();
|
|
|
+ }, []);
|
|
|
+
|
|
|
+ // 转换树形数据为 TreeSelect 格式
|
|
|
+ const convertToTreeSelectData = (treeData: any[], labelKey: string): any[] => {
|
|
|
+ return treeData.map(item => ({
|
|
|
+ title: item[labelKey],
|
|
|
+ value: item.id,
|
|
|
+ key: item.id,
|
|
|
+ children: item.children ? convertToTreeSelectData(item.children, labelKey) : undefined,
|
|
|
+ }));
|
|
|
+ };
|
|
|
+
|
|
|
+ // 搜索
|
|
|
+ const handleQuery = () => {
|
|
|
+ setQueryParams({ ...queryParams, current: 1 });
|
|
|
+ getList({ ...queryParams, current: 1 });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 重置搜索
|
|
|
+ const resetQuery = () => {
|
|
|
+ const resetParams: PageParam = {
|
|
|
+ current: 1,
|
|
|
+ size: 10,
|
|
|
+ pointName: undefined,
|
|
|
+ workstationId: undefined,
|
|
|
+ machineryId: undefined,
|
|
|
+ lotoId: undefined,
|
|
|
+ powerType: undefined,
|
|
|
+ };
|
|
|
+ setQueryParams(resetParams);
|
|
|
+ getList(resetParams);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 打开表单
|
|
|
+ const openForm = (type: string, id?: number) => {
|
|
|
+ formRef.current?.open(type, id);
|
|
|
+ };
|
|
|
+
|
|
|
+ // 删除隔离点
|
|
|
+ const handleDelete = async (id?: number) => {
|
|
|
+ const pointIds = id ? [id] : selectedRowKeys.map(key => Number(key));
|
|
|
+
|
|
|
+ Modal.confirm({
|
|
|
+ title: '确认删除',
|
|
|
+ icon: <ExclamationCircleOutlined />,
|
|
|
+ content: `确定要删除选中的 ${pointIds.length} 条数据吗?`,
|
|
|
+ okText: '确定删除',
|
|
|
+ okType: 'danger',
|
|
|
+ cancelText: '取消',
|
|
|
+ onOk: async () => {
|
|
|
+ try {
|
|
|
+ await segregationPointApi.deleteIsIsolationPointByPointIds(pointIds);
|
|
|
+ toast.success('删除成功');
|
|
|
+ setSelectedRowKeys([]);
|
|
|
+ await getList();
|
|
|
+ } catch (error: any) {
|
|
|
+ toast.error(error.message || '删除失败');
|
|
|
+ }
|
|
|
+ },
|
|
|
+ });
|
|
|
+ };
|
|
|
+
|
|
|
+ // 表格列配置
|
|
|
+ const columns: ColumnsType<SegregationPointVO> = [
|
|
|
+ {
|
|
|
+ title: '隔离点编号',
|
|
|
+ dataIndex: 'id',
|
|
|
+ width: 100,
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '隔离点名称',
|
|
|
+ dataIndex: 'pointName',
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '隔离点图标',
|
|
|
+ dataIndex: 'pointIcon',
|
|
|
+ width: 100,
|
|
|
+ align: 'center',
|
|
|
+ render: (text: string) => {
|
|
|
+ if (text) {
|
|
|
+ return <Image src={text} width={50} height={50} style={{ objectFit: 'cover' }} />;
|
|
|
+ }
|
|
|
+ return '-';
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '开关状态',
|
|
|
+ dataIndex: 'switchStatus',
|
|
|
+ width: 100,
|
|
|
+ align: 'center',
|
|
|
+ render: (status: number | string | null) => {
|
|
|
+ if (status === null || status === undefined) {
|
|
|
+ return '-';
|
|
|
+ }
|
|
|
+ const isChecked = String(status) === '1';
|
|
|
+ return (
|
|
|
+ <Switch
|
|
|
+ checked={isChecked}
|
|
|
+ checkedChildren="ON"
|
|
|
+ unCheckedChildren="OFF"
|
|
|
+ disabled
|
|
|
+ style={{ pointerEvents: 'none' }}
|
|
|
+ />
|
|
|
+ );
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '隔离点NFC',
|
|
|
+ dataIndex: 'pointNfc',
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '岗位',
|
|
|
+ dataIndex: 'workstationName',
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '设备/工艺',
|
|
|
+ dataIndex: 'machineryName',
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '锁定站',
|
|
|
+ dataIndex: 'lotoName',
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '隔离点序列号',
|
|
|
+ dataIndex: 'pointSerialNumber',
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '作用',
|
|
|
+ dataIndex: 'remark',
|
|
|
+ align: 'center',
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '隔离点图片',
|
|
|
+ dataIndex: 'pointPicture',
|
|
|
+ width: 100,
|
|
|
+ align: 'center',
|
|
|
+ render: (text: string) => {
|
|
|
+ if (text) {
|
|
|
+ return <Image src={text} width={50} height={50} style={{ objectFit: 'cover' }} />;
|
|
|
+ }
|
|
|
+ return '-';
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '能量源',
|
|
|
+ dataIndex: 'powerType',
|
|
|
+ align: 'center',
|
|
|
+ render: (value: string) => {
|
|
|
+ const option = powerTypeOptions.find(opt => opt.value === value);
|
|
|
+ return option ? option.label : value || '-';
|
|
|
+ },
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ width: 120,
|
|
|
+ align: 'center',
|
|
|
+ fixed: 'right',
|
|
|
+ render: (_: any, record: SegregationPointVO) => (
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ icon={<EditOutlined />}
|
|
|
+ onClick={() => openForm('update', record.pointId)}
|
|
|
+ title="编辑"
|
|
|
+ />
|
|
|
+ <Button
|
|
|
+ type="link"
|
|
|
+ danger
|
|
|
+ icon={<DeleteOutlined />}
|
|
|
+ onClick={() => handleDelete(record.pointId)}
|
|
|
+ title="删除"
|
|
|
+ />
|
|
|
+ </Space>
|
|
|
+ ),
|
|
|
+ },
|
|
|
+ ];
|
|
|
+
|
|
|
+ return (
|
|
|
+ <div className="p-6 space-y-4">
|
|
|
+ {/* 搜索栏 */}
|
|
|
+ <div className="bg-white rounded-xl border border-gray-200/50 shadow-sm p-5">
|
|
|
+ <div className="flex items-center justify-between gap-4 flex-wrap">
|
|
|
+ {/* 搜索输入框 */}
|
|
|
+ <div className="flex items-center gap-3 flex-wrap flex-1">
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <label className="text-sm font-medium text-gray-700 whitespace-nowrap">隔离点名称:</label>
|
|
|
+ <Input
|
|
|
+ value={queryParams.pointName}
|
|
|
+ onChange={(e) => setQueryParams({ ...queryParams, pointName: e.target.value })}
|
|
|
+ onPressEnter={handleQuery}
|
|
|
+ placeholder="请输入隔离点名称"
|
|
|
+ style={{ width: 192 }}
|
|
|
+ allowClear
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <label className="text-sm font-medium text-gray-700 whitespace-nowrap">岗位:</label>
|
|
|
+ <TreeSelect
|
|
|
+ value={queryParams.workstationId}
|
|
|
+ onChange={(value) => setQueryParams({ ...queryParams, workstationId: value })}
|
|
|
+ treeData={deptOptions}
|
|
|
+ placeholder="选择岗位"
|
|
|
+ allowClear
|
|
|
+ style={{ width: 192 }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <label className="text-sm font-medium text-gray-700 whitespace-nowrap">设备/工艺:</label>
|
|
|
+ <TreeSelect
|
|
|
+ value={queryParams.machineryId}
|
|
|
+ onChange={(value) => setQueryParams({ ...queryParams, machineryId: value })}
|
|
|
+ treeData={machineryOptions}
|
|
|
+ placeholder="选择设备/工艺"
|
|
|
+ allowClear
|
|
|
+ style={{ width: 192 }}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <label className="text-sm font-medium text-gray-700 whitespace-nowrap">锁定站:</label>
|
|
|
+ <Select
|
|
|
+ value={queryParams.lotoId}
|
|
|
+ onChange={(value) => setQueryParams({ ...queryParams, lotoId: value })}
|
|
|
+ placeholder="请选择锁定站"
|
|
|
+ allowClear
|
|
|
+ style={{ width: 192 }}
|
|
|
+ options={lotoOptions}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ <div className="flex items-center gap-3">
|
|
|
+ <label className="text-sm font-medium text-gray-700 whitespace-nowrap">能量源:</label>
|
|
|
+ <Select
|
|
|
+ value={queryParams.powerType}
|
|
|
+ onChange={(value) => setQueryParams({ ...queryParams, powerType: value })}
|
|
|
+ placeholder="请选择能量源"
|
|
|
+ allowClear
|
|
|
+ style={{ width: 192 }}
|
|
|
+ options={powerTypeOptions}
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 操作按钮组 */}
|
|
|
+ <Space>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ icon={<Search />}
|
|
|
+ onClick={handleQuery}
|
|
|
+ >
|
|
|
+ 搜索
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ icon={<RefreshCw />}
|
|
|
+ onClick={resetQuery}
|
|
|
+ >
|
|
|
+ 重置
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ type="primary"
|
|
|
+ icon={<Plus />}
|
|
|
+ onClick={() => openForm('create')}
|
|
|
+ >
|
|
|
+ 新增
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ danger
|
|
|
+ icon={<DeleteOutlined />}
|
|
|
+ disabled={selectedRowKeys.length === 0}
|
|
|
+ onClick={() => handleDelete()}
|
|
|
+ >
|
|
|
+ 批量删除
|
|
|
+ </Button>
|
|
|
+ </Space>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 表格 */}
|
|
|
+ <div className="bg-white rounded-lg border border-gray-200">
|
|
|
+ <Table
|
|
|
+ columns={columns}
|
|
|
+ dataSource={list}
|
|
|
+ rowKey="pointId"
|
|
|
+ loading={loading}
|
|
|
+ pagination={false}
|
|
|
+ scroll={{ x: 'max-content' }}
|
|
|
+ rowSelection={{
|
|
|
+ selectedRowKeys,
|
|
|
+ onChange: (keys) => setSelectedRowKeys(keys),
|
|
|
+ }}
|
|
|
+ />
|
|
|
+
|
|
|
+ {/* 分页 */}
|
|
|
+ {!loading && list.length > 0 && (
|
|
|
+ <div className="bg-white rounded-lg border border-gray-200 px-6 py-4">
|
|
|
+ <div className="flex items-center justify-between">
|
|
|
+ <div className="text-sm text-gray-600">
|
|
|
+ 共 <span className="text-blue-600 font-medium">{total}</span> 条记录
|
|
|
+ </div>
|
|
|
+ <div className="flex gap-2">
|
|
|
+ <Button
|
|
|
+ onClick={() => setQueryParams({ ...queryParams, current: (queryParams.current || 1) - 1 })}
|
|
|
+ disabled={(queryParams.current || 1) <= 1}
|
|
|
+ >
|
|
|
+ 上一页
|
|
|
+ </Button>
|
|
|
+ <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
|
|
|
+ {queryParams.current || 1} / {Math.ceil(total / (queryParams.size || 10)) || 1}
|
|
|
+ </span>
|
|
|
+ <Button
|
|
|
+ onClick={() => setQueryParams({ ...queryParams, current: (queryParams.current || 1) + 1 })}
|
|
|
+ disabled={(queryParams.current || 1) >= Math.ceil(total / (queryParams.size || 10))}
|
|
|
+ >
|
|
|
+ 下一页
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ {/* 表单弹窗 */}
|
|
|
+ <SegregationPointForm ref={formRef} onSuccess={getList} />
|
|
|
+ </div>
|
|
|
+ );
|
|
|
+}
|
|
|
+
|