ソースを参照

用户个人资料接口对接 硬件钥匙和挂锁冲突解决

pm 5 ヶ月 前
コミット
639bd1afd1
4 ファイル変更626 行追加0 行削除
  1. 94 0
      src/api/Key.ts
  2. 7 0
      src/components/HardwareManagement.tsx
  3. 516 0
      src/components/KeyManagement.tsx
  4. 9 0
      src/utils/dict.ts

+ 94 - 0
src/api/Key.ts

@@ -0,0 +1,94 @@
+import { request } from '../utils/axios';
+
+export interface KeyVO {
+  keyId?: number;
+  id?: number; // 兼容字段
+  keyCode?: string;
+  keyName: string;
+  keyNfc?: string;
+  macAddress?: string;
+  keySpec?: string;
+  hardwareId?: number;
+  hardwareName?: string;
+  exStatus?: string; // 状态:'0' 或 '1'
+  exRemark?: string; // 备注
+  enableFlag?: string;
+  remark?: string;
+  createBy?: string;
+  createTime?: Date | string;
+  updateBy?: string;
+  updateTime?: Date | string;
+}
+
+export interface PageParam {
+  pageNo: number;
+  pageSize: number;
+  keyCode?: string;
+  keyName?: string;
+  keyNfc?: string;
+  macAddress?: string;
+}
+
+// 分页响应类型
+export interface PageResponse<T> {
+  list: T[];
+  total: number;
+}
+
+// 钥匙管理 API
+export const keyApi = {
+  // 查询钥匙-列表
+  listKey: async (params: PageParam): Promise<PageResponse<KeyVO>> => {
+    const response: any = await request.get({ 
+      url: '/iscs/key/getKeyPage', 
+      params 
+    });
+    
+    // 处理响应数据格式
+    if (response && typeof response === 'object') {
+      if ('data' in response && response.data) {
+        return response.data;
+      } else if ('list' in response || 'records' in response) {
+        return {
+          list: (response as any).list || (response as any).records || [],
+          total: (response as any).total || 0,
+        };
+      }
+    }
+    return response as PageResponse<KeyVO>;
+  },
+
+  // 获取钥匙详细信息
+  getKeyInfo: async (id: number): Promise<KeyVO> => {
+    const response: any = await request.get({
+      url: '/iscs/key/selectKeyById',
+      params: { id }
+    });
+    return (response?.data || response) as KeyVO;
+  },
+
+  // 新增钥匙
+  addKey: async (data: KeyVO): Promise<void> => {
+    await request.post({ 
+      url: '/iscs/key/insertKey', 
+      data 
+    });
+  },
+
+  // 修改钥匙信息
+  updateKey: async (data: KeyVO): Promise<void> => {
+    await request.put({ 
+      url: '/iscs/key/updateKey', 
+      data 
+    });
+  },
+
+  // 删除钥匙信息
+  delKey: async (ids: number | number[] | string): Promise<void> => {
+    const idsParam = typeof ids === 'string' ? ids : (Array.isArray(ids) ? ids.join(',') : ids);
+    await request.delete({
+      url: `/iscs/key/deleteKeyList?ids=${idsParam}`,
+    });
+  },
+};
+

+ 7 - 0
src/components/HardwareManagement.tsx

@@ -3,6 +3,7 @@ import { Plus, Search, Edit2, Trash2, MoreVertical, Package, Key, Lock, Briefcas
 import { Button } from 'antd';
 import { Button as UIButton } from './ui/button';
 import PadLockManagement from './PadLockManagement';
+import KeyManagement from './KeyManagement';
 import HardwareLockCabinetManagement from './lockCabinet/HardwareLockCabinetManagement';
 
 interface TableRow {
@@ -38,6 +39,12 @@ export default function HardwareManagement({ subMenu }: HardwareManagementProps)
     );
   }
 
+  // 如果是钥匙,使用专门的钥匙管理组件
+  // subMenu 可能是 '钥匙'、'key' 或 'keys'
+  if (subMenu === '钥匙' || subMenu === 'key' || subMenu === 'keys') {
+    return <KeyManagement subMenu={subMenu} />;
+  }
+
   const [searchTerm, setSearchTerm] = useState('');
   const [showAddModal, setShowAddModal] = useState(false);
   const [editingItem, setEditingItem] = useState<TableRow | null>(null);

+ 516 - 0
src/components/KeyManagement.tsx

@@ -0,0 +1,516 @@
+import React, { useState, useEffect } from 'react';
+import { Search, Plus, RefreshCw, Edit2, Trash2 } from 'lucide-react';
+import { keyApi, KeyVO, PageParam } from '../api/Key';
+import { hardwareApi } from '../api/Hardware';
+import { toast } from 'sonner';
+import { Modal, Button, Input, Space, Table, Select, Form, Switch, Radio } from 'antd';
+import { ExclamationCircleOutlined } from '@ant-design/icons';
+import type { ColumnsType } from 'antd/es/table';
+import { DICT_TYPE, getStrDictOptions } from '../utils/dict';
+import { dateFormatter } from '../utils/formatTime';
+import { Button as UIButton } from './ui/button';
+
+interface KeyManagementProps {
+  subMenu?: string;
+}
+
+export default function KeyManagement({ subMenu }: KeyManagementProps) {
+  const [loading, setLoading] = useState(true);
+  const [list, setList] = useState<KeyVO[]>([]);
+  const [total, setTotal] = useState(0);
+  const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
+  const [queryParams, setQueryParams] = useState<PageParam>({
+    pageNo: 1,
+    pageSize: 10,
+    keyName: undefined,
+  });
+  const [showKeyForm, setShowKeyForm] = useState(false);
+  const [editingKey, setEditingKey] = useState<KeyVO | null>(null);
+
+  // 获取钥匙列表
+  const getList = async (params?: PageParam) => {
+    const currentParams = params || queryParams;
+    setLoading(true);
+    try {
+      const response = await keyApi.listKey(currentParams);
+      setList(response.list || []);
+      setTotal(response.total || 0);
+    } catch (error: any) {
+      toast.error(error.message || '获取钥匙列表失败');
+    } finally {
+      setLoading(false);
+    }
+  };
+
+  useEffect(() => {
+    getList();
+  }, [queryParams.pageNo, queryParams.pageSize, queryParams.keyName]);
+
+  // 搜索钥匙
+  const handleQuery = () => {
+    const newParams = { ...queryParams, pageNo: 1 };
+    setQueryParams(newParams);
+    getList(newParams);
+  };
+
+  // 重置搜索
+  const resetQuery = () => {
+    const resetParams: PageParam = {
+      pageNo: 1,
+      pageSize: 10,
+      keyName: undefined,
+    };
+    setQueryParams(resetParams);
+    getList(resetParams);
+  };
+
+  // 删除钥匙
+  const handleDelete = async (id?: number) => {
+    const ids = id ? [id] : selectedRowKeys.map(key => Number(key));
+    if (ids.length === 0) {
+      toast.error('请选择要删除的数据');
+      return;
+    }
+
+    Modal.confirm({
+      title: '确认删除',
+      icon: <ExclamationCircleOutlined />,
+      content: (
+        <div>
+          <p>确定要删除选中的 {ids.length} 条钥匙数据吗?</p>
+          <p style={{ color: '#ff4d4f', marginTop: '8px' }}>删除后无法恢复,请谨慎操作!</p>
+        </div>
+      ),
+      okText: '确定删除',
+      okType: 'danger',
+      cancelText: '取消',
+      onOk: async () => {
+        try {
+          await keyApi.delKey(ids);
+          toast.success('删除成功');
+          setSelectedRowKeys([]);
+          await getList();
+        } catch (error: any) {
+          toast.error(error.message || '删除失败');
+        }
+      },
+    });
+  };
+
+  // 打开钥匙表单
+  const openKeyForm = (type: 'create' | 'update', id?: number) => {
+    if (type === 'create') {
+      setEditingKey(null);
+      setShowKeyForm(true);
+    } else if (type === 'update' && id) {
+      keyApi.getKeyInfo(id).then((data) => {
+        setEditingKey(data);
+        setShowKeyForm(true);
+      }).catch((error: any) => {
+        toast.error(error.message || '获取钥匙信息失败');
+      });
+    }
+  };
+
+  // 保存钥匙
+  const saveKey = async (formData: KeyVO) => {
+    try {
+      if (formData.keyId || formData.id) {
+        await keyApi.updateKey({
+          ...formData,
+          keyId: formData.keyId || formData.id,
+        });
+        toast.success('修改成功');
+      } else {
+        await keyApi.addKey(formData);
+        toast.success('新增成功');
+      }
+      setShowKeyForm(false);
+      setEditingKey(null);
+      await getList();
+    } catch (error: any) {
+      toast.error(error.message || '保存失败');
+    }
+  };
+
+  // 钥匙列表表格列
+  const keyColumns: ColumnsType<KeyVO> = [
+    {
+      title: '钥匙名称',
+      dataIndex: 'keyName',
+      width: 180,
+    },
+    {
+      title: '钥匙NFC',
+      dataIndex: 'keyNfc',
+      width: 180,
+      ellipsis: true,
+    },
+    {
+      title: '钥匙型号',
+      dataIndex: 'keySpec',
+      width: 180,
+      ellipsis: true,
+      render: (text: string) => text || '-',
+    },
+    {
+      title: 'MAC地址',
+      dataIndex: 'macAddress',
+      width: 150,
+      render: (text: string) => text || '-',
+    },
+    {
+      title: '状态',
+      dataIndex: 'exStatus',
+      width: 100,
+      align: 'center',
+      render: (status: string) => {
+        if (status === null || status === undefined) return '-';
+        return (
+          <Switch
+            checked={status === '1'}
+            disabled
+            checkedChildren="启用"
+            unCheckedChildren="禁用"
+          />
+        );
+      },
+    },
+    {
+      title: '备注',
+      dataIndex: 'exRemark',
+      ellipsis: true,
+      render: (text: string) => text || '-',
+    },
+    {
+      title: '所属硬件',
+      dataIndex: 'hardwareName',
+      width: 150,
+      render: (text: string) => text || '-',
+    },
+    {
+      title: '创建时间',
+      dataIndex: 'createTime',
+      width: 180,
+      render: (text: string | Date) => text ? dateFormatter(text) : '-',
+    },
+    {
+      title: '操作',
+      width: 150,
+      align: 'center',
+      fixed: 'right',
+      render: (_: any, record: KeyVO) => (
+        <div className="flex items-center gap-2 justify-center">
+          <UIButton
+            variant="ghost"
+            size="sm"
+            onClick={() => openKeyForm('update', record.keyId || record.id)}
+            className="h-8 px-2"
+          >
+            <Edit2 className="w-4 h-4" />
+            <span className="ml-1">编辑</span>
+          </UIButton>
+          <UIButton
+            variant="ghost"
+            size="sm"
+            onClick={() => handleDelete(record.keyId || record.id)}
+            className="h-8 px-2 text-red-600 hover:text-red-700"
+          >
+            <Trash2 className="w-4 h-4" />
+            <span className="ml-1">删除</span>
+          </UIButton>
+        </div>
+      ),
+    },
+  ];
+
+  return (
+    <div className="space-y-4">
+      {/* 钥匙列表 - 搜索栏和表格放在一起 */}
+      <div className="bg-white rounded-xl border border-gray-200/50 shadow-sm overflow-hidden">
+        {/* 搜索栏 */}
+        <div className="p-5 border-b border-gray-200">
+          <div className="flex items-center justify-between gap-4 flex-wrap">
+            {/* 搜索输入框 */}
+            <div className="flex items-center gap-3 flex-wrap flex-1">
+              <div className="flex items-center gap-3">
+                <label className="text-sm font-medium text-gray-700 whitespace-nowrap">钥匙名称:</label>
+                <Input
+                  value={queryParams.keyName || ''}
+                  onChange={(e) => setQueryParams({ ...queryParams, keyName: e.target.value })}
+                  onPressEnter={handleQuery}
+                  placeholder="请输入钥匙名称"
+                  style={{ width: 192 }}
+                  allowClear
+                />
+              </div>
+            </div>
+
+            {/* 操作按钮组 */}
+            <Space>
+              <Button
+                type="primary"
+                icon={<Search className="w-4 h-4" />}
+                onClick={handleQuery}
+              >
+                搜索
+              </Button>
+              
+              <Button
+                icon={<RefreshCw className="w-4 h-4" />}
+                onClick={resetQuery}
+              >
+                重置
+              </Button>
+              
+              <Button
+                type="primary"
+                icon={<Plus className="w-4 h-4" />}
+                onClick={() => openKeyForm('create')}
+              >
+                新增
+              </Button>
+              
+              <Button
+                danger
+                icon={<Trash2 className="w-4 h-4" />}
+                onClick={() => handleDelete()}
+                disabled={selectedRowKeys.length === 0}
+              >
+                批量删除
+              </Button>
+            </Space>
+          </div>
+        </div>
+
+        {/* 表格 */}
+        <div>
+          <Table
+            columns={keyColumns}
+            dataSource={list}
+            rowKey={(record) => record.keyId || 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">
+              共 <span className="text-blue-600 font-medium">{total}</span> 条记录
+            </div>
+            <div className="flex gap-2">
+              <Button
+                onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo! - 1 })}
+                disabled={queryParams.pageNo! <= 1}
+              >
+                上一页
+              </Button>
+              <span className="px-4 py-2 text-sm text-gray-600 flex items-center">
+                {queryParams.pageNo} / {Math.ceil(total / queryParams.pageSize!) || 1}
+              </span>
+              <Button
+                onClick={() => setQueryParams({ ...queryParams, pageNo: queryParams.pageNo! + 1 })}
+                disabled={queryParams.pageNo! >= Math.ceil(total / queryParams.pageSize!)}
+              >
+                下一页
+              </Button>
+            </div>
+          </div>
+        </div>
+      )}
+
+      {/* 钥匙表单弹窗 */}
+      {showKeyForm && (
+        <KeyFormModal
+          visible={showKeyForm}
+          editingKey={editingKey}
+          onCancel={() => {
+            setShowKeyForm(false);
+            setEditingKey(null);
+          }}
+          onSave={saveKey}
+        />
+      )}
+    </div>
+  );
+}
+
+// 钥匙表单弹窗组件
+interface KeyFormModalProps {
+  visible: boolean;
+  editingKey: KeyVO | null;
+  onCancel: () => void;
+  onSave: (data: KeyVO) => void;
+}
+
+function KeyFormModal({ visible, editingKey, onCancel, onSave }: KeyFormModalProps) {
+  const [form] = Form.useForm();
+  const [formLoading, setFormLoading] = useState(false);
+  const [hardwareOptions, setHardwareOptions] = useState<{ label: string; value: number }[]>([]);
+  const [statusOptions] = useState(() => {
+    return getStrDictOptions(DICT_TYPE.KEY_STATUS);
+  });
+
+  // 获取硬件列表
+  const getHardwareList = async () => {
+    try {
+      const response = await hardwareApi.listHardware({ pageNo: 1, pageSize: -1 });
+      const options = response.list.map(item => ({
+        label: item.hardwareName,
+        value: item.id!,
+      }));
+      setHardwareOptions(options);
+    } catch (error) {
+      console.error('获取硬件列表失败:', error);
+    }
+  };
+
+  useEffect(() => {
+    if (visible) {
+      getHardwareList();
+      
+      if (editingKey) {
+        form.setFieldsValue({
+          hardwareId: editingKey.hardwareId,
+          keyName: editingKey.keyName || '',
+          keyNfc: editingKey.keyNfc || '',
+          macAddress: editingKey.macAddress || '',
+          keySpec: editingKey.keySpec || '',
+          exStatus: editingKey.exStatus || '1',
+          exRemark: editingKey.exRemark || '',
+        });
+      } else {
+        form.resetFields();
+        form.setFieldsValue({
+          hardwareId: undefined,
+          keyName: '',
+          keyNfc: '',
+          macAddress: '',
+          keySpec: '',
+          exStatus: '1',
+          exRemark: '',
+        });
+      }
+    } else {
+      // 弹窗关闭时重置表单
+      form.resetFields();
+    }
+  }, [visible, editingKey, form]);
+
+  const handleSubmit = async () => {
+    try {
+      const values = await form.validateFields();
+      setFormLoading(true);
+      
+      const submitData: KeyVO = {
+        ...values,
+      };
+      
+      // 如果是编辑模式,添加 keyId
+      if (editingKey) {
+        submitData.keyId = editingKey.keyId || editingKey.id;
+        submitData.id = editingKey.keyId || editingKey.id;
+      }
+      
+      onSave(submitData);
+    } catch (error) {
+      // 表单验证失败
+    } finally {
+      setFormLoading(false);
+    }
+  };
+
+  return (
+    <Modal
+      title={editingKey ? '编辑钥匙' : '新增钥匙'}
+      open={visible}
+      onOk={handleSubmit}
+      onCancel={onCancel}
+      okText="确定"
+      cancelText="取消"
+      confirmLoading={formLoading}
+      width={600}
+    >
+      <Form
+        form={form}
+        layout="vertical"
+        className="mt-4"
+      >
+        <Form.Item
+          label="所属硬件"
+          name="hardwareId"
+        >
+          <Select
+            placeholder="请选择所属硬件"
+            allowClear
+            options={hardwareOptions}
+          />
+        </Form.Item>
+        
+        <Form.Item
+          label="钥匙名称"
+          name="keyName"
+          rules={[{ required: true, message: '请输入钥匙名称' }]}
+        >
+          <Input placeholder="请输入钥匙名称" />
+        </Form.Item>
+        
+        <Form.Item
+          label="钥匙NFC"
+          name="keyNfc"
+          rules={[{ required: true, message: '请输入钥匙NFC' }]}
+        >
+          <Input 
+            placeholder="请输入钥匙NFC" 
+            maxLength={16}
+          />
+        </Form.Item>
+        
+        <Form.Item
+          label="MAC地址"
+          name="macAddress"
+          rules={[{ required: true, message: '请输入MAC地址' }]}
+        >
+          <Input placeholder="请输入MAC地址" />
+        </Form.Item>
+        
+        <Form.Item
+          label="钥匙型号"
+          name="keySpec"
+        >
+          <Input placeholder="请输入钥匙型号" />
+        </Form.Item>
+        
+        <Form.Item
+          label="状态"
+          name="exStatus"
+        >
+          <Radio.Group>
+            {statusOptions.map(option => (
+              <Radio key={option.value} value={option.value}>
+                {option.label}
+              </Radio>
+            ))}
+          </Radio.Group>
+        </Form.Item>
+        
+        <Form.Item
+          label="备注"
+          name="exRemark"
+        >
+          <Input.TextArea placeholder="请输入备注" rows={3} />
+        </Form.Item>
+      </Form>
+    </Modal>
+  );
+}
+

+ 9 - 0
src/utils/dict.ts

@@ -77,6 +77,14 @@ export const getDictOptions = (dictType: string): DictDataType[] => {
     ];
   }
 
+  // 钥匙状态
+  if (dictType === DICT_TYPE.KEY_STATUS) {
+    return [
+      { dictType, label: '禁用', value: '0', colorType: 'danger', cssClass: '' },
+      { dictType, label: '启用', value: '1', colorType: 'success', cssClass: '' },
+    ];
+  }
+
   // 其他类型返回空数组
   // 实际项目中可以在这里调用 API 获取字典数据
   return [];
@@ -209,5 +217,6 @@ export enum DICT_TYPE {
   
   // ========== ISCS 模块 ==========
   POWER_TYPE = 'power_type', // 能量源
+  KEY_STATUS = 'key_status', // 钥匙状态
 }