| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180 |
- 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';
- 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">
- {/* 搜索条件 + 搜索、重置紧跟其后 */}
- <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 flex-shrink-0">
- <label className="text-sm font-medium text-gray-700 whitespace-nowrap">{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-[150px] max-w-[200px]"
- 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="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,
- }}
- />
- </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">
- {/* 搜索条件 + 搜索、重置紧跟其后 */}
- <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 flex-shrink-0">
- <label className="text-sm font-medium text-gray-700 whitespace-nowrap">{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-[150px] max-w-[200px]"
- 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>
- <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}
- />
- )}
- </>
- )}
- </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>
- );
- }
|