Bladeren bron

挂锁管理提交

pm 5 maanden geleden
bovenliggende
commit
58ae5c2343

+ 22 - 2
src/Dashboard.tsx

@@ -5,7 +5,7 @@ import { Shield, Settings, Users, Cpu, MapPin, Layers, Bell, User, LogOut, Chevr
 import SystemConfig from './components/SystemConfig';
 import UserManagement from './components/UserManagement';
 import HardwareManagement from './components/HardwareManagement';
-import LocationManagement from './components/LocationManagement';
+import SegregationPointManagement from './components/SegregationPointManagement';
 import IsolationWork from './components/IsolationWork';
 import ProfileSettings from './components/ProfileSettings';
 import CockpitDashboard from './components/CockpitDashboard';
@@ -558,6 +558,26 @@ export default function Dashboard() {
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, []); // 只在组件挂载时执行一次
 
+  // 监听从用户管理跳转到岗位管理的事件
+  useEffect(() => {
+    const handleSwitchToPostManagement = (event: CustomEvent) => {
+      const userId = event.detail?.userId;
+      if (userId) {
+        // 将用户ID存储到 sessionStorage
+        sessionStorage.setItem('selectedUserId', String(userId));
+      }
+      // 切换到岗位管理页面
+      setActiveMenu('systemConfig');
+      setActiveSubMenu('positionManagement');
+    };
+
+    window.addEventListener('switchToPostManagement', handleSwitchToPostManagement as EventListener);
+
+    return () => {
+      window.removeEventListener('switchToPostManagement', handleSwitchToPostManagement as EventListener);
+    };
+  }, []);
+
   return (
     <div className="min-h-screen bg-gradient-to-br from-gray-50 via-blue-50/30 to-gray-50">
       {/* 顶部导航栏 */}
@@ -816,7 +836,7 @@ export default function Dashboard() {
         ) : activeMenu === 'hardwareManagement' ? (
           <HardwareManagement subMenu={activeSubMenu} />
         ) : activeMenu === 'locationManagement' ? (
-          <LocationManagement />
+          <SegregationPointManagement />
         ) : activeMenu === 'isolationWork' ? (
           <IsolationWork subMenu={activeSubMenu} />
         ) : activeMenu === 'notificationManagement' ? (

+ 61 - 0
src/api/DictData.ts

@@ -0,0 +1,61 @@
+import request from '@/config/axios';
+
+// 分页参数类型
+export interface PageParam {
+  pageNo?: number;
+  pageSize?: number;
+  [key: string]: any;
+}
+
+// 字典数据 VO
+export type DictDataVO = {
+  id: number | undefined;
+  sort: number | undefined;
+  label: string;
+  value: string;
+  dictType: string;
+  status: number;
+  colorType: string;
+  cssClass: string;
+  remark: string;
+  createTime: Date;
+};
+
+// 字典数据管理 API
+export const dictDataApi = {
+  // 查询字典数据(精简)列表
+  getSimpleDictDataList: async () => {
+    return await request.get({ url: '/system/dict-data/simple-list' });
+  },
+
+  // 查询字典数据列表
+  getDictDataPage: async (params?: PageParam) => {
+    return await request.get({ url: '/system/dict-data/page', params });
+  },
+
+  // 查询字典数据详情
+  getDictData: async (id: number) => {
+    return await request.get({ url: `/system/dict-data/get?id=${id}` });
+  },
+
+  // 新增字典数据
+  createDictData: async (data: DictDataVO) => {
+    return await request.post({ url: '/system/dict-data/create', data });
+  },
+
+  // 修改字典数据
+  updateDictData: async (data: DictDataVO) => {
+    return await request.put({ url: '/system/dict-data/update', data });
+  },
+
+  // 删除字典数据
+  deleteDictData: async (id: number) => {
+    return await request.delete({ url: `/system/dict-data/delete?id=${id}` });
+  },
+
+  // 导出字典类型数据
+  exportDictData: async (params?: PageParam) => {
+    return await request.download({ url: '/system/dict-data/export', params });
+  },
+};
+

+ 57 - 0
src/api/DictType.ts

@@ -0,0 +1,57 @@
+import request from '@/config/axios';
+
+// 分页参数类型
+export interface PageParam {
+  pageNo?: number;
+  pageSize?: number;
+  [key: string]: any;
+}
+
+// 字典类型 VO
+export type DictTypeVO = {
+  id: number | undefined;
+  name: string;
+  type: string;
+  status: number;
+  remark: string;
+  createTime: Date;
+};
+
+// 字典类型管理 API
+export const dictTypeApi = {
+  // 查询字典(精简)列表
+  getSimpleDictTypeList: async () => {
+    return await request.get({ url: '/system/dict-type/list-all-simple' });
+  },
+
+  // 查询字典列表
+  getDictTypePage: async (params?: PageParam) => {
+    return await request.get({ url: '/system/dict-type/page', params });
+  },
+
+  // 查询字典详情
+  getDictType: async (id: number) => {
+    return await request.get({ url: `/system/dict-type/get?id=${id}` });
+  },
+
+  // 新增字典
+  createDictType: async (data: DictTypeVO) => {
+    return await request.post({ url: '/system/dict-type/create', data });
+  },
+
+  // 修改字典
+  updateDictType: async (data: DictTypeVO) => {
+    return await request.put({ url: '/system/dict-type/update', data });
+  },
+
+  // 删除字典
+  deleteDictType: async (id: number) => {
+    return await request.delete({ url: `/system/dict-type/delete?id=${id}` });
+  },
+
+  // 导出字典类型
+  exportDictType: async (params?: PageParam) => {
+    return await request.download({ url: '/system/dict-type/export', params });
+  },
+};
+

+ 85 - 0
src/api/PadLock.ts

@@ -0,0 +1,85 @@
+import request from '../config/axios';
+
+export interface PadLockVO {
+  lockId?: number;
+  lockCode: string;
+  lockName: string;
+  enableFlag?: string;
+  remark?: string;
+  createBy?: string;
+  createTime?: Date;
+  updateBy?: string;
+  updateTime?: Date;
+}
+
+export interface PageParam {
+  pageNo: number;
+  pageSize: number;
+  lockCode?: string;
+  lockName?: string;
+  enableFlag?: string;
+}
+
+// 分页响应类型
+export interface PageResponse<T> {
+  list: T[];
+  total: number;
+}
+
+// 挂锁管理 API
+export const padLockApi = {
+  // 查询挂锁-列表
+  listPadLock: async (params: PageParam): Promise<PageResponse<PadLockVO>> => {
+    const response = await request.get({ 
+      url: '/iscs/lock/getLockPage', 
+      params 
+    });
+    
+    // 处理响应数据格式
+    if (response && typeof response === 'object') {
+      if ('data' in response && response.data) {
+        return response.data;
+      } else if ('list' in response || 'records' in response) {
+        return {
+          list: response.list || response.records || [],
+          total: response.total || 0,
+        };
+      }
+    }
+    return response;
+  },
+
+  // 获取挂锁详细信息
+  getPadLockInfo: async (id: number): Promise<PadLockVO> => {
+    const response = await request.get({
+      url: '/iscs/lock/selectLockById',
+      params: { id }
+    });
+    return response?.data || response;
+  },
+
+  // 新增挂锁
+  addPadLock: async (data: PadLockVO): Promise<void> => {
+    return await request.post({ 
+      url: '/iscs/lock/insertLock', 
+      data 
+    });
+  },
+
+  // 修改挂锁信息
+  updatePadLock: async (data: PadLockVO): Promise<void> => {
+    return 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({
+      url: `/iscs/lock/deleteLockList?ids=${idsParam}`,
+    });
+  },
+};
+

+ 86 - 0
src/api/PadLockType.ts

@@ -0,0 +1,86 @@
+import request from '../config/axios';
+
+export interface PadLockTypeVO {
+  lockTypeId?: number;
+  parentTypeId?: number;
+  lockTypeCode: string;
+  lockTypeName: string;
+  enableFlag?: string;
+  remark?: string;
+  createBy?: string;
+  createTime?: Date;
+  updateBy?: string;
+  updateTime?: Date;
+}
+
+export interface PageParam {
+  pageNo: number;
+  pageSize: number;
+  lockTypeCode?: string;
+  lockTypeName?: string;
+  enableFlag?: string;
+}
+
+// 分页响应类型
+export interface PageResponse<T> {
+  list: T[];
+  total: number;
+}
+
+// 挂锁类型管理 API
+export const padLockTypeApi = {
+  // 查询挂锁类型-列表
+  listPadLockType: async (params: PageParam): Promise<PageResponse<PadLockTypeVO>> => {
+    const response = await request.get({ 
+      url: '/iscs/lock-type/getLockTypePage', 
+      params 
+    });
+    
+    // 处理响应数据格式
+    if (response && typeof response === 'object') {
+      if ('data' in response && response.data) {
+        return response.data;
+      } else if ('list' in response || 'records' in response) {
+        return {
+          list: response.list || response.records || [],
+          total: response.total || 0,
+        };
+      }
+    }
+    return response;
+  },
+
+  // 获取挂锁类型详细信息
+  getPadLockTypeInfo: async (id: number): Promise<PadLockTypeVO> => {
+    const response = await request.get({
+      url: '/iscs/lock-type/selectLockTypeById',
+      params: { id }
+    });
+    return response?.data || response;
+  },
+
+  // 新增挂锁类型
+  addPadLockType: async (data: PadLockTypeVO): Promise<void> => {
+    return await request.post({ 
+      url: '/iscs/lock-type/insertLockType', 
+      data 
+    });
+  },
+
+  // 修改挂锁类型信息
+  updatePadLockType: async (data: PadLockTypeVO): Promise<void> => {
+    return await request.put({ 
+      url: '/iscs/lock-type/updateLockType', 
+      data 
+    });
+  },
+
+  // 删除挂锁类型信息
+  delPadLockType: async (ids: number | number[]): Promise<void> => {
+    const idsParam = Array.isArray(ids) ? ids.join(',') : ids;
+    return await request.delete({
+      url: `/iscs/lock-type/deleteLockTypeList?ids=${idsParam}`,
+    });
+  },
+};
+

+ 12 - 0
src/api/index.ts

@@ -3,6 +3,8 @@ import { menuApi } from './Menu';
 import { postApi } from './Post';
 import { roleApi } from './Role';
 import { rolePageApi, pageUiComponentApi } from './RolePage';
+import { dictTypeApi } from './DictType';
+import { dictDataApi } from './DictData';
 import { userApi } from './user';
 import { userPermissionApi } from './user/permission';
 import { userImportApi } from './user/import';
@@ -10,6 +12,8 @@ import { userCharacteristicApi } from './user/characteristic';
 import { systemApi } from './System';
 import { hardwareApi } from './Hardware';
 import { deptApi } from './dept';
+import { padLockApi } from './PadLock';
+import { padLockTypeApi } from './PadLockType';
 
 // API 响应类型
 export interface ApiResponse<T = any> {
@@ -24,6 +28,8 @@ export { menuApi } from './Menu';
 export { postApi } from './Post';
 export { roleApi } from './Role';
 export { rolePageApi, pageUiComponentApi } from './RolePage';
+export { dictTypeApi } from './DictType';
+export { dictDataApi } from './DictData';
 export { userApi } from './user';
 export { userPermissionApi } from './user/permission';
 export { userImportApi } from './user/import';
@@ -31,6 +37,8 @@ export { userCharacteristicApi } from './user/characteristic';
 export { systemApi } from './System';
 export { hardwareApi } from './Hardware';
 export { deptApi } from './dept';
+export { padLockApi } from './PadLock';
+export { padLockTypeApi } from './PadLockType';
 
 // 为了兼容旧代码,导出 authApi 作为 loginApi 的别名
 export { loginApi as authApi } from './Login';
@@ -44,6 +52,8 @@ export default {
   role: roleApi,
   rolePage: rolePageApi,
   pageUiComponent: pageUiComponentApi,
+  dictType: dictTypeApi,
+  dictData: dictDataApi,
   user: userApi,
   userPermission: userPermissionApi,
   userImport: userImportApi,
@@ -51,5 +61,7 @@ export default {
   system: systemApi,
   hardware: hardwareApi,
   dept: deptApi,
+  padLock: padLockApi,
+  padLockType: padLockTypeApi,
 };
 

+ 30 - 0
src/api/lotoStation/index.ts

@@ -0,0 +1,30 @@
+import axiosInstance from '../../utils/axios';
+
+// 锁定站 VO 类型
+export interface LotoStationVO {
+  id?: number;
+  lotoName: string;
+  [key: string]: any;
+}
+
+// 分页参数类型
+export interface PageParam {
+  pageNo?: number;
+  pageSize?: number;
+  [key: string]: any;
+}
+
+// 分页响应类型
+export interface PageResponse<T> {
+  list: T[];
+  total: number;
+}
+
+// 锁定站管理 API
+export const lotoStationApi = {
+  // 查询锁定站列表
+  listLoto: (params?: PageParam): Promise<PageResponse<LotoStationVO>> => {
+    return axiosInstance.get('/iscs/loto-station/list', { params });
+  },
+};
+

+ 32 - 0
src/api/marsdept/index.ts

@@ -0,0 +1,32 @@
+import axiosInstance from '../../utils/axios';
+
+// 岗位(MarsDept)VO 类型
+export interface MarsDeptVO {
+  id?: number;
+  workstationName: string;
+  parentId?: number;
+  children?: MarsDeptVO[];
+  [key: string]: any;
+}
+
+// 分页参数类型
+export interface PageParam {
+  pageNo?: number;
+  pageSize?: number;
+  [key: string]: any;
+}
+
+// 分页响应类型
+export interface PageResponse<T> {
+  list: T[];
+  total: number;
+}
+
+// 岗位(MarsDept)管理 API
+export const marsDeptApi = {
+  // 查询岗位列表
+  listMarsDept: (params?: PageParam): Promise<PageResponse<MarsDeptVO>> => {
+    return axiosInstance.get('/system/marsdept/list', { params });
+  },
+};
+

+ 30 - 0
src/api/rfid/index.ts

@@ -0,0 +1,30 @@
+import axiosInstance from '../../utils/axios';
+
+// RFID Token VO 类型
+export interface RfidTokenVO {
+  id?: number;
+  rfid: string;
+  [key: string]: any;
+}
+
+// 分页参数类型
+export interface PageParam {
+  pageNo?: number;
+  pageSize?: number;
+  [key: string]: any;
+}
+
+// 分页响应类型
+export interface PageResponse<T> {
+  list: T[];
+  total: number;
+}
+
+// RFID Token 管理 API
+export const rfidApi = {
+  // 查询 RFID Token 列表(分页)
+  getIsRfidTokenPage: (params?: PageParam): Promise<PageResponse<RfidTokenVO>> => {
+    return axiosInstance.get('/iscs/rfid-token/page', { params });
+  },
+};
+

+ 132 - 0
src/api/spm/index.ts

@@ -0,0 +1,132 @@
+import request from '../../config/axios';
+
+// 隔离点 VO 类型
+export interface SegregationPointVO {
+  pointId?: number;
+  pointCode: string;
+  pointName: string;
+  pointIcon?: string;
+  pointPicture?: string;
+  pointNfc: string;
+  workstationId: number;
+  lotoId: number;
+  powerType?: string;
+  pointSerialNumber?: string;
+  remark?: string;
+  createTime?: Date;
+  // 扩展字段(用于显示)
+  workstationName?: string;
+  lotoName?: string;
+  machineryId?: number;
+  machineryName?: string;
+  switchStatus?: number | string;
+}
+
+// 分页参数类型
+export interface PageParam {
+  current?: number;
+  size?: number;
+  pageNo?: number; // 兼容旧参数
+  pageSize?: number; // 兼容旧参数
+  pointCode?: string;
+  pointName?: string;
+  workstationId?: number;
+  lotoId?: number;
+  machineryId?: number;
+  powerType?: string;
+  [key: string]: any;
+}
+
+// 分页响应类型
+export interface PageResponse<T> {
+  list: T[];
+  total: number;
+  records?: T[]; // 兼容其他响应格式
+  data?: {
+    list: T[];
+    total: number;
+  };
+}
+
+// 隔离点管理 API
+export const segregationPointApi = {
+  // 查询隔离点列表(分页)
+  getIsIsolationPointPage: async (params?: PageParam): Promise<PageResponse<SegregationPointVO>> => {
+    // 转换参数格式:pageNo -> current, pageSize -> size
+    const requestParams: any = { ...params };
+    if (requestParams.pageNo !== undefined) {
+      requestParams.current = requestParams.pageNo;
+      delete requestParams.pageNo;
+    }
+    if (requestParams.pageSize !== undefined) {
+      requestParams.size = requestParams.pageSize;
+      delete requestParams.pageSize;
+    }
+    
+    const response = await request.get({ 
+      url: '/iscs/isolation-point/getIsolationPointPage', 
+      params: requestParams 
+    });
+    
+    // 处理响应数据格式
+    if (response && typeof response === 'object') {
+      if ('data' in response && response.data) {
+        return response.data;
+      } else if ('list' in response || 'records' in response) {
+        return {
+          list: response.list || response.records || [],
+          total: response.total || 0,
+        };
+      }
+    }
+    return response;
+  },
+
+  // 查询隔离点详情
+  selectIsIsolationPointById: async (id: number): Promise<SegregationPointVO> => {
+    const response = await request.get({ 
+      url: '/iscs/isolation-point/selectIsolationPointById', 
+      params: { id } 
+    });
+    return response?.data || response;
+  },
+
+  // 新增隔离点
+  addinsertIsIsolationPoint: async (data: SegregationPointVO): Promise<void> => {
+    return await request.post({ 
+      url: '/iscs/isolation-point/insertIsolationPoint', 
+      data 
+    });
+  },
+
+  // 修改隔离点
+  updateIsIsolationPoint: async (data: SegregationPointVO): Promise<void> => {
+    return await request.put({ 
+      url: '/iscs/isolation-point/updateIsolationPoint', 
+      data 
+    });
+  },
+
+  // 删除隔离点
+  deleteIsIsolationPointByPointIds: async (pointIds: number | number[]): Promise<void> => {
+    const ids = Array.isArray(pointIds) ? pointIds.join(',') : pointIds;
+    return await request.delete({
+      url: '/iscs/isolation-point/deleteIsolationPointList',
+      params: { ids },
+    });
+  },
+
+  // 查询车间列表
+  getWorkshopList: async () => {
+    return await request.get({ url: '/mes/md/workshop/listAll' });
+  },
+
+  // 查询工作区域列表
+  getWorkareaList: async (workshopId: number) => {
+    return await request.get({
+      url: '/iscs/workarea/getIsWorkareaList',
+      params: { workshopId },
+    });
+  },
+};
+

+ 33 - 0
src/api/technology/index.ts

@@ -0,0 +1,33 @@
+import axiosInstance from '../../utils/axios';
+
+// 设备/工艺 VO 类型
+export interface TechnologyVO {
+  id?: number;
+  machineryName: string;
+  machineryType?: string;
+  parentId?: number;
+  children?: TechnologyVO[];
+  [key: string]: any;
+}
+
+// 分页参数类型
+export interface PageParam {
+  pageNo?: number;
+  pageSize?: number;
+  [key: string]: any;
+}
+
+// 分页响应类型
+export interface PageResponse<T> {
+  list: T[];
+  total: number;
+}
+
+// 设备/工艺管理 API
+export const technologyApi = {
+  // 查询设备/工艺列表
+  listTechnology: (params?: PageParam): Promise<PageResponse<TechnologyVO>> => {
+    return axiosInstance.get('/iscs/technology/list', { params });
+  },
+};
+

+ 1 - 1
src/api/user/permission.ts

@@ -4,7 +4,7 @@ import axiosInstance from '../../utils/axios';
 export const userPermissionApi = {
   // 获取用户角色列表
   getUserRoleList: (userId: number): Promise<number[]> => {
-    return axiosInstance.get(`/system/permission/get-user-role-list?userId=${userId}`);
+    return axiosInstance.get(`/system/permission/list-user-roles?userId=${userId}`);
   },
 
   // 分配用户角色

+ 7 - 0
src/components/HardwareManagement.tsx

@@ -1,5 +1,6 @@
 import React, { useState } from 'react';
 import { Plus, Search, Edit2, Trash2, MoreVertical, Package, Key, Lock, Briefcase, QrCode, MapPin } from 'lucide-react';
+import PadLockManagement from './PadLockManagement';
 
 interface TableRow {
   id: number;
@@ -11,6 +12,12 @@ interface HardwareManagementProps {
 }
 
 export default function HardwareManagement({ subMenu }: HardwareManagementProps) {
+  // 如果是挂锁,使用专门的挂锁管理组件
+  // subMenu 可能是 '挂锁'、'padlock' 或 'lock'
+  if (subMenu === '挂锁' || subMenu === 'padlock' || subMenu === 'lock') {
+    return <PadLockManagement subMenu={subMenu} />;
+  }
+
   const [searchTerm, setSearchTerm] = useState('');
   const [showAddModal, setShowAddModal] = useState(false);
   const [editingItem, setEditingItem] = useState<TableRow | null>(null);

+ 693 - 0
src/components/PadLockManagement.tsx

@@ -0,0 +1,693 @@
+import React, { useState, useEffect, useRef } from 'react';
+import { Search, Plus, RefreshCw, Edit2, Trash2, Settings } from 'lucide-react';
+import { padLockApi, PadLockVO, PageParam } from '../api/PadLock';
+import { padLockTypeApi, PadLockTypeVO, PageParam as PadLockTypePageParam } from '../api/PadLockType';
+import { toast } from 'sonner';
+import { Modal, Button, Input, Space, Table, Select } from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import type { ColumnsType } from 'antd/es/table';
+
+interface PadLockManagementProps {
+  subMenu?: string;
+}
+
+export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
+  const [viewMode, setViewMode] = useState<'list' | 'type'>('list'); // 'list' 挂锁列表, 'type' 挂锁类型
+  const [loading, setLoading] = useState(true);
+  const [list, setList] = useState<PadLockVO[]>([]);
+  const [total, setTotal] = useState(0);
+  const [queryParams, setQueryParams] = useState<PageParam>({
+    pageNo: 1,
+    pageSize: 10,
+    lockCode: undefined,
+    lockName: undefined,
+    enableFlag: undefined,
+  });
+
+  // 挂锁类型相关状态
+  const [typeLoading, setTypeLoading] = useState(true);
+  const [typeList, setTypeList] = useState<PadLockTypeVO[]>([]);
+  const [typeTotal, setTypeTotal] = useState(0);
+  const [typeQueryParams, setTypeQueryParams] = useState<PadLockTypePageParam>({
+    pageNo: 1,
+    pageSize: 10,
+    lockTypeCode: undefined,
+    lockTypeName: undefined,
+    enableFlag: undefined,
+  });
+  const [showTypeForm, setShowTypeForm] = useState(false);
+  const [editingType, setEditingType] = useState<PadLockTypeVO | null>(null);
+
+  // 获取挂锁列表
+  const getList = async (params?: PageParam) => {
+    const currentParams = params || queryParams;
+    setLoading(true);
+    try {
+      const response = await padLockApi.listPadLock(currentParams);
+      setList(response.list || []);
+      setTotal(response.total || 0);
+    } catch (error: any) {
+      toast.error(error.message || '获取挂锁列表失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  // 获取挂锁类型列表
+  const getTypeList = async (params?: PadLockTypePageParam) => {
+    const currentParams = params || typeQueryParams;
+    setTypeLoading(true);
+    try {
+      const response = await padLockTypeApi.listPadLockType(currentParams);
+      setTypeList(response.list || []);
+      setTypeTotal(response.total || 0);
+    } catch (error: any) {
+      toast.error(error.message || '获取挂锁类型列表失败');
+    } finally {
+      setTypeLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    if (viewMode === 'list') {
+      getList();
+    } else {
+      getTypeList();
+    }
+  }, [viewMode, queryParams.pageNo, queryParams.pageSize, typeQueryParams.pageNo, typeQueryParams.pageSize]);
+
+  // 搜索挂锁
+  const handleQuery = () => {
+    const newParams = { ...queryParams, pageNo: 1 };
+    setQueryParams(newParams);
+    getList(newParams);
+  };
+
+  // 重置挂锁搜索
+  const resetQuery = () => {
+    const resetParams: PageParam = {
+      pageNo: 1,
+      pageSize: 10,
+      lockCode: undefined,
+      lockName: undefined,
+      enableFlag: undefined,
+    };
+    setQueryParams(resetParams);
+    getList(resetParams);
+  };
+
+  // 搜索挂锁类型
+  const handleTypeQuery = () => {
+    const newParams = { ...typeQueryParams, pageNo: 1 };
+    setTypeQueryParams(newParams);
+    getTypeList(newParams);
+  };
+
+  // 重置挂锁类型搜索
+  const resetTypeQuery = () => {
+    const resetParams: PadLockTypePageParam = {
+      pageNo: 1,
+      pageSize: 10,
+      lockTypeCode: undefined,
+      lockTypeName: undefined,
+      enableFlag: undefined,
+    };
+    setTypeQueryParams(resetParams);
+    getTypeList(resetParams);
+  };
+
+  // 删除挂锁
+  const handleDelete = async (id: number, name?: string) => {
+    Modal.confirm({
+      title: '确认删除',
+      icon: <ExclamationCircleOutlined />,
+      content: (
+        <div>
+          <p>确定要删除挂锁 <strong>"{name || '该挂锁'}"</strong> 吗?</p>
+          <p style={{ color: '#ff4d4f', marginTop: '8px' }}>删除后无法恢复,请谨慎操作!</p>
+        </div>
+      ),
+      okText: '确定删除',
+      okType: 'danger',
+      cancelText: '取消',
+      onOk: async () => {
+        try {
+          await padLockApi.delPadLock(id);
+          toast.success('删除成功');
+          await getList();
+        } catch (error: any) {
+          toast.error(error.message || '删除失败');
+        }
+      },
+    });
+  };
+
+  // 删除挂锁类型
+  const handleDeleteType = async (id: number, name?: string) => {
+    Modal.confirm({
+      title: '确认删除',
+      icon: <ExclamationCircleOutlined />,
+      content: (
+        <div>
+          <p>确定要删除挂锁类型 <strong>"{name || '该类型'}"</strong> 吗?</p>
+          <p style={{ color: '#ff4d4f', marginTop: '8px' }}>删除后无法恢复,请谨慎操作!</p>
+        </div>
+      ),
+      okText: '确定删除',
+      okType: 'danger',
+      cancelText: '取消',
+      onOk: async () => {
+        try {
+          await padLockTypeApi.delPadLockType(id);
+          toast.success('删除成功');
+          await getTypeList();
+        } catch (error: any) {
+          toast.error(error.message || '删除失败');
+        }
+      },
+    });
+  };
+
+  // 打开挂锁类型表单
+  const openTypeForm = (type?: 'create' | 'update', id?: number) => {
+    if (type === 'create') {
+      setEditingType(null);
+      setShowTypeForm(true);
+    } else if (type === 'update' && id) {
+      padLockTypeApi.getPadLockTypeInfo(id).then((data) => {
+        setEditingType(data);
+        setShowTypeForm(true);
+      }).catch((error: any) => {
+        toast.error(error.message || '获取挂锁类型信息失败');
+      });
+    }
+  };
+
+  // 保存挂锁类型
+  const saveType = async (formData: PadLockTypeVO) => {
+    try {
+      if (editingType?.lockTypeId) {
+        await padLockTypeApi.updatePadLockType({
+          ...formData,
+          lockTypeId: editingType.lockTypeId,
+        });
+        toast.success('修改成功');
+      } else {
+        await padLockTypeApi.addPadLockType(formData);
+        toast.success('新增成功');
+      }
+      setShowTypeForm(false);
+      setEditingType(null);
+      await getTypeList();
+    } catch (error: any) {
+      toast.error(error.message || '保存失败');
+    }
+  };
+
+  // 挂锁列表表格列
+  const padLockColumns: ColumnsType<PadLockVO> = [
+    {
+      title: '序号',
+      width: 80,
+      align: 'center',
+      render: (_: any, __: PadLockVO, index: number) => {
+        return (queryParams.pageNo - 1) * queryParams.pageSize + index + 1;
+      },
+    },
+    {
+      title: '挂锁编号',
+      dataIndex: 'lockId',
+      width: 100,
+      align: 'center',
+    },
+    {
+      title: '挂锁编码',
+      dataIndex: 'lockCode',
+      width: 150,
+    },
+    {
+      title: '挂锁名称',
+      dataIndex: 'lockName',
+      width: 200,
+    },
+    {
+      title: '状态',
+      dataIndex: 'enableFlag',
+      width: 100,
+      align: 'center',
+      render: (flag: string) => {
+        return flag === '1' || flag === 'Y' ? '启用' : '禁用';
+      },
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      ellipsis: true,
+    },
+    {
+      title: '操作',
+      width: 120,
+      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>
+      ),
+    },
+  ];
+
+  // 挂锁类型表格列
+  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: '类型名称',
+      dataIndex: 'lockTypeName',
+      width: 200,
+    },
+    {
+      title: '父类型',
+      dataIndex: 'parentTypeId',
+      width: 100,
+      align: 'center',
+      render: (id: number) => id || '-',
+    },
+    {
+      title: '状态',
+      dataIndex: 'enableFlag',
+      width: 100,
+      align: 'center',
+      render: (flag: string) => {
+        return flag === '1' || flag === 'Y' ? '启用' : '禁用';
+      },
+    },
+    {
+      title: '备注',
+      dataIndex: 'remark',
+      ellipsis: true,
+    },
+    {
+      title: '操作',
+      width: 120,
+      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>
+      ),
+    },
+  ];
+
+  return (
+    <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="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
+                    value={queryParams.lockName || ''}
+                    onChange={(e) => setQueryParams({ ...queryParams, lockName: e.target.value })}
+                    onPressEnter={handleQuery}
+                    placeholder="请输入挂锁名称"
+                    style={{ width: 192 }}
+                    allowClear
+                  />
+                </div>
+              </div>
+
+              {/* 操作按钮组 */}
+              <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={<Plus className="w-4 h-4" />}
+                  onClick={() => {
+                    // TODO: 实现新增挂锁功能
+                    toast.info('新增挂锁功能待实现');
+                  }}
+                >
+                  新增
+                </Button>
+                
+                <Button
+                  icon={<Settings className="w-4 h-4" />}
+                  onClick={() => setViewMode('type')}
+                >
+                  设置挂锁类型
+                </Button>
+              </Space>
+            </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>
+
+          {/* 分页 */}
+          {!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, pageNo: queryParams.pageNo! - 1 })}
+                    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={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo! + 1 })}
+                    disabled={queryParams.pageNo! >= Math.ceil(total / queryParams.pageSize!)}
+                  >
+                    下一页
+                  </Button>
+                </div>
+              </div>
+            </div>
+          )}
+        </>
+      ) : (
+        <>
+          {/* 挂锁类型搜索栏 */}
+          <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>
+                <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">
+                  <Button
+                    onClick={() => setTypeQueryParams({ ...typeQueryParams, pageNo: typeQueryParams.pageNo! - 1 })}
+                    disabled={typeQueryParams.pageNo! <= 1}
+                  >
+                    上一页
+                  </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!)}
+                  >
+                    下一页
+                  </Button>
+                </div>
+              </div>
+            </div>
+          )}
+
+          {/* 挂锁类型表单弹窗 */}
+          {showTypeForm && (
+            <TypeFormModal
+              visible={showTypeForm}
+              editingType={editingType}
+              onCancel={() => {
+                setShowTypeForm(false);
+                setEditingType(null);
+              }}
+              onSave={saveType}
+            />
+          )}
+        </>
+      )}
+    </div>
+  );
+}
+
+// 挂锁类型表单弹窗组件
+interface TypeFormModalProps {
+  visible: boolean;
+  editingType: PadLockTypeVO | null;
+  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: '',
+  });
+
+  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: '',
+      });
+    }
+  }, [editingType, visible]);
+
+  const handleSubmit = () => {
+    if (!formData.lockTypeCode.trim()) {
+      toast.error('请输入类型编码');
+      return;
+    }
+    if (!formData.lockTypeName.trim()) {
+      toast.error('请输入类型名称');
+      return;
+    }
+    onSave(formData);
+  };
+
+  return (
+    <Modal
+      title={editingType ? '编辑挂锁类型' : '新增挂锁类型'}
+      open={visible}
+      onOk={handleSubmit}
+      onCancel={onCancel}
+      okText="确定"
+      cancelText="取消"
+      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="请输入类型名称"
+          />
+        </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(可选)"
+          />
+        </div>
+        <div>
+          <label className="block text-sm text-gray-700 mb-2">状态</label>
+          <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}
+          />
+        </div>
+      </div>
+    </Modal>
+  );
+}
+

+ 8 - 3
src/config/axios.ts

@@ -17,9 +17,14 @@ const request = {
     return await axiosInstance.put(url, data);
   },
 
-  delete: async (config: { url: string }) => {
-    const { url } = config;
-    return await axiosInstance.delete(url);
+  delete: async (config: { url: string; params?: any }) => {
+    const { url, params } = config;
+    return await axiosInstance.delete(url, { params });
+  },
+
+  download: async (config: { url: string; params?: any }) => {
+    const { url, params } = config;
+    return await axiosInstance.get(url, { params, responseType: 'blob' });
   },
 };
 

+ 9 - 0
src/types/index.ts

@@ -72,3 +72,12 @@ export interface RoleVO {
   remark?: string;
 }
 
+// 岗位/工作站节点类型
+export interface WorkstationNode {
+  id: number;
+  workstationName: string;
+  parentId?: number;
+  children?: WorkstationNode[];
+  [key: string]: any;
+}
+

+ 81 - 2
src/utils/axios.ts

@@ -73,9 +73,88 @@ axiosInstance.interceptors.response.use(
     if (data.code !== undefined) {
       if (data.code === 200 || data.code === 0) {
         return data.data !== undefined ? data.data : data;
+      } else if (data.code === 401) {
+        // 业务错误码 401,表示未登录,需要处理 token 刷新或退出登录
+        const originalRequest = config as InternalAxiosRequestConfig & { _retry?: boolean };
+        
+        // 如果是刷新token的请求失败,直接跳转登录
+        if (originalRequest.url?.includes('/refresh-token') || originalRequest._retry) {
+          const errorMessage = '登录已过期,请重新登录';
+          clearAuth();
+          window.location.href = '/login';
+          return Promise.reject(new Error(errorMessage));
+        }
+
+        // 如果正在刷新token,将请求加入队列
+        if (isRefreshing) {
+          return new Promise((resolve, reject) => {
+            failedQueue.push({ resolve, reject });
+          })
+            .then((token) => {
+              if (originalRequest.headers) {
+                originalRequest.headers.Authorization = `Bearer ${token}`;
+              }
+              return axiosInstance(originalRequest);
+            })
+            .catch((err) => {
+              return Promise.reject(err);
+            });
+        }
+
+        // 尝试刷新token
+        const refreshToken = getRefreshToken();
+        if (!refreshToken) {
+          const errorMessage = '登录已过期,请重新登录';
+          clearAuth();
+          window.location.href = '/login';
+          return Promise.reject(new Error(errorMessage));
+        }
+
+        isRefreshing = true;
+        originalRequest._retry = true;
+
+        return loginApi.refreshToken(refreshToken)
+          .then((res: any) => {
+            const tokenData = res?.data || res;
+            const newAccessToken = tokenData?.accessToken || tokenData?.token;
+            const newRefreshToken = tokenData?.refreshToken;
+
+            if (newAccessToken) {
+              // 更新token
+              setToken({ accessToken: newAccessToken, token: newAccessToken });
+              setAccessToken(newAccessToken);
+              if (newRefreshToken) {
+                setRefreshToken(newRefreshToken);
+              }
+
+              // 更新请求头
+              if (originalRequest.headers) {
+                originalRequest.headers.Authorization = `Bearer ${newAccessToken}`;
+              }
+
+              // 处理队列中的请求
+              processQueue(null, newAccessToken);
+
+              // 重试原始请求
+              return axiosInstance(originalRequest);
+            } else {
+              throw new Error('刷新token失败:未返回新token');
+            }
+          })
+          .catch((refreshError: any) => {
+            // 刷新失败,清除所有认证信息并跳转登录
+            const errorMessage = '登录已过期,请重新登录';
+            processQueue(refreshError, null);
+            clearAuth();
+            window.location.href = '/login';
+            return Promise.reject(new Error(errorMessage));
+          })
+          .finally(() => {
+            isRefreshing = false;
+          });
       } else {
-        // 业务错误
-        const errorMessage = data.message || '请求失败';
+        // 其他业务错误
+        const errorMessage = data.message || data.msg || '请求失败';
         toast.error(errorMessage);
         return Promise.reject(new Error(errorMessage));
       }

+ 3 - 0
src/utils/dict.ts

@@ -162,5 +162,8 @@ export enum DICT_TYPE {
   SYSTEM_MAIL_SEND_STATUS = 'system_mail_send_status',
   SYSTEM_NOTIFY_TEMPLATE_TYPE = 'system_notify_template_type',
   SYSTEM_SOCIAL_TYPE = 'system_social_type',
+  
+  // ========== ISCS 模块 ==========
+  POWER_TYPE = 'power_type', // 能量源
 }