Kaynağa Gözat

新增挂锁和机柜管理

pm 5 ay önce
ebeveyn
işleme
efc04d3173

+ 25 - 14
src/api/PadLock.ts

@@ -2,14 +2,23 @@ import { request } from '../utils/axios';
 
 export interface PadLockVO {
   lockId?: number;
-  lockCode: string;
+  id?: number; // 兼容字段
+  lockCode?: string;
   lockName: string;
+  lockNfc?: string;
+  hardwareId?: number;
+  hardwareName?: string;
+  lockTypeId?: number;
+  lockTypeName?: string;
+  lockSpec?: string;
+  exStatus?: string; // 状态:'0' 或 '1'
+  exRemark?: string; // 备注
   enableFlag?: string;
   remark?: string;
   createBy?: string;
-  createTime?: Date;
+  createTime?: Date | string;
   updateBy?: string;
-  updateTime?: Date;
+  updateTime?: Date | string;
 }
 
 export interface PageParam {
@@ -17,6 +26,8 @@ export interface PageParam {
   pageSize: number;
   lockCode?: string;
   lockName?: string;
+  lockNfc?: string;
+  lockTypeId?: number;
   enableFlag?: string;
 }
 
@@ -30,7 +41,7 @@ export interface PageResponse<T> {
 export const padLockApi = {
   // 查询挂锁-列表
   listPadLock: async (params: PageParam): Promise<PageResponse<PadLockVO>> => {
-    const response = await request.get({ 
+    const response: any = await request.get({ 
       url: '/iscs/lock/getLockPage', 
       params 
     });
@@ -41,26 +52,26 @@ export const padLockApi = {
         return response.data;
       } else if ('list' in response || 'records' in response) {
         return {
-          list: response.list || response.records || [],
-          total: response.total || 0,
+          list: (response as any).list || (response as any).records || [],
+          total: (response as any).total || 0,
         };
       }
     }
-    return response;
+    return response as PageResponse<PadLockVO>;
   },
 
   // 获取挂锁详细信息
   getPadLockInfo: async (id: number): Promise<PadLockVO> => {
-    const response = await request.get({
+    const response: any = await request.get({
       url: '/iscs/lock/selectLockById',
       params: { id }
     });
-    return response?.data || response;
+    return (response?.data || response) as PadLockVO;
   },
 
   // 新增挂锁
   addPadLock: async (data: PadLockVO): Promise<void> => {
-    return await request.post({ 
+    await request.post({ 
       url: '/iscs/lock/insertLock', 
       data 
     });
@@ -68,16 +79,16 @@ export const padLockApi = {
 
   // 修改挂锁信息
   updatePadLock: async (data: PadLockVO): Promise<void> => {
-    return await request.put({ 
+    await request.put({ 
       url: '/iscs/lock/updateLock', 
       data 
     });
   },
 
   // 删除挂锁信息
-  delPadLock: async (ids: number | number[]): Promise<void> => {
-    const idsParam = Array.isArray(ids) ? ids.join(',') : ids;
-    return await request.delete({
+  delPadLock: async (ids: number | number[] | string): Promise<void> => {
+    const idsParam = typeof ids === 'string' ? ids : (Array.isArray(ids) ? ids.join(',') : ids);
+    await request.delete({
       url: `/iscs/lock/deleteLockList?ids=${idsParam}`,
     });
   },

+ 16 - 10
src/api/PadLockType.ts

@@ -2,15 +2,21 @@ import { request } from '../utils/axios';
 
 export interface PadLockTypeVO {
   lockTypeId?: number;
+  id?: number; // 兼容字段
   parentTypeId?: number;
-  lockTypeCode: string;
+  lockTypeCode?: string;
   lockTypeName: string;
+  lockTypeIcon?: string;
+  lockTypeImg?: string;
+  lockTypeSpec?: string;
+  hardwareTypeId?: number;
   enableFlag?: string;
   remark?: string;
   createBy?: string;
   createTime?: Date;
   updateBy?: string;
   updateTime?: Date;
+  children?: PadLockTypeVO[]; // 树形结构
 }
 
 export interface PageParam {
@@ -31,7 +37,7 @@ export interface PageResponse<T> {
 export const padLockTypeApi = {
   // 查询挂锁类型-列表
   listPadLockType: async (params: PageParam): Promise<PageResponse<PadLockTypeVO>> => {
-    const response = await request.get({ 
+    const response: any = await request.get({ 
       url: '/iscs/lock-type/getLockTypePage', 
       params 
     });
@@ -42,26 +48,26 @@ export const padLockTypeApi = {
         return response.data;
       } else if ('list' in response || 'records' in response) {
         return {
-          list: response.list || response.records || [],
-          total: response.total || 0,
+          list: (response as any).list || (response as any).records || [],
+          total: (response as any).total || 0,
         };
       }
     }
-    return response;
+    return response as PageResponse<PadLockTypeVO>;
   },
 
   // 获取挂锁类型详细信息
   getPadLockTypeInfo: async (id: number): Promise<PadLockTypeVO> => {
-    const response = await request.get({
+    const response: any = await request.get({
       url: '/iscs/lock-type/selectLockTypeById',
       params: { id }
     });
-    return response?.data || response;
+    return (response?.data || response) as PadLockTypeVO;
   },
 
   // 新增挂锁类型
   addPadLockType: async (data: PadLockTypeVO): Promise<void> => {
-    return await request.post({ 
+    await request.post({ 
       url: '/iscs/lock-type/insertLockType', 
       data 
     });
@@ -69,7 +75,7 @@ export const padLockTypeApi = {
 
   // 修改挂锁类型信息
   updatePadLockType: async (data: PadLockTypeVO): Promise<void> => {
-    return await request.put({ 
+    await request.put({ 
       url: '/iscs/lock-type/updateLockType', 
       data 
     });
@@ -78,7 +84,7 @@ export const padLockTypeApi = {
   // 删除挂锁类型信息
   delPadLockType: async (ids: number | number[]): Promise<void> => {
     const idsParam = Array.isArray(ids) ? ids.join(',') : ids;
-    return await request.delete({
+    await request.delete({
       url: `/iscs/lock-type/deleteLockTypeList?ids=${idsParam}`,
     });
   },

+ 3 - 1
src/components/HardwareManagement.tsx

@@ -270,7 +270,9 @@ export default function HardwareManagement({ subMenu }: HardwareManagementProps)
   ];
 
   // 机柜管理使用专用组件
-  if (subMenu === '机柜' || subMenu === 'cabinet') {
+  // subMenu 可能是 '机柜'、'cabinet' 或 'hwcabinet'
+  if (subMenu === '机柜' || subMenu === 'cabinet' || subMenu === 'hwcabinet') {
+    console.log('HardwareManagement: 渲染机柜管理组件,subMenu:', subMenu);
     return <HardwareLockCabinetManagement key="hardware-lock-cabinet-management" />;
   }
 

+ 689 - 313
src/components/PadLockManagement.tsx

@@ -1,11 +1,17 @@
 import React, { useState, useEffect, useRef } from 'react';
-import { Search, Plus, RefreshCw, Edit2, Trash2, Settings } from 'lucide-react';
+import { Search, Plus, RefreshCw, Edit2, Trash2, Settings, ArrowUpDown } from 'lucide-react';
 import { padLockApi, PadLockVO, PageParam } from '../api/PadLock';
 import { padLockTypeApi, PadLockTypeVO, PageParam as PadLockTypePageParam } from '../api/PadLockType';
+import { hardwareApi } from '../api/Hardware';
 import { toast } from 'sonner';
-import { Modal, Button, Input, Space, Table, Select } from 'antd';
+import { Modal, Button, Input, Space, Table, Select, TreeSelect, Form, Image, Switch, Radio } from 'antd';
 import { ExclamationCircleOutlined } from '@ant-design/icons';
 import type { ColumnsType } from 'antd/es/table';
+import { handleTree } from '../utils/tree';
+import { DICT_TYPE, getStrDictOptions } from '../utils/dict';
+import { dateFormatter } from '../utils/formatTime';
+import UploadImg from './lockCabinet/UploadImg';
+import { Button as UIButton } from './ui/button';
 
 interface PadLockManagementProps {
   subMenu?: string;
@@ -16,6 +22,7 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
   const [loading, setLoading] = useState(true);
   const [list, setList] = useState<PadLockVO[]>([]);
   const [total, setTotal] = useState(0);
+  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
   const [queryParams, setQueryParams] = useState<PageParam>({
     pageNo: 1,
     pageSize: 10,
@@ -23,20 +30,26 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
     lockName: undefined,
     enableFlag: undefined,
   });
+  const [showPadLockForm, setShowPadLockForm] = useState(false);
+  const [editingPadLock, setEditingPadLock] = useState<PadLockVO | null>(null);
 
   // 挂锁类型相关状态
   const [typeLoading, setTypeLoading] = useState(true);
   const [typeList, setTypeList] = useState<PadLockTypeVO[]>([]);
+  const [typeTreeList, setTypeTreeList] = useState<PadLockTypeVO[]>([]);
   const [typeTotal, setTypeTotal] = useState(0);
   const [typeQueryParams, setTypeQueryParams] = useState<PadLockTypePageParam>({
     pageNo: 1,
-    pageSize: 10,
+    pageSize: -1, // 使用 -1 获取全部数据用于树形结构
     lockTypeCode: undefined,
     lockTypeName: undefined,
     enableFlag: undefined,
   });
   const [showTypeForm, setShowTypeForm] = useState(false);
   const [editingType, setEditingType] = useState<PadLockTypeVO | null>(null);
+  const [parentTypeId, setParentTypeId] = useState<number | undefined>(undefined);
+  const [expandedRowKeys, setExpandedRowKeys] = useState<React.Key[]>([]);
+  const [isExpandAll, setIsExpandAll] = useState(true);
 
   // 获取挂锁列表
   const getList = async (params?: PageParam) => {
@@ -59,8 +72,36 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
     setTypeLoading(true);
     try {
       const response = await padLockTypeApi.listPadLockType(currentParams);
-      setTypeList(response.list || []);
+      const flatList = response.list || [];
+      setTypeList(flatList);
       setTypeTotal(response.total || 0);
+      
+      // 转换为树形结构
+      const treeData = handleTree<PadLockTypeVO>(
+        flatList.map(item => ({ ...item, id: item.lockTypeId || item.id })),
+        'id',
+        'parentTypeId',
+        'children'
+      );
+      setTypeTreeList(treeData);
+      
+      // 默认展开所有节点
+      if (isExpandAll && treeData.length > 0) {
+        const getAllIds = (nodes: PadLockTypeVO[]): React.Key[] => {
+          const ids: React.Key[] = [];
+          nodes.forEach(node => {
+            const id = node.lockTypeId || node.id;
+            if (id) {
+              ids.push(id);
+              if (node.children && node.children.length > 0) {
+                ids.push(...getAllIds(node.children));
+              }
+            }
+          });
+          return ids;
+        };
+        setExpandedRowKeys(getAllIds(treeData));
+      }
     } catch (error: any) {
       toast.error(error.message || '获取挂锁类型列表失败');
     } finally {
@@ -117,13 +158,19 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
   };
 
   // 删除挂锁
-  const handleDelete = async (id: number, name?: string) => {
+  const handleDelete = async (id?: number) => {
+    const ids = id ? [id] : selectedRowKeys.map(key => Number(key));
+    if (ids.length === 0) {
+      toast.error('请选择要删除的数据');
+      return;
+    }
+
     Modal.confirm({
       title: '确认删除',
       icon: <ExclamationCircleOutlined />,
       content: (
         <div>
-          <p>确定要删除挂锁 <strong>"{name || '该挂锁'}"</strong> 吗?</p>
+          <p>确定要删除选中的 {ids.length} 条挂锁数据吗?</p>
           <p style={{ color: '#ff4d4f', marginTop: '8px' }}>删除后无法恢复,请谨慎操作!</p>
         </div>
       ),
@@ -132,8 +179,9 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
       cancelText: '取消',
       onOk: async () => {
         try {
-          await padLockApi.delPadLock(id);
+          await padLockApi.delPadLock(ids);
           toast.success('删除成功');
+          setSelectedRowKeys([]);
           await getList();
         } catch (error: any) {
           toast.error(error.message || '删除失败');
@@ -142,6 +190,42 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
     });
   };
 
+  // 打开挂锁表单
+  const openPadLockForm = (type: 'create' | 'update', id?: number) => {
+    if (type === 'create') {
+      setEditingPadLock(null);
+      setShowPadLockForm(true);
+    } else if (type === 'update' && id) {
+      padLockApi.getPadLockInfo(id).then((data) => {
+        setEditingPadLock(data);
+        setShowPadLockForm(true);
+      }).catch((error: any) => {
+        toast.error(error.message || '获取挂锁信息失败');
+      });
+    }
+  };
+
+  // 保存挂锁
+  const savePadLock = async (formData: PadLockVO) => {
+    try {
+      if (editingPadLock?.lockId || editingPadLock?.id) {
+        await padLockApi.updatePadLock({
+          ...formData,
+          lockId: editingPadLock.lockId || editingPadLock.id,
+        });
+        toast.success('修改成功');
+      } else {
+        await padLockApi.addPadLock(formData);
+        toast.success('新增成功');
+      }
+      setShowPadLockForm(false);
+      setEditingPadLock(null);
+      await getList();
+    } catch (error: any) {
+      toast.error(error.message || '保存失败');
+    }
+  };
+
   // 删除挂锁类型
   const handleDeleteType = async (id: number, name?: string) => {
     Modal.confirm({
@@ -169,13 +253,15 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
   };
 
   // 打开挂锁类型表单
-  const openTypeForm = (type?: 'create' | 'update', id?: number) => {
+  const openTypeForm = (type?: 'create' | 'update', id?: number, parentId?: number) => {
     if (type === 'create') {
       setEditingType(null);
+      setParentTypeId(parentId);
       setShowTypeForm(true);
     } else if (type === 'update' && id) {
       padLockTypeApi.getPadLockTypeInfo(id).then((data) => {
         setEditingType(data);
+        setParentTypeId(undefined);
         setShowTypeForm(true);
       }).catch((error: any) => {
         toast.error(error.message || '获取挂锁类型信息失败');
@@ -183,13 +269,36 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
     }
   };
 
+  // 展开/折叠所有
+  const toggleExpandAll = () => {
+    if (isExpandAll) {
+      setExpandedRowKeys([]);
+    } else {
+      const getAllIds = (nodes: PadLockTypeVO[]): React.Key[] => {
+        const ids: React.Key[] = [];
+        nodes.forEach(node => {
+          const id = node.lockTypeId || node.id;
+          if (id) {
+            ids.push(id);
+            if (node.children && node.children.length > 0) {
+              ids.push(...getAllIds(node.children));
+            }
+          }
+        });
+        return ids;
+      };
+      setExpandedRowKeys(getAllIds(typeTreeList));
+    }
+    setIsExpandAll(!isExpandAll);
+  };
+
   // 保存挂锁类型
   const saveType = async (formData: PadLockTypeVO) => {
     try {
-      if (editingType?.lockTypeId) {
+      if (editingType?.lockTypeId || editingType?.id) {
         await padLockTypeApi.updatePadLockType({
           ...formData,
-          lockTypeId: editingType.lockTypeId,
+          lockTypeId: editingType.lockTypeId || editingType.id,
         });
         toast.success('修改成功');
       } else {
@@ -198,6 +307,7 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
       }
       setShowTypeForm(false);
       setEditingType(null);
+      setParentTypeId(undefined);
       await getTypeList();
     } catch (error: any) {
       toast.error(error.message || '保存失败');
@@ -207,139 +317,187 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
   // 挂锁列表表格列
   const padLockColumns: ColumnsType<PadLockVO> = [
     {
-      title: '序号',
-      width: 80,
-      align: 'center',
-      render: (_: any, __: PadLockVO, index: number) => {
-        return (queryParams.pageNo - 1) * queryParams.pageSize + index + 1;
-      },
+      title: '挂锁名称',
+      dataIndex: 'lockName',
+      width: 200,
     },
     {
-      title: '挂锁编号',
-      dataIndex: 'lockId',
-      width: 100,
-      align: 'center',
+      title: '挂锁NFC',
+      dataIndex: 'lockNfc',
+      width: 180,
+      ellipsis: true,
     },
     {
-      title: '挂锁编码',
-      dataIndex: 'lockCode',
+      title: '所属硬件',
+      dataIndex: 'hardwareName',
       width: 150,
+      render: (text: string) => text || '-',
     },
     {
-      title: '挂锁名称',
-      dataIndex: 'lockName',
-      width: 200,
+      title: '挂锁类型',
+      dataIndex: 'lockTypeName',
+      width: 150,
+      render: (text: string) => text || '-',
+    },
+    {
+      title: '挂锁型号',
+      dataIndex: 'lockSpec',
+      width: 150,
+      render: (text: string) => text || '-',
     },
     {
       title: '状态',
-      dataIndex: 'enableFlag',
+      dataIndex: 'exStatus',
       width: 100,
       align: 'center',
-      render: (flag: string) => {
-        return flag === '1' || flag === 'Y' ? '启用' : '禁用';
+      render: (status: string) => {
+        if (status === null || status === undefined) return '-';
+        return (
+          <Switch
+            checked={status === '1'}
+            disabled
+            checkedChildren="启用"
+            unCheckedChildren="禁用"
+          />
+        );
       },
     },
     {
       title: '备注',
-      dataIndex: 'remark',
+      dataIndex: 'exRemark',
       ellipsis: true,
+      render: (text: string) => text || '-',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      width: 180,
+      render: (text: string | Date) => text ? dateFormatter(text) : '-',
     },
     {
       title: '操作',
-      width: 120,
+      width: 150,
       align: 'center',
       fixed: 'right',
       render: (_: any, record: PadLockVO) => (
-        <Space>
-          <Button
-            type="link"
-            icon={<Edit2 className="w-4 h-4" />}
-            onClick={() => {
-              // TODO: 实现编辑功能
-              toast.info('编辑功能待实现');
-            }}
-            title="编辑"
-          />
-          <Button
-            type="link"
-            danger
-            icon={<Trash2 className="w-4 h-4" />}
-            onClick={() => handleDelete(record.lockId!, record.lockName)}
-            title="删除"
-          />
-        </Space>
+        <div className="flex items-center gap-2 justify-center">
+          <UIButton
+            variant="ghost"
+            size="sm"
+            onClick={() => openPadLockForm('update', record.lockId || record.id)}
+            className="h-8 px-2"
+          >
+            <Edit2 className="w-4 h-4" />
+            <span className="ml-1">编辑</span>
+          </UIButton>
+          <UIButton
+            variant="ghost"
+            size="sm"
+            onClick={() => handleDelete(record.lockId || record.id)}
+            className="h-8 px-2 text-red-600 hover:text-red-700"
+          >
+            <Trash2 className="w-4 h-4" />
+            <span className="ml-1">删除</span>
+          </UIButton>
+        </div>
       ),
     },
   ];
 
-  // 挂锁类型表格列
+  // 挂锁类型表格列(树形表格)
   const padLockTypeColumns: ColumnsType<PadLockTypeVO> = [
     {
-      title: '序号',
-      width: 80,
-      align: 'center',
-      render: (_: any, __: PadLockTypeVO, index: number) => {
-        return (typeQueryParams.pageNo - 1) * typeQueryParams.pageSize + index + 1;
-      },
-    },
-    {
-      title: '类型编号',
-      dataIndex: 'lockTypeId',
-      width: 100,
-      align: 'center',
-    },
-    {
-      title: '类型编码',
-      dataIndex: 'lockTypeCode',
-      width: 150,
-    },
-    {
-      title: '类型名称',
+      title: '挂锁类型名称',
       dataIndex: 'lockTypeName',
       width: 200,
+      align: 'center',
     },
     {
-      title: '父类型',
-      dataIndex: 'parentTypeId',
-      width: 100,
+      title: '挂锁类型图标',
+      dataIndex: 'lockTypeIcon',
+      width: 120,
       align: 'center',
-      render: (id: number) => id || '-',
+      render: (url: string) => {
+        if (!url) return '-';
+        return (
+          <Image
+            src={url}
+            alt="图标"
+            width={50}
+            height={50}
+            style={{ objectFit: 'cover' }}
+            preview={{
+              mask: '查看',
+            }}
+          />
+        );
+      },
     },
     {
-      title: '状态',
-      dataIndex: 'enableFlag',
-      width: 100,
+      title: '挂锁类型图片',
+      dataIndex: 'lockTypeImg',
+      width: 120,
       align: 'center',
-      render: (flag: string) => {
-        return flag === '1' || flag === 'Y' ? '启用' : '禁用';
+      render: (url: string) => {
+        if (!url) return '-';
+        return (
+          <Image
+            src={url}
+            alt="图片"
+            width={50}
+            height={50}
+            style={{ objectFit: 'cover' }}
+            preview={{
+              mask: '查看',
+            }}
+          />
+        );
       },
     },
     {
-      title: '备注',
-      dataIndex: 'remark',
-      ellipsis: true,
+      title: '挂锁型号',
+      dataIndex: 'lockTypeSpec',
+      width: 150,
+      align: 'center',
+      render: (spec: string) => spec || '-',
     },
     {
       title: '操作',
-      width: 120,
+      width: 200,
       align: 'center',
       fixed: 'right',
       render: (_: any, record: PadLockTypeVO) => (
-        <Space>
-          <Button
-            type="link"
-            icon={<Edit2 className="w-4 h-4" />}
-            onClick={() => openTypeForm('update', record.lockTypeId)}
-            title="编辑"
-          />
-          <Button
-            type="link"
-            danger
-            icon={<Trash2 className="w-4 h-4" />}
-            onClick={() => handleDeleteType(record.lockTypeId!, record.lockTypeName)}
-            title="删除"
-          />
-        </Space>
+        <div className="flex items-center gap-2 justify-center">
+          <UIButton
+            variant="ghost"
+            size="sm"
+            onClick={() => openTypeForm('update', record.lockTypeId || record.id)}
+            className="h-8 px-2"
+          >
+            <Edit2 className="w-4 h-4" />
+            <span className="ml-1">修改</span>
+          </UIButton>
+          <UIButton
+            variant="ghost"
+            size="sm"
+            onClick={() => openTypeForm('create', undefined, record.lockTypeId || record.id)}
+            className="h-8 px-2"
+          >
+            <Plus className="w-4 h-4" />
+            <span className="ml-1">新增</span>
+          </UIButton>
+          {record.parentTypeId !== 0 && (
+            <UIButton
+              variant="ghost"
+              size="sm"
+              onClick={() => handleDeleteType(record.lockTypeId || record.id!, record.lockTypeName)}
+              className="h-8 px-2 text-red-600 hover:text-red-700"
+            >
+              <Trash2 className="w-4 h-4" />
+              <span className="ml-1">删除</span>
+            </UIButton>
+          )}
+        </div>
       ),
     },
   ];
@@ -348,22 +506,13 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
     <div className="space-y-4">
       {viewMode === 'list' ? (
         <>
-          {/* 挂锁列表搜索栏 */}
-          <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="bg-white rounded-xl border border-gray-200/50 shadow-sm overflow-hidden">
+            {/* 搜索栏 */}
+            <div className="p-5 border-b border-gray-200">
+              <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.lockCode || ''}
-                    onChange={(e) => setQueryParams({ ...queryParams, lockCode: 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>
                   <Input
@@ -377,32 +526,38 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
                 </div>
               </div>
 
-              {/* 操作按钮组 */}
-              <Space>
+                {/* 操作按钮组 */}
+                <Space>
+                  <Button
+                    type="primary"
+                    icon={<Search className="w-4 h-4" />}
+                    onClick={handleQuery}
+                  >
+                    搜索
+                  </Button>
+                  
+                  <Button
+                    icon={<RefreshCw className="w-4 h-4" />}
+                    onClick={resetQuery}
+                  >
+                    重置
+                  </Button>
+                  
                 <Button
                   type="primary"
-                  icon={<Search className="w-4 h-4" />}
-                  onClick={handleQuery}
+                  icon={<Plus className="w-4 h-4" />}
+                  onClick={() => openPadLockForm('create')}
                 >
-                  搜索
+                  新增
                 </Button>
                 
                 <Button
-                  icon={<RefreshCw className="w-4 h-4" />}
-                  onClick={resetQuery}
+                  danger
+                  icon={<Trash2 className="w-4 h-4" />}
+                  onClick={() => handleDelete()}
+                  disabled={selectedRowKeys.length === 0}
                 >
-                  重置
-                </Button>
-                
-                <Button
-                  type="primary"
-                  icon={<Plus className="w-4 h-4" />}
-                  onClick={() => {
-                    // TODO: 实现新增挂锁功能
-                    toast.info('新增挂锁功能待实现');
-                  }}
-                >
-                  新增
+                  批量删除
                 </Button>
                 
                 <Button
@@ -411,20 +566,25 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
                 >
                   设置挂锁类型
                 </Button>
-              </Space>
+                </Space>
+              </div>
             </div>
-          </div>
 
-          {/* 挂锁列表表格 */}
-          <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm overflow-hidden">
-            <Table
-              columns={padLockColumns}
-              dataSource={list}
-              rowKey="lockId"
-              loading={loading}
-              pagination={false}
-              scroll={{ x: 'max-content' }}
-            />
+            {/* 表格 */}
+            <div>
+              <Table
+                columns={padLockColumns}
+                dataSource={list}
+                rowKey={(record) => record.lockId || record.id || ''}
+                loading={loading}
+                pagination={false}
+                scroll={{ x: 'max-content' }}
+                rowSelection={{
+                  selectedRowKeys,
+                  onChange: setSelectedRowKeys,
+                }}
+              />
+            </div>
           </div>
 
           {/* 分页 */}
@@ -457,117 +617,97 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
         </>
       ) : (
         <>
-          {/* 挂锁类型搜索栏 */}
-          <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={typeQueryParams.lockTypeCode || ''}
-                    onChange={(e) => setTypeQueryParams({ ...typeQueryParams, lockTypeCode: e.target.value })}
-                    onPressEnter={handleTypeQuery}
-                    placeholder="请输入类型编码"
-                    style={{ width: 192 }}
-                    allowClear
-                  />
+          {/* 挂锁类型 - 搜索栏和表格放在一起 */}
+          <div className="bg-white rounded-xl border border-gray-200/50 shadow-sm overflow-hidden">
+            {/* 搜索栏 */}
+            <div className="p-5 border-b border-gray-200">
+              <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={typeQueryParams.lockTypeName || ''}
+                      onChange={(e) => setTypeQueryParams({ ...typeQueryParams, lockTypeName: e.target.value })}
+                      onPressEnter={handleTypeQuery}
+                      placeholder="请输入类型名称"
+                      style={{ width: 192 }}
+                      allowClear
+                    />
+                  </div>
                 </div>
-                <div className="flex items-center gap-3">
-                  <label className="text-sm font-medium text-gray-700 whitespace-nowrap">类型名称:</label>
-                  <Input
-                    value={typeQueryParams.lockTypeName || ''}
-                    onChange={(e) => setTypeQueryParams({ ...typeQueryParams, lockTypeName: e.target.value })}
-                    onPressEnter={handleTypeQuery}
-                    placeholder="请输入类型名称"
-                    style={{ width: 192 }}
-                    allowClear
-                  />
-                </div>
-              </div>
 
-              {/* 操作按钮组 */}
-              <Space>
-                <Button
-                  type="primary"
-                  icon={<Search className="w-4 h-4" />}
-                  onClick={handleTypeQuery}
-                >
-                  搜索
-                </Button>
-                
-                <Button
-                  icon={<RefreshCw className="w-4 h-4" />}
-                  onClick={resetTypeQuery}
-                >
-                  重置
-                </Button>
-                
-                <Button
-                  type="primary"
-                  icon={<Plus className="w-4 h-4" />}
-                  onClick={() => openTypeForm('create')}
-                >
-                  新增
-                </Button>
-                
-                <Button
-                  onClick={() => setViewMode('list')}
-                >
-                  返回挂锁列表
-                </Button>
-              </Space>
-            </div>
-          </div>
-
-          {/* 挂锁类型表格 */}
-          <div className="bg-white rounded-2xl border border-gray-200/50 shadow-sm overflow-hidden">
-            <Table
-              columns={padLockTypeColumns}
-              dataSource={typeList}
-              rowKey="lockTypeId"
-              loading={typeLoading}
-              pagination={false}
-              scroll={{ x: 'max-content' }}
-            />
-          </div>
-
-          {/* 分页 */}
-          {!typeLoading && typeList.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">{typeTotal}</span> 条记录
-                </div>
-                <div className="flex gap-2">
+                {/* 操作按钮组 */}
+                <Space>
                   <Button
-                    onClick={() => setTypeQueryParams({ ...typeQueryParams, pageNo: typeQueryParams.pageNo! - 1 })}
-                    disabled={typeQueryParams.pageNo! <= 1}
+                    type="primary"
+                    icon={<Search className="w-4 h-4" />}
+                    onClick={handleTypeQuery}
                   >
-                    上一页
+                    搜索
                   </Button>
-                  <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
-                    {typeQueryParams.pageNo} / {Math.ceil(typeTotal / typeQueryParams.pageSize!) || 1}
-                  </span>
+                  
                   <Button
-                    onClick={() => setTypeQueryParams({ ...typeQueryParams, pageNo: typeQueryParams.pageNo! + 1 })}
-                    disabled={typeQueryParams.pageNo! >= Math.ceil(typeTotal / typeQueryParams.pageSize!)}
+                    icon={<RefreshCw className="w-4 h-4" />}
+                    onClick={resetTypeQuery}
                   >
-                    下一页
+                    重置
                   </Button>
-                </div>
+                  
+                  <Button
+                    type="primary"
+                    icon={<Plus className="w-4 h-4" />}
+                    onClick={() => openTypeForm('create')}
+                  >
+                    新增
+                  </Button>
+                  
+                  <Button
+                    icon={<ArrowUpDown className="w-4 h-4" />}
+                    onClick={toggleExpandAll}
+                  >
+                    展开/折叠
+                  </Button>
+                  
+                  <Button
+                    onClick={() => setViewMode('list')}
+                  >
+                    返回挂锁列表
+                  </Button>
+                </Space>
               </div>
             </div>
-          )}
+
+            {/* 表格 - 树形表格 */}
+            <div>
+              <Table
+                columns={padLockTypeColumns}
+                dataSource={typeTreeList}
+                rowKey={(record) => record.lockTypeId || record.id || ''}
+                loading={typeLoading}
+                pagination={false}
+                scroll={{ x: 'max-content' }}
+                expandable={{
+                  expandedRowKeys,
+                  onExpandedRowsChange: setExpandedRowKeys,
+                  defaultExpandAllRows: isExpandAll,
+                }}
+              />
+            </div>
+          </div>
+
 
           {/* 挂锁类型表单弹窗 */}
           {showTypeForm && (
             <TypeFormModal
               visible={showTypeForm}
               editingType={editingType}
+              parentTypeId={parentTypeId}
+              typeList={typeList}
               onCancel={() => {
                 setShowTypeForm(false);
                 setEditingType(null);
+                setParentTypeId(undefined);
               }}
               onSave={saveType}
             />
@@ -582,49 +722,78 @@ export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
 interface TypeFormModalProps {
   visible: boolean;
   editingType: PadLockTypeVO | null;
+  parentTypeId?: number;
+  typeList: PadLockTypeVO[];
   onCancel: () => void;
   onSave: (data: PadLockTypeVO) => void;
 }
 
-function TypeFormModal({ visible, editingType, onCancel, onSave }: TypeFormModalProps) {
-  const [formData, setFormData] = useState<PadLockTypeVO>({
-    lockTypeCode: '',
-    lockTypeName: '',
-    parentTypeId: undefined,
-    enableFlag: '1',
-    remark: '',
-  });
+function TypeFormModal({ visible, editingType, parentTypeId, typeList, onCancel, onSave }: TypeFormModalProps) {
+  const [form] = Form.useForm();
+  const [formLoading, setFormLoading] = useState(false);
+  const [typeTreeOptions, setTypeTreeOptions] = useState<any[]>([]);
 
+  // 构建树形选择器数据
   useEffect(() => {
-    if (editingType) {
-      setFormData({
-        lockTypeCode: editingType.lockTypeCode || '',
-        lockTypeName: editingType.lockTypeName || '',
-        parentTypeId: editingType.parentTypeId,
-        enableFlag: editingType.enableFlag || '1',
-        remark: editingType.remark || '',
-      });
-    } else {
-      setFormData({
-        lockTypeCode: '',
-        lockTypeName: '',
-        parentTypeId: undefined,
-        enableFlag: '1',
-        remark: '',
-      });
+    if (typeList.length > 0) {
+      const treeData = handleTree<PadLockTypeVO>(
+        typeList.map(item => ({ ...item, id: item.lockTypeId || item.id })),
+        'id',
+        'parentTypeId',
+        'children'
+      );
+      
+      const buildTreeSelectData = (nodes: PadLockTypeVO[]): any[] => {
+        return nodes.map(node => ({
+          title: node.lockTypeName,
+          value: node.lockTypeId || node.id,
+          children: node.children ? buildTreeSelectData(node.children) : undefined,
+        }));
+      };
+      
+      setTypeTreeOptions(buildTreeSelectData(treeData));
     }
-  }, [editingType, visible]);
+  }, [typeList]);
 
-  const handleSubmit = () => {
-    if (!formData.lockTypeCode.trim()) {
-      toast.error('请输入类型编码');
-      return;
+  useEffect(() => {
+    if (visible) {
+      if (editingType) {
+        form.setFieldsValue({
+          lockTypeName: editingType.lockTypeName || '',
+          parentTypeId: editingType.parentTypeId === 0 ? undefined : editingType.parentTypeId,
+          lockTypeSpec: editingType.lockTypeSpec || '',
+          lockTypeIcon: editingType.lockTypeIcon || '',
+          lockTypeImg: editingType.lockTypeImg || '',
+        });
+      } else {
+        form.setFieldsValue({
+          lockTypeName: '',
+          parentTypeId: parentTypeId || undefined,
+          lockTypeSpec: '',
+          lockTypeIcon: '',
+          lockTypeImg: '',
+        });
+      }
     }
-    if (!formData.lockTypeName.trim()) {
-      toast.error('请输入类型名称');
-      return;
+  }, [visible, editingType, parentTypeId, form]);
+
+  const handleSubmit = async () => {
+    try {
+      const values = await form.validateFields();
+      setFormLoading(true);
+      
+      const submitData: PadLockTypeVO = {
+        ...values,
+        parentTypeId: values.parentTypeId || 0,
+        lockTypeId: editingType?.lockTypeId || editingType?.id,
+      };
+      
+      onSave(submitData);
+    } catch (error) {
+      // 表单验证失败
+    } finally {
+      setFormLoading(false);
     }
-    onSave(formData);
   };
 
   return (
@@ -635,58 +804,265 @@ function TypeFormModal({ visible, editingType, onCancel, onSave }: TypeFormModal
       onCancel={onCancel}
       okText="确定"
       cancelText="取消"
+      confirmLoading={formLoading}
       width={600}
     >
-      <div className="space-y-4 mt-4">
-        <div>
-          <label className="block text-sm text-gray-700 mb-2">类型编码 *</label>
-          <Input
-            value={formData.lockTypeCode}
-            onChange={(e) => setFormData({ ...formData, lockTypeCode: e.target.value })}
-            placeholder="请输入类型编码"
-          />
-        </div>
-        <div>
-          <label className="block text-sm text-gray-700 mb-2">类型名称 *</label>
-          <Input
-            value={formData.lockTypeName}
-            onChange={(e) => setFormData({ ...formData, lockTypeName: e.target.value })}
-            placeholder="请输入类型名称"
+      <Form
+        form={form}
+        layout="vertical"
+        className="mt-4"
+      >
+        {editingType?.parentTypeId !== 0 && (
+          <Form.Item
+            label="父类型"
+            name="parentTypeId"
+          >
+            <TreeSelect
+              treeData={typeTreeOptions}
+              placeholder="选择父级"
+              allowClear
+              treeDefaultExpandAll
+            />
+          </Form.Item>
+        )}
+        
+        <Form.Item
+          label="挂锁类型名称"
+          name="lockTypeName"
+          rules={[{ required: true, message: '请输入挂锁类型名称' }]}
+        >
+          <Input placeholder="请输入挂锁类型名称" />
+        </Form.Item>
+        
+        <Form.Item
+          label="挂锁型号"
+          name="lockTypeSpec"
+        >
+          <Input placeholder="请输入挂锁型号" />
+        </Form.Item>
+        
+        <Form.Item
+          label="挂锁类型图标"
+          name="lockTypeIcon"
+        >
+          <UploadImg
+            value={form.getFieldValue('lockTypeIcon')}
+            onChange={(value) => form.setFieldsValue({ lockTypeIcon: value })}
+            limit={1}
+            height="100px"
+            width="100px"
           />
-        </div>
-        <div>
-          <label className="block text-sm text-gray-700 mb-2">父类型ID</label>
-          <Input
-            type="number"
-            value={formData.parentTypeId || ''}
-            onChange={(e) => setFormData({ 
-              ...formData, 
-              parentTypeId: e.target.value ? Number(e.target.value) : undefined 
-            })}
-            placeholder="请输入父类型ID(可选)"
+        </Form.Item>
+        
+        <Form.Item
+          label="挂锁类型图片"
+          name="lockTypeImg"
+        >
+          <UploadImg
+            value={form.getFieldValue('lockTypeImg')}
+            onChange={(value) => form.setFieldsValue({ lockTypeImg: value })}
+            limit={1}
+            height="100px"
+            width="100px"
           />
-        </div>
-        <div>
-          <label className="block text-sm text-gray-700 mb-2">状态</label>
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+}
+
+// 挂锁表单弹窗组件
+interface PadLockFormModalProps {
+  visible: boolean;
+  editingPadLock: PadLockVO | null;
+  onCancel: () => void;
+  onSave: (data: PadLockVO) => void;
+}
+
+function PadLockFormModal({ visible, editingPadLock, onCancel, onSave }: PadLockFormModalProps) {
+  const [form] = Form.useForm();
+  const [formLoading, setFormLoading] = useState(false);
+  const [hardwareOptions, setHardwareOptions] = useState<{ label: string; value: number }[]>([]);
+  const [lockTypeTreeOptions, setLockTypeTreeOptions] = useState<any[]>([]);
+  const [statusOptions] = useState(() => {
+    // 使用默认状态选项
+    return [
+      { dictType: 'common_status', label: '启用', value: '1' },
+      { dictType: 'common_status', label: '禁用', value: '0' },
+    ];
+  });
+
+  // 获取硬件列表
+  const getHardwareList = async () => {
+    try {
+      const response = await hardwareApi.listHardware({ pageNo: 1, pageSize: -1 });
+      const options = response.list.map(item => ({
+        label: item.hardwareName,
+        value: item.id!,
+      }));
+      setHardwareOptions(options);
+    } catch (error) {
+      console.error('获取硬件列表失败:', error);
+    }
+  };
+
+  // 获取挂锁类型列表
+  const getLockTypeList = async () => {
+    try {
+      const response = await padLockTypeApi.listPadLockType({ pageNo: 1, pageSize: -1 });
+      const flatList = response.list || [];
+      
+      // 转换为树形结构
+      const treeData = handleTree<PadLockTypeVO>(
+        flatList.map(item => ({ ...item, id: item.lockTypeId || item.id })),
+        'id',
+        'parentTypeId',
+        'children'
+      );
+      
+      const buildTreeSelectData = (nodes: PadLockTypeVO[]): any[] => {
+        return nodes.map(node => ({
+          title: node.lockTypeName,
+          value: node.lockTypeId || node.id,
+          children: node.children ? buildTreeSelectData(node.children) : undefined,
+        }));
+      };
+      
+      setLockTypeTreeOptions(buildTreeSelectData(treeData));
+    } catch (error) {
+      console.error('获取挂锁类型列表失败:', error);
+    }
+  };
+
+  useEffect(() => {
+    if (visible) {
+      getHardwareList();
+      getLockTypeList();
+      
+      if (editingPadLock) {
+        form.setFieldsValue({
+          hardwareId: editingPadLock.hardwareId,
+          lockTypeId: editingPadLock.lockTypeId,
+          lockName: editingPadLock.lockName || '',
+          lockNfc: editingPadLock.lockNfc || '',
+          lockSpec: editingPadLock.lockSpec || '',
+          exStatus: editingPadLock.exStatus || '1',
+          exRemark: editingPadLock.exRemark || '',
+        });
+      } else {
+        form.setFieldsValue({
+          hardwareId: undefined,
+          lockTypeId: undefined,
+          lockName: '',
+          lockNfc: '',
+          lockSpec: '',
+          exStatus: '1',
+          exRemark: '',
+        });
+      }
+    }
+  }, [visible, editingPadLock, form]);
+
+  const handleSubmit = async () => {
+    try {
+      const values = await form.validateFields();
+      setFormLoading(true);
+      
+      const submitData: PadLockVO = {
+        ...values,
+        lockId: editingPadLock?.lockId || editingPadLock?.id,
+      };
+      
+      onSave(submitData);
+    } catch (error) {
+      // 表单验证失败
+    } finally {
+      setFormLoading(false);
+    }
+  };
+
+  return (
+    <Modal
+      title={editingPadLock ? '编辑挂锁' : '新增挂锁'}
+      open={visible}
+      onOk={handleSubmit}
+      onCancel={onCancel}
+      okText="确定"
+      cancelText="取消"
+      confirmLoading={formLoading}
+      width={600}
+    >
+      <Form
+        form={form}
+        layout="vertical"
+        className="mt-4"
+      >
+        <Form.Item
+          label="所属硬件"
+          name="hardwareId"
+        >
           <Select
-            value={formData.enableFlag}
-            onChange={(value) => setFormData({ ...formData, enableFlag: value })}
-            style={{ width: '100%' }}
-          >
-            <Select.Option value="1">启用</Select.Option>
-            <Select.Option value="0">禁用</Select.Option>
-          </Select>
-        </div>
-        <div>
-          <label className="block text-sm text-gray-700 mb-2">备注</label>
-          <Input.TextArea
-            value={formData.remark}
-            onChange={(e) => setFormData({ ...formData, remark: e.target.value })}
-            placeholder="请输入备注"
-            rows={3}
+            placeholder="请选择所属硬件"
+            allowClear
+            options={hardwareOptions}
           />
-        </div>
-      </div>
+        </Form.Item>
+        
+        <Form.Item
+          label="挂锁类型"
+          name="lockTypeId"
+        >
+          <TreeSelect
+            treeData={lockTypeTreeOptions}
+            placeholder="请选择挂锁类型"
+            allowClear
+            treeDefaultExpandAll
+          />
+        </Form.Item>
+        
+        <Form.Item
+          label="挂锁名称"
+          name="lockName"
+          rules={[{ required: true, message: '请输入挂锁名称' }]}
+        >
+          <Input placeholder="请输入挂锁名称" />
+        </Form.Item>
+        
+        <Form.Item
+          label="挂锁NFC"
+          name="lockNfc"
+          rules={[{ required: true, message: '请输入挂锁NFC' }]}
+        >
+          <Input placeholder="请输入挂锁NFC" />
+        </Form.Item>
+        
+        <Form.Item
+          label="挂锁型号"
+          name="lockSpec"
+        >
+          <Input placeholder="请输入挂锁型号" />
+        </Form.Item>
+        
+        <Form.Item
+          label="状态"
+          name="exStatus"
+        >
+          <Radio.Group>
+            {statusOptions.map(option => (
+              <Radio key={option.value} value={option.value}>
+                {option.label}
+              </Radio>
+            ))}
+          </Radio.Group>
+        </Form.Item>
+        
+        <Form.Item
+          label="备注"
+          name="exRemark"
+        >
+          <Input.TextArea placeholder="请输入备注" rows={3} />
+        </Form.Item>
+      </Form>
     </Modal>
   );
 }

+ 23 - 17
src/components/SegregationPointManagement.tsx

@@ -1,16 +1,17 @@
 import React, { useState, useEffect, useRef } from 'react';
-import { Plus, Search, RefreshCw } from 'lucide-react';
+import { Plus, Search, RefreshCw, Edit2, Trash2 } 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 { ExclamationCircleOutlined } 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';
+import { Button as UIButton } from './ui/button';
 
 interface SegregationPointManagementProps {
   subMenu?: string;
@@ -266,25 +267,30 @@ export default function SegregationPointManagement({ subMenu }: SegregationPoint
     },
     {
       title: '操作',
-      width: 120,
+      width: 150,
       align: 'center',
       fixed: 'right',
       render: (_: any, record: SegregationPointVO) => (
-        <Space>
-          <Button
-            type="link"
-            icon={<EditOutlined />}
+        <div className="flex items-center gap-2 justify-center">
+          <UIButton
+            variant="ghost"
+            size="sm"
             onClick={() => openForm('update', record.pointId)}
-            title="编辑"
-          />
-          <Button
-            type="link"
-            danger
-            icon={<DeleteOutlined />}
+            className="h-8 px-2"
+          >
+            <Edit2 className="w-4 h-4" />
+            <span className="ml-1">编辑</span>
+          </UIButton>
+          <UIButton
+            variant="ghost"
+            size="sm"
             onClick={() => handleDelete(record.pointId)}
-            title="删除"
-          />
-        </Space>
+            className="h-8 px-2 text-red-600 hover:text-red-700"
+          >
+            <Trash2 className="w-4 h-4" />
+            <span className="ml-1">删除</span>
+          </UIButton>
+        </div>
       ),
     },
   ];
@@ -377,7 +383,7 @@ export default function SegregationPointManagement({ subMenu }: SegregationPoint
             </Button>
             <Button
               danger
-              icon={<DeleteOutlined />}
+              icon={<Trash2 className="w-4 h-4" />}
               disabled={selectedRowKeys.length === 0}
               onClick={() => handleDelete()}
             >

+ 124 - 75
src/components/lockCabinet/HardwareLockCabinetManagement.tsx

@@ -1,18 +1,16 @@
 import React, { useState, useEffect } from 'react';
-import { Search, RefreshCw, Eye } from 'lucide-react';
+import { Eye } from 'lucide-react';
 import { lockCabinetApi, LockCabinetVO, PageParam } from '../../api/lockCabinet';
 import { toast } from 'sonner';
 import { dateFormatter } from '../../utils/formatTime';
-import { DICT_TYPE, getDictLabel } from '../../utils/dict';
-import { Table, Image } from 'antd';
-import type { ColumnsType } from 'antd/es/table';
+import { DICT_TYPE, getDictLabel, getStrDictOptions } from '../../utils/dict';
+import { Table, Image, Input, Select, Space, Button as AntButton } from 'antd';
+import { SearchOutlined, ReloadOutlined } from '@ant-design/icons';
 import { Button } from '../ui/button';
-import { Input } from '../ui/input';
-import { Label } from '../ui/label';
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select';
 import { useNavigate } from 'react-router-dom';
 
 export default function HardwareLockCabinetManagement() {
+  console.log('HardwareLockCabinetManagement: 组件开始渲染');
   const navigate = useNavigate();
   const [loading, setLoading] = useState(true);
   const [list, setList] = useState<LockCabinetVO[]>([]);
@@ -25,8 +23,11 @@ export default function HardwareLockCabinetManagement() {
   });
   const [isOnlineOptions] = useState(() => {
     try {
-      return require('../../utils/dict').getStrDictOptions(DICT_TYPE.ISONLINE_STATUS);
-    } catch {
+      const options = getStrDictOptions(DICT_TYPE.ISONLINE_STATUS);
+      console.log('是否在线选项:', options);
+      return options || [];
+    } catch (error) {
+      console.error('获取是否在线选项失败:', error);
       return [];
     }
   });
@@ -128,78 +129,98 @@ export default function HardwareLockCabinetManagement() {
 
   // 查看详情
   const lookDetail = (row: LockCabinetVO) => {
-    navigate(`/hw/lockCabinet/lookDetail?id=${row.cabinetId}`);
+    // 使用 id 或 cabinetId,优先使用 id
+    const cabinetId = (row as any).id || row.cabinetId;
+    if (cabinetId) {
+      // 直接使用 navigate 跳转,Dashboard 会检测路径变化
+      navigate(`/lock-cabinet/detail?cabinetId=${cabinetId}`);
+    }
   };
 
+  // 调试:打印组件状态
+  useEffect(() => {
+    console.log('HardwareLockCabinetManagement 组件已渲染', {
+      loading,
+      listLength: list.length,
+      total,
+      queryParams,
+    });
+  }, [loading, list.length, total, queryParams]);
+
   return (
     <div className="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-1 min-w-[200px]">
-            <Label htmlFor="cabinet-name-search" className="text-sm font-medium text-gray-700 whitespace-nowrap">
-              锁柜名称
-            </Label>
-            <Input
-              id="cabinet-name-search"
-              placeholder="请输入锁柜名称"
-              value={queryParams.cabinetName || ''}
-              onChange={(e) => setQueryParams(prev => ({ ...prev, cabinetName: e.target.value }))}
-              onKeyDown={(e) => e.key === 'Enter' && handleQuery()}
-              className="h-10 bg-gray-50 border-gray-200 focus:bg-white focus:border-blue-400 focus:ring-2 focus:ring-blue-100 transition-all"
-            />
-          </div>
+      {/* 搜索栏和表格放在一起 */}
+      <div className="bg-white rounded-xl border border-gray-200/50 shadow-sm overflow-hidden">
+        {/* 搜索栏 */}
+        <div className="p-5 border-b border-gray-200">
+          <Space size="middle" wrap>
+            <div className="flex items-center gap-2">
+              <span className="text-sm font-medium text-gray-700 whitespace-nowrap">锁柜名称:</span>
+              <Input
+                placeholder="请输入锁柜名称"
+                value={queryParams.cabinetName || ''}
+                onChange={(e) => setQueryParams(prev => ({ ...prev, cabinetName: e.target.value }))}
+                onPressEnter={handleQuery}
+                style={{ width: 200 }}
+                allowClear
+              />
+            </div>
 
-          <div className="flex items-center gap-3 min-w-[200px]">
-            <Label htmlFor="is-online-search" className="text-sm font-medium text-gray-700 whitespace-nowrap">
-              是否在线
-            </Label>
-            <Select
-              value={queryParams.isOnline || 'all'}
-              onValueChange={(value) => setQueryParams(prev => ({ ...prev, isOnline: value === 'all' ? undefined : value }))}
-            >
-              <SelectTrigger className="h-10 bg-gray-50 border-gray-200 focus:bg-white focus:border-blue-400">
-                <SelectValue placeholder="请选择是否在线" />
-              </SelectTrigger>
-              <SelectContent>
-                <SelectItem value="all">全部</SelectItem>
-                {isOnlineOptions.map(option => (
-                  <SelectItem key={option.value} value={String(option.value)}>
-                    {option.label}
-                  </SelectItem>
-                ))}
-              </SelectContent>
-            </Select>
-          </div>
+            <div className="flex items-center gap-2">
+              <span className="text-sm font-medium text-gray-700 whitespace-nowrap">是否在线:</span>
+              <Select
+                placeholder="请选择是否在线"
+                value={queryParams.isOnline || undefined}
+                onChange={(value) => setQueryParams(prev => ({ ...prev, isOnline: value }))}
+                style={{ width: 200 }}
+                allowClear
+              >
+                {isOnlineOptions && isOnlineOptions.length > 0 ? (
+                  isOnlineOptions.map(option => (
+                    <Select.Option key={option.value} value={String(option.value)}>
+                      {option.label}
+                    </Select.Option>
+                  ))
+                ) : (
+                  <>
+                    <Select.Option value="0">离线</Select.Option>
+                    <Select.Option value="1">在线</Select.Option>
+                  </>
+                )}
+              </Select>
+            </div>
 
-          {/* 操作按钮组 */}
-          <div className="flex items-center gap-3">
-            <Button
+            {/* 操作按钮组 */}
+            <AntButton
+              type="primary"
+              icon={<SearchOutlined />}
               onClick={handleQuery}
-              className="flex items-center gap-2"
             >
-              <Search className="w-4 h-4" />
               搜索
-            </Button>
+            </AntButton>
             
-            <Button
-              variant="outline"
+            <AntButton
+              icon={<ReloadOutlined />}
               onClick={resetQuery}
-              className="flex items-center gap-2"
             >
-              <RefreshCw className="w-4 h-4" />
               重置
-            </Button>
-          </div>
+            </AntButton>
+          </Space>
         </div>
-      </div>
 
-      {/* 表格 */}
-      <div className="bg-white rounded-lg border border-gray-200 overflow-hidden">
-        <Table
-          rowKey="cabinetId"
+        {/* 表格 */}
+        <div>
+          <Table
+          rowKey={(record) => {
+            // 兼容不同的 ID 字段名
+            const id = record.cabinetId || (record as any).id || (record as any).cabinetId;
+            return id || `row-${Math.random()}`;
+          }}
           loading={loading}
           dataSource={list}
+          locale={{
+            emptyText: loading ? '加载中...' : '暂无数据',
+          }}
           columns={[
             {
               title: '锁柜名称',
@@ -319,19 +340,47 @@ export default function HardwareLockCabinetManagement() {
               ),
             },
           ]}
-          pagination={{
-            current: queryParams.pageNo,
-            pageSize: queryParams.pageSize,
-            total: total,
-            showTotal: (total) => `共 ${total} 条记录`,
-            showSizeChanger: true,
-            showQuickJumper: true,
-            onChange: (page, pageSize) => {
-              setQueryParams(prev => ({ ...prev, pageNo: page, pageSize: pageSize || 10 }));
-            },
-          }}
+          pagination={false}
+          scroll={{ x: 'max-content' }}
         />
+        </div>
       </div>
+
+      {/* 分页 */}
+      {!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={() => {
+                  const newParams = { ...queryParams, pageNo: queryParams.pageNo - 1 };
+                  setQueryParams(newParams);
+                  getList(newParams);
+                }}
+                disabled={queryParams.pageNo <= 1}
+              >
+                上一页
+              </Button>
+              <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
+                {queryParams.pageNo} / {Math.ceil(total / queryParams.pageSize) || 1}
+              </span>
+              <Button
+                onClick={() => {
+                  const newParams = { ...queryParams, pageNo: queryParams.pageNo + 1 };
+                  setQueryParams(newParams);
+                  getList(newParams);
+                }}
+                disabled={queryParams.pageNo >= Math.ceil(total / queryParams.pageSize)}
+              >
+                下一页
+              </Button>
+            </div>
+          </div>
+        </div>
+      )}
     </div>
   );
 }

+ 2 - 93
src/components/lockCabinet/SlotsList.tsx

@@ -1,13 +1,10 @@
-import React, { useState, useEffect, useRef } from 'react';
-import { Table, Modal, Select, Space, message, Button as AntButton } from 'antd';
-import { ExclamationCircleOutlined } from '@ant-design/icons';
-import { Edit2, Trash2 } from 'lucide-react';
+import React, { useState, useEffect } from 'react';
+import { Table, Select, Space, Button as AntButton } from 'antd';
 import { Button } from '../ui/button';
 import { slotApi, LockCabinetSlotVO, SlotPageParam } from '../../api/lockCabinet/slots';
 import { DICT_TYPE, getIntDictOptions, getDictLabel } from '../../utils/dict';
 import { dateFormatter } from '../../utils/formatTime';
 import type { ColumnsType } from 'antd/es/table';
-import SlotForm, { SlotFormRef } from './SlotForm';
 
 interface SlotsListProps {
   cabinetId: string;
@@ -17,7 +14,6 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
   const [loading, setLoading] = useState(true);
   const [list, setList] = useState<LockCabinetSlotVO[]>([]);
   const [total, setTotal] = useState(0);
-  const [selectedIds, setSelectedIds] = useState<number[]>([]);
   const [queryParams, setQueryParams] = useState<SlotPageParam>({
     pageNo: 1,
     pageSize: 10,
@@ -25,7 +21,6 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
     slotType: undefined,
     status: undefined,
   });
-  const formRef = useRef<SlotFormRef>(null);
 
   // 获取仓位列表
   const getList = async (params?: SlotPageParam) => {
@@ -47,7 +42,6 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
       setTotal(response.total || 0);
     } catch (error: any) {
       console.error('获取仓位列表失败:', error);
-      message.error(error.message || '获取仓位列表失败');
     } finally {
       setLoading(false);
     }
@@ -79,39 +73,6 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
     getList(resetParams);
   };
 
-  // 打开表单
-  const openForm = (type: string, id?: number) => {
-    if (formRef.current) {
-      formRef.current.open(type, id);
-    } else {
-      message.error('表单组件未初始化,请刷新页面重试');
-    }
-  };
-
-  // 删除
-  const handleDelete = async (id?: number) => {
-    const ids = id ? [id] : selectedIds;
-    if (ids.length === 0) {
-      message.warning('请选择要删除的数据');
-      return;
-    }
-
-    Modal.confirm({
-      title: '确认删除',
-      icon: <ExclamationCircleOutlined />,
-      content: `确定要删除选中的 ${ids.length} 条数据吗?`,
-      onOk: async () => {
-        try {
-          await slotApi.deleteIsLockCabinetSlotsBySlotIds(ids.join(','));
-          message.success('删除成功');
-          getList();
-          setSelectedIds([]);
-        } catch (error: any) {
-          message.error(error.message || '删除失败');
-        }
-      },
-    });
-  };
 
   // 表格列定义
   const columns: ColumnsType<LockCabinetSlotVO> = [
@@ -164,43 +125,8 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
       key: 'createTime',
       render: (text) => text ? dateFormatter(text) : '-',
     },
-    {
-      title: '操作',
-      key: 'action',
-      width: 150,
-      align: 'center',
-      render: (_: any, record: LockCabinetSlotVO) => (
-        <div className="flex items-center gap-2 justify-center">
-          <Button
-            variant="ghost"
-            size="sm"
-            onClick={() => openForm('update', (record as any).slotId || (record as any).id)}
-            className="h-8 px-2"
-          >
-            <Edit2 className="w-4 h-4" />
-            <span className="ml-1">编辑</span>
-          </Button>
-          <Button
-            variant="ghost"
-            size="sm"
-            onClick={() => handleDelete((record as any).slotId || (record as any).id)}
-            className="h-8 px-2 text-red-600 hover:text-red-700"
-          >
-            <Trash2 className="w-4 h-4" />
-            <span className="ml-1">删除</span>
-          </Button>
-        </div>
-      ),
-    },
   ];
 
-  // 行选择配置
-  const rowSelection = {
-    selectedRowKeys: selectedIds,
-    onChange: (selectedRowKeys: React.Key[]) => {
-      setSelectedIds(selectedRowKeys as number[]);
-    },
-  };
 
   const slotTypeOptions = getIntDictOptions(DICT_TYPE.SLOT_TYPE);
   const statusOptions = getIntDictOptions(DICT_TYPE.SLOT_STATUS);
@@ -244,19 +170,6 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
           </div> */}
           <AntButton type="primary" onClick={handleQuery}>搜索</AntButton>
           <AntButton onClick={resetQuery}>重置</AntButton>
-          <AntButton
-            type="primary"
-            onClick={() => openForm('create')}
-          >
-            新增
-          </AntButton>
-          <AntButton
-            danger
-            disabled={selectedIds.length === 0}
-            onClick={() => handleDelete()}
-          >
-            批量删除
-          </AntButton>
         </Space>
       </div>
 
@@ -266,7 +179,6 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
           rowKey={(record) => (record as any).slotId || (record as any).id || `row-${Math.random()}`}
           loading={loading}
           dataSource={list}
-          rowSelection={rowSelection}
           columns={columns}
           pagination={false}
           scroll={{ x: 'max-content' }}
@@ -308,9 +220,6 @@ export default function SlotsList({ cabinetId }: SlotsListProps) {
           </div>
         </div>
       )}
-
-      {/* 表单弹窗 */}
-      <SlotForm ref={formRef} onSuccess={getList} cabinetId={cabinetId} />
     </div>
   );
 }