| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187 |
- import React, { useState, useEffect, useRef, useMemo } from '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 { toast } from 'sonner';
- import { Modal, Button, Input, Space, Table, TreeSelect, Form, Image, Switch, Radio, Tooltip } 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';
- import { useTranslation } from 'react-i18next';
- import PermissionWrapper from './PermissionWrapper';
- import './IsolationWorkListTable.css';
- interface PadLockManagementProps {
- subMenu?: string;
- }
- export default function PadLockManagement({ subMenu }: PadLockManagementProps) {
- const { t } = useTranslation();
- 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 [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
- const [queryParams, setQueryParams] = useState<PageParam>({
- pageNo: 1,
- pageSize: 10,
- lockCode: undefined,
- 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: -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) => {
- 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 || t('padLockManagement.getPadLockListFailed'));
- } finally {
- setLoading(false);
- }
- };
- // 获取挂锁类型列表
- const getTypeList = async (params?: PadLockTypePageParam) => {
- const currentParams = params || typeQueryParams;
- setTypeLoading(true);
- try {
- const response = await padLockTypeApi.listPadLockType(currentParams);
- 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 || t('padLockManagement.getPadLockTypeListFailed'));
- } 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) => {
- const ids = id ? [id] : selectedRowKeys.map(key => Number(key));
- if (ids.length === 0) {
- toast.error(t('common.pleaseSelect'));
- return;
- }
- Modal.confirm({
- title: t('common.confirmDelete'),
- icon: <ExclamationCircleOutlined />,
- content: (
- <div>
- <p>{t('common.confirmDeleteText')} {ids.length} {t('common.items')}?</p>
- <p style={{ color: '#ff4d4f', marginTop: '8px' }}>{t('common.confirmDeleteWarning')}</p>
- </div>
- ),
- okText: t('common.confirmDelete'),
- okType: 'danger',
- cancelText: t('common.cancel'),
- onOk: async () => {
- try {
- await padLockApi.delPadLock(ids);
- toast.success(t('common.deleteSuccess'));
- setSelectedRowKeys([]);
- await getList();
- } catch (error: any) {
- toast.error(error.message || t('common.deleteFailed'));
- }
- },
- });
- };
- // 打开挂锁表单
- 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 || t('padLockManagement.getPadLockInfoFailed'));
- });
- }
- };
- // 保存挂锁
- const savePadLock = async (formData: PadLockVO) => {
- try {
- if (editingPadLock?.lockId || editingPadLock?.id) {
- // 编辑模式下,合并原始数据和表单数据,确保所有必要字段都被传递
- const updateData: PadLockVO = {
- // 保留原始数据中的字段(这些字段用户不能编辑但需要传递)
- ...editingPadLock,
- // 用表单数据覆盖可编辑字段
- ...formData,
- // 确保 id 和 lockId 都存在
- id: editingPadLock.id || editingPadLock.lockId,
- lockId: editingPadLock.lockId || editingPadLock.id,
- };
-
- console.log('提交的挂锁数据:', updateData); // 调试用
- await padLockApi.updatePadLock(updateData);
- toast.success(t('common.updateSuccess'));
- } else {
- await padLockApi.addPadLock(formData);
- toast.success(t('common.addSuccess'));
- }
- setShowPadLockForm(false);
- setEditingPadLock(null);
- await getList();
- } catch (error: any) {
- toast.error(error.message || t('common.saveFailed'));
- }
- };
- // 删除挂锁类型
- const handleDeleteType = async (id: number, name?: string) => {
- Modal.confirm({
- title: t('common.confirmDelete'),
- icon: <ExclamationCircleOutlined />,
- content: (
- <div>
- <p>{t('common.confirmDeleteText')} <strong>"{name || t('common.type')}"</strong>?</p>
- <p style={{ color: '#ff4d4f', marginTop: '8px' }}>{t('common.confirmDeleteWarning')}</p>
- </div>
- ),
- okText: t('common.confirmDeleteButton'),
- okType: 'danger',
- cancelText: t('common.cancel'),
- onOk: async () => {
- try {
- await padLockTypeApi.delPadLockType(id);
- toast.success(t('common.deleteSuccess'));
- await getTypeList();
- } catch (error: any) {
- toast.error(error.message || t('common.deleteFailed'));
- }
- },
- });
- };
- // 打开挂锁类型表单
- 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 || t('padLockManagement.getPadLockTypeInfoFailed'));
- });
- }
- };
- // 展开/折叠所有
- 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 || editingType?.id) {
- // 编辑模式下,合并原始数据和表单数据,确保所有必要字段都被传递
- const updateData: PadLockTypeVO = {
- // 保留原始数据中的字段(这些字段用户不能编辑但需要传递)
- ...editingType,
- // 用表单数据覆盖可编辑字段
- ...formData,
- // 确保 id 和 lockTypeId 都存在
- id: editingType.id || editingType.lockTypeId,
- lockTypeId: editingType.lockTypeId || editingType.id,
- };
-
- console.log('提交的挂锁类型数据:', updateData); // 调试用
- await padLockTypeApi.updatePadLockType(updateData);
- toast.success(t('common.updateSuccess'));
- } else {
- await padLockTypeApi.addPadLockType(formData);
- toast.success(t('common.addSuccess'));
- }
- setShowTypeForm(false);
- setEditingType(null);
- setParentTypeId(undefined);
- await getTypeList();
- } catch (error: any) {
- toast.error(error.message || t('common.saveFailed'));
- }
- };
- // 挂锁列表表格列
- const padLockColumns: ColumnsType<PadLockVO> = [
- {
- title: t('table.padLockName'),
- dataIndex: 'lockName',
- width: 200,
- },
- {
- title: t('table.padLockNfc'),
- dataIndex: 'lockNfc',
- width: 180,
- ellipsis: true,
- },
- {
- title: t('table.padLockType'),
- dataIndex: 'lockTypeName',
- width: 150,
- render: (text: string) => text || '-',
- },
- {
- title: t('table.padLockModel'),
- dataIndex: 'lockSpec',
- width: 150,
- render: (text: string) => text || '-',
- },
- {
- title: t('table.status'),
- dataIndex: 'exStatus',
- width: 100,
- align: 'center',
- render: (status: string) => {
- if (status === null || status === undefined) return '-';
- const isEnabled = status === '1';
- return (
- <span className={`inline-flex px-3 py-1 rounded-lg text-xs ${
- isEnabled ? 'bg-green-100 text-green-700' : 'bg-gray-100 text-gray-700'
- }`}>
- {isEnabled ? t('common.enabled') : t('common.disabled')}
- </span>
- );
- },
- },
- {
- title: t('table.remark'),
- dataIndex: 'exRemark',
- render: (text: string) => {
- const remarkText = text || '-';
- const maxLength = 20;
- const shouldTruncate = remarkText.length > maxLength;
- const displayText = shouldTruncate ? remarkText.slice(0, maxLength) + '...' : remarkText;
-
- return (
- <Tooltip placement="topLeft" title={remarkText}>
- <span>{displayText}</span>
- </Tooltip>
- );
- },
- },
- {
- title: t('table.createTime'),
- dataIndex: 'createTime',
- width: 180,
- render: (text: string | Date) => text ? dateFormatter(text) : '-',
- },
- {
- title: t('table.operation'),
- width: 150,
- align: 'center',
- fixed: 'right',
- render: (_: any, record: PadLockVO) => (
- <div className="flex items-center gap-2 justify-center">
- <PermissionWrapper permission="iscs:lock:update">
- <UIButton
- variant="ghost"
- size="sm"
- onClick={() => openPadLockForm('update', record.lockId || record.id)}
- className="h-8 px-2 transition-colors hover:underline"
- style={{ color: '#000000' }}
- onMouseEnter={(e) => {
- e.currentTarget.style.color = '#1677ff';
- e.currentTarget.style.textDecoration = 'underline';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #1677ff');
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.color = '#000000';
- e.currentTarget.style.textDecoration = 'none';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #000000');
- }}
- >
- <Edit2 className="w-4 h-4" style={{ color: '#000000' }} />
- <span className="ml-1">{t('common.edit')}</span>
- </UIButton>
- </PermissionWrapper>
- <PermissionWrapper permission="iscs:lock:delete">
- <UIButton
- variant="ghost"
- size="sm"
- onClick={() => handleDelete(record.lockId || record.id)}
- className="h-8 px-2 transition-colors hover:underline"
- style={{ color: '#000000' }}
- onMouseEnter={(e) => {
- e.currentTarget.style.color = '#1677ff';
- e.currentTarget.style.textDecoration = 'underline';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #1677ff');
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.color = '#000000';
- e.currentTarget.style.textDecoration = 'none';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #000000');
- }}
- >
- <Trash2 className="w-4 h-4" style={{ color: '#000000' }} />
- <span className="ml-1">{t('common.delete')}</span>
- </UIButton>
- </PermissionWrapper>
- </div>
- ),
- },
- ];
- // 挂锁类型表格列(树形表格)
- const padLockTypeColumns: ColumnsType<PadLockTypeVO> = [
- {
- title: t('table.padLockTypeName'),
- dataIndex: 'lockTypeName',
- width: 200,
- align: 'center',
- },
- {
- title: t('table.padLockTypeIcon'),
- dataIndex: 'lockTypeIcon',
- width: 120,
- align: 'center',
- render: (url: string) => {
- if (!url) return '-';
- return (
- <Image
- src={url}
- alt={t('padLockManagement.icon')}
- width={50}
- height={50}
- style={{ objectFit: 'cover' }}
- preview={{
- mask: t('padLockManagement.view'),
- }}
- />
- );
- },
- },
- {
- title: t('table.padLockTypeImage'),
- dataIndex: 'lockTypeImg',
- width: 120,
- align: 'center',
- render: (url: string) => {
- if (!url) return '-';
- return (
- <Image
- src={url}
- alt={t('padLockManagement.image')}
- width={50}
- height={50}
- style={{ objectFit: 'cover' }}
- preview={{
- mask: t('padLockManagement.view'),
- }}
- />
- );
- },
- },
- {
- title: t('table.padLockModel'),
- dataIndex: 'lockTypeSpec',
- width: 150,
- align: 'center',
- render: (spec: string) => spec || '-',
- },
- {
- title: t('table.operation'),
- width: 200,
- align: 'center',
- fixed: 'right',
- render: (_: any, record: PadLockTypeVO) => (
- <div className="flex items-center gap-2 justify-center">
- <PermissionWrapper permission="iscs:lock:update">
- <UIButton
- variant="ghost"
- size="sm"
- onClick={() => openTypeForm('update', record.lockTypeId || record.id)}
- className="h-8 px-2 transition-colors hover:underline"
- style={{ color: '#000000' }}
- onMouseEnter={(e) => {
- e.currentTarget.style.color = '#1677ff';
- e.currentTarget.style.textDecoration = 'underline';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #1677ff');
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.color = '#000000';
- e.currentTarget.style.textDecoration = 'none';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #000000');
- }}
- >
- <Edit2 className="w-4 h-4" style={{ color: '#000000' }} />
- <span className="ml-1">{t('common.edit')}</span>
- </UIButton>
- </PermissionWrapper>
- <UIButton
- variant="ghost"
- size="sm"
- onClick={() => openTypeForm('create', undefined, record.lockTypeId || record.id)}
- className="h-8 px-2 transition-colors hover:underline"
- style={{ color: '#000000' }}
- onMouseEnter={(e) => {
- e.currentTarget.style.color = '#1677ff';
- e.currentTarget.style.textDecoration = 'underline';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #1677ff');
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.color = '#000000';
- e.currentTarget.style.textDecoration = 'none';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #000000');
- }}
- >
- <Plus className="w-4 h-4" style={{ color: '#000000' }} />
- <span className="ml-1">{t('common.addNew')}</span>
- </UIButton>
- <PermissionWrapper permission="iscs:lock:delete">
- <UIButton
- variant="ghost"
- size="sm"
- onClick={() => handleDeleteType(record.lockTypeId || record.id!, record.lockTypeName)}
- className="h-8 px-2 transition-colors hover:underline"
- style={{ color: '#000000' }}
- onMouseEnter={(e) => {
- e.currentTarget.style.color = '#1677ff';
- e.currentTarget.style.textDecoration = 'underline';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #1677ff');
- }}
- onMouseLeave={(e) => {
- e.currentTarget.style.color = '#000000';
- e.currentTarget.style.textDecoration = 'none';
- e.currentTarget.querySelector('svg')?.setAttribute('style', 'color: #000000');
- }}
- >
- <Trash2 className="w-4 h-4" style={{ color: '#000000' }} />
- <span className="ml-1">{t('common.delete')}</span>
- </UIButton>
- </PermissionWrapper>
- </div>
- ),
- },
- ];
- return (
- <div className="space-y-4">
- {viewMode === 'list' ? (
- <>
- {/* 挂锁列表 - 搜索栏和表格放在一起 */}
- <div className="bg-white rounded-xl border border-gray-200/50 shadow-sm overflow-hidden">
- {/* 搜索栏 */}
- <div className="p-4 lg:p-5 border-b border-gray-200">
- <div className="flex items-center justify-between gap-3 lg:gap-4 flex-wrap min-w-0">
- {/* 搜索条件:按原宽度 200px,不超出,可缩小 */}
- <div className="flex items-center gap-2 lg:gap-3 flex-wrap min-w-0">
- <div className="flex items-center gap-2 lg:gap-3 w-[200px] min-w-[100px] flex-shrink">
- <label className="text-sm font-medium text-gray-700 whitespace-nowrap flex-shrink-0">{t('form.padLockName')}:</label>
- <Input
- value={queryParams.lockName || ''}
- onChange={(e) => setQueryParams({ ...queryParams, lockName: e.target.value })}
- onPressEnter={handleQuery}
- placeholder={t('form.padLockNamePlaceholder')}
- className="min-w-0 w-full"
- allowClear
- />
- </div>
- <Space className="flex-shrink-0" size="small">
- <PermissionWrapper permission="iscs:lock:query">
- <Button
- type="primary"
- icon={<Search className="w-4 h-4" />}
- onClick={handleQuery}
- >
- {t('common.search')}
- </Button>
- </PermissionWrapper>
-
- <PermissionWrapper permission="iscs:lock:query">
- <Button
- icon={<RefreshCw className="w-4 h-4" />}
- onClick={resetQuery}
- >
- {t('common.reset')}
- </Button>
- </PermissionWrapper>
- </Space>
- </div>
- {/* 新增 / 批量删除 / 设置类型 等按钮组保持在最右侧 */}
- <Space className="flex-shrink-0" size="small">
- <PermissionWrapper permission="iscs:lock:create">
- <Button
- type="primary"
- icon={<Plus className="w-4 h-4" />}
- onClick={() => openPadLockForm('create')}
- >
- {t('common.addNew')}
- </Button>
- </PermissionWrapper>
-
- <PermissionWrapper permission="iscs:lock:delete">
- <Button
- danger
- icon={<Trash2 className="w-4 h-4" />}
- onClick={() => handleDelete()}
- disabled={selectedRowKeys.length === 0}
- >
- {t('common.batchDelete')}
- </Button>
- </PermissionWrapper>
-
- <Button
- icon={<Settings className="w-4 h-4" />}
- onClick={() => setViewMode('type')}
- >
- {t('common.setPadLockType')}
- </Button>
- </Space>
- </div>
- </div>
- {/* 表格 */}
- <div className="isolation-work-list-table min-w-0">
- <Table
- columns={padLockColumns}
- dataSource={list}
- rowKey={(record) => record.lockId || record.id || ''}
- loading={loading}
- pagination={false}
- scroll={{ x: 'max-content' }}
- rowSelection={{
- selectedRowKeys,
- onChange: setSelectedRowKeys,
- }}
- rowClassName={(_, index) =>
- index !== undefined && index % 2 === 1 ? 'isolation-work-list-row-alt' : ''
- }
- />
- </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">
- {t('common.total')} <span className="text-blue-600 font-medium">{total}</span> {t('common.records')}
- </div>
- <div className="flex gap-2">
- <Button
- onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo! - 1 })}
- disabled={queryParams.pageNo! <= 1}
- >
- {t('common.prevPage')}
- </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!)}
- >
- {t('common.nextPage')}
- </Button>
- </div>
- </div>
- </div>
- )}
- {/* 挂锁表单弹窗 */}
- <PadLockFormModal
- visible={showPadLockForm}
- editingPadLock={editingPadLock}
- onCancel={() => {
- setShowPadLockForm(false);
- setEditingPadLock(null);
- }}
- onSave={savePadLock}
- />
- </>
- ) : (
- <>
- {/* 挂锁类型 - 搜索栏和表格放在一起 */}
- <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">
- {/* 搜索条件:按原宽度 200px,不超出,可缩小 */}
- <div className="flex items-center gap-2 lg:gap-3 flex-wrap min-w-0">
- <div className="flex items-center gap-2 lg:gap-3 w-[200px] min-w-[100px] flex-shrink">
- <label className="text-sm font-medium text-gray-700 whitespace-nowrap flex-shrink-0">{t('form.padLockTypeName')}:</label>
- <Input
- value={typeQueryParams.lockTypeName || ''}
- onChange={(e) => setTypeQueryParams({ ...typeQueryParams, lockTypeName: e.target.value })}
- onPressEnter={handleTypeQuery}
- placeholder={t('form.padLockTypeNamePlaceholder')}
- className="min-w-0 w-full"
- allowClear
- />
- </div>
- <Space className="flex-shrink-0" size="small">
- <PermissionWrapper permission="iscs:lock:query">
- <Button
- type="primary"
- icon={<Search className="w-4 h-4" />}
- onClick={handleTypeQuery}
- >
- {t('common.search')}
- </Button>
- </PermissionWrapper>
-
- <PermissionWrapper permission="iscs:lock:query">
- <Button
- icon={<RefreshCw className="w-4 h-4" />}
- onClick={resetTypeQuery}
- >
- {t('common.reset')}
- </Button>
- </PermissionWrapper>
- </Space>
- </div>
- {/* 新增 / 展开收起 / 返回列表 等按钮组保持在最右侧 */}
- <Space className="flex-shrink-0" size="small">
- <PermissionWrapper permission="iscs:lock:create">
- <Button
- type="primary"
- icon={<Plus className="w-4 h-4" />}
- onClick={() => openTypeForm('create')}
- >
- {t('common.addNew')}
- </Button>
- </PermissionWrapper>
-
- <Button
- icon={<ArrowUpDown className="w-4 h-4" />}
- onClick={toggleExpandAll}
- >
- {t('padLockManagement.expandCollapse')}
- </Button>
-
- <Button
- onClick={() => setViewMode('list')}
- >
- {t('padLockManagement.backToPadLockList')}
- </Button>
- </Space>
- </div>
- </div>
- {/* 表格 - 树形表格 */}
- <div className="isolation-work-list-table">
- <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,
- }}
- rowClassName={(_, index) =>
- index !== undefined && index % 2 === 1 ? 'isolation-work-list-row-alt' : ''
- }
- />
- </div>
- </div>
- {/* 挂锁类型表单弹窗 */}
- {showTypeForm && (
- <TypeFormModal
- visible={showTypeForm}
- editingType={editingType}
- parentTypeId={parentTypeId}
- typeList={typeList}
- onCancel={() => {
- setShowTypeForm(false);
- setEditingType(null);
- setParentTypeId(undefined);
- }}
- onSave={saveType}
- />
- )}
- </>
- )}
- </div>
- );
- }
- // 挂锁类型表单弹窗组件
- interface TypeFormModalProps {
- visible: boolean;
- editingType: PadLockTypeVO | null;
- parentTypeId?: number;
- typeList: PadLockTypeVO[];
- onCancel: () => void;
- onSave: (data: PadLockTypeVO) => void;
- }
- function TypeFormModal({ visible, editingType, parentTypeId, typeList, onCancel, onSave }: TypeFormModalProps) {
- const { t } = useTranslation();
- const [form] = Form.useForm();
- const [formLoading, setFormLoading] = useState(false);
- const [typeTreeOptions, setTypeTreeOptions] = useState<any[]>([]);
- // 构建树形选择器数据
- useEffect(() => {
- 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));
- }
- }, [typeList]);
- 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: '',
- });
- }
- }
- }, [visible, editingType, parentTypeId, form]);
- const handleSubmit = async () => {
- try {
- const values = await form.validateFields();
- setFormLoading(true);
-
- // 编辑模式下,合并原始数据和表单数据,确保所有必要字段都被传递
- const submitData: PadLockTypeVO = editingType ? {
- // 保留原始数据中的字段(这些字段用户不能编辑但需要传递)
- ...editingType,
- // 用表单数据覆盖可编辑字段
- ...values,
- // 确保 parentTypeId 存在(如果表单中没有,使用原始数据或默认值 0)
- parentTypeId: values.parentTypeId !== undefined ? (values.parentTypeId || 0) : (editingType.parentTypeId || 0),
- // 确保 id 和 lockTypeId 都存在
- id: editingType.id || editingType.lockTypeId,
- lockTypeId: editingType.lockTypeId || editingType.id,
- } : {
- // 新增模式下,只传递表单数据
- ...values,
- parentTypeId: values.parentTypeId || 0,
- };
-
- onSave(submitData);
- } catch (error) {
- // 表单验证失败
- } finally {
- setFormLoading(false);
- }
- };
- return (
- <Modal
- title={editingType ? t('form.editPadLockType') : t('form.addPadLockType')}
- open={visible}
- onOk={handleSubmit}
- onCancel={onCancel}
- okText={t('common.confirm')}
- cancelText={t('common.cancel')}
- confirmLoading={formLoading}
- width={600}
- >
- <Form
- form={form}
- layout="horizontal"
- labelCol={{ span: 6 }}
- wrapperCol={{ span: 18 }}
- className="mt-4"
- >
- {editingType?.parentTypeId !== 0 && (
- <Form.Item
- label={t('form.parentType')}
- name="parentTypeId"
- >
- <TreeSelect
- treeData={typeTreeOptions}
- placeholder={t('form.parentTypePlaceholder')}
- allowClear
- treeDefaultExpandAll
- />
- </Form.Item>
- )}
-
- <Form.Item
- label={t('form.padLockTypeName')}
- name="lockTypeName"
- rules={[{ required: true, message: t('form.padLockTypeNameRequired') }]}
- >
- <Input placeholder={t('form.padLockTypeNamePlaceholder')} />
- </Form.Item>
-
- <Form.Item
- label={t('form.padLockTypeSpec')}
- name="lockTypeSpec"
- >
- <Input placeholder={t('form.padLockTypeSpecPlaceholder')} />
- </Form.Item>
-
- <Form.Item
- label={t('form.padLockTypeIcon')}
- name="lockTypeIcon"
- >
- <UploadImg
- value={form.getFieldValue('lockTypeIcon')}
- onChange={(value) => form.setFieldsValue({ lockTypeIcon: value })}
- limit={1}
- height="100px"
- width="100px"
- />
- </Form.Item>
-
- <Form.Item
- label={t('form.padLockTypeImage')}
- name="lockTypeImg"
- >
- <UploadImg
- value={form.getFieldValue('lockTypeImg')}
- onChange={(value) => form.setFieldsValue({ lockTypeImg: value })}
- limit={1}
- height="100px"
- width="100px"
- />
- </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 { t } = useTranslation();
- const [form] = Form.useForm();
- const [formLoading, setFormLoading] = useState(false);
- const [lockTypeTreeOptions, setLockTypeTreeOptions] = useState<any[]>([]);
- const statusOptions = useMemo(() => {
- // 使用默认状态选项
- return [
- { dictType: 'common_status', label: t('common.enabled'), value: '1' },
- { dictType: 'common_status', label: t('common.disabled'), value: '0' },
- ];
- }, [t]);
- // 获取挂锁类型列表
- 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(t('padLockManagement.getPadLockTypeListFailed'), error);
- }
- };
- useEffect(() => {
- if (visible) {
- 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 = editingPadLock ? {
- // 保留原始数据中的字段(这些字段用户不能编辑但需要传递)
- ...editingPadLock,
- // 用表单数据覆盖可编辑字段
- ...values,
- // 确保 id 和 lockId 都存在
- id: editingPadLock.id || editingPadLock.lockId,
- lockId: editingPadLock.lockId || editingPadLock.id,
- } : {
- // 新增模式下,只传递表单数据
- ...values,
- };
-
- onSave(submitData);
- } catch (error) {
- // 表单验证失败
- } finally {
- setFormLoading(false);
- }
- };
- return (
- <Modal
- title={editingPadLock ? t('form.editPadLock') : t('form.addPadLock')}
- open={visible}
- onOk={handleSubmit}
- onCancel={onCancel}
- okText={t('common.confirm')}
- cancelText={t('common.cancel')}
- confirmLoading={formLoading}
- width={600}
- >
- <Form
- form={form}
- layout="horizontal"
- labelCol={{ span: 6 }}
- wrapperCol={{ span: 18 }}
- className="mt-4"
- >
- <Form.Item
- label={t('form.padLockType')}
- name="lockTypeId"
- >
- <TreeSelect
- treeData={lockTypeTreeOptions}
- placeholder={t('form.padLockTypePlaceholder')}
- allowClear
- treeDefaultExpandAll
- />
- </Form.Item>
-
- <Form.Item
- label={t('form.padLockName')}
- name="lockName"
- rules={[{ required: true, message: t('form.padLockNameRequired') }]}
- >
- <Input placeholder={t('form.padLockNamePlaceholder')} />
- </Form.Item>
-
- <Form.Item
- label={t('form.padLockNfc')}
- name="lockNfc"
- rules={[{ required: true, message: t('form.padLockNfcRequired') }]}
- >
- <Input placeholder={t('form.padLockNfcPlaceholder')} />
- </Form.Item>
-
- <Form.Item
- label={t('form.padLockSpec')}
- name="lockSpec"
- >
- <Input placeholder={t('form.padLockSpecPlaceholder')} />
- </Form.Item>
-
- <Form.Item
- label={t('form.status')}
- name="exStatus"
- >
- <Radio.Group>
- {statusOptions.map(option => (
- <Radio key={option.value} value={option.value}>
- {option.label}
- </Radio>
- ))}
- </Radio.Group>
- </Form.Item>
-
- <Form.Item
- label={t('form.remark')}
- name="exRemark"
- >
- <Input.TextArea placeholder={t('form.remarkPlaceholder')} rows={3} />
- </Form.Item>
- </Form>
- </Modal>
- );
- }
|