| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212 |
- 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 { hardwareApi } from '../api/Hardware';
- import { toast } from 'sonner';
- import { Modal, Button, Input, Space, Table, Select, 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.hardwareName'),
- dataIndex: 'hardwareName',
- width: 150,
- render: (text: string) => text || '-',
- },
- {
- 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 flex-1 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>
- </div>
- {/* 操作按钮组 */}
- <Space className="flex-shrink-0">
- <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>
-
- <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 flex-1 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>
- </div>
- {/* 操作按钮组 */}
- <Space className="flex-shrink-0">
- <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>
-
- <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 [hardwareOptions, setHardwareOptions] = useState<{ label: string; value: number }[]>([]);
- 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 getHardwareList = async () => {
- try {
- const response = await hardwareApi.listHardware({ pageNo: 1, pageSize: -1 });
- const options = response.list.map(item => ({
- label: item.hardwareName,
- value: item.id!,
- }));
- setHardwareOptions(options);
- } catch (error) {
- console.error(t('padLockManagement.getHardwareListFailed'), error);
- }
- };
- // 获取挂锁类型列表
- const getLockTypeList = async () => {
- try {
- const response = await padLockTypeApi.listPadLockType({ pageNo: 1, pageSize: -1 });
- const flatList = response.list || [];
-
- // 转换为树形结构
- const treeData = handleTree<PadLockTypeVO>(
- flatList.map(item => ({ ...item, id: item.lockTypeId || item.id })),
- 'id',
- 'parentTypeId',
- 'children'
- );
-
- const buildTreeSelectData = (nodes: PadLockTypeVO[]): any[] => {
- return nodes.map(node => ({
- title: node.lockTypeName,
- value: node.lockTypeId || node.id,
- children: node.children ? buildTreeSelectData(node.children) : undefined,
- }));
- };
-
- setLockTypeTreeOptions(buildTreeSelectData(treeData));
- } catch (error) {
- console.error(t('padLockManagement.getPadLockTypeListFailed'), error);
- }
- };
- useEffect(() => {
- if (visible) {
- getHardwareList();
- getLockTypeList();
-
- if (editingPadLock) {
- form.setFieldsValue({
- hardwareId: editingPadLock.hardwareId,
- lockTypeId: editingPadLock.lockTypeId,
- lockName: editingPadLock.lockName || '',
- lockNfc: editingPadLock.lockNfc || '',
- lockSpec: editingPadLock.lockSpec || '',
- exStatus: editingPadLock.exStatus || '1',
- exRemark: editingPadLock.exRemark || '',
- });
- } else {
- form.setFieldsValue({
- hardwareId: undefined,
- lockTypeId: undefined,
- lockName: '',
- lockNfc: '',
- lockSpec: '',
- exStatus: '1',
- exRemark: '',
- });
- }
- }
- }, [visible, editingPadLock, form]);
- const handleSubmit = async () => {
- try {
- const values = await form.validateFields();
- setFormLoading(true);
-
- // 编辑模式下,合并原始数据和表单数据,确保所有必要字段都被传递
- const submitData: PadLockVO = 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('table.hardwareName')}
- name="hardwareId"
- >
- <Select
- placeholder={t('form.hardwareNamePlaceholder')}
- allowClear
- options={hardwareOptions}
- />
- </Form.Item>
-
- <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>
- );
- }
|