diff --git a/apps/web/src/app/main/Test/Page.tsx b/apps/web/src/app/main/Test/Page.tsx index a5fd766..fc543a4 100755 --- a/apps/web/src/app/main/Test/Page.tsx +++ b/apps/web/src/app/main/Test/Page.tsx @@ -114,26 +114,31 @@ const TestPage: React.FC = () => { }); } } catch (error) { - message.error('操作失败:' + error.message); + message.error('操作失败:' + (error as Error).message); } }; + const handleImportSuccess = () => { + refetch(); + message.success('数据已更新'); + }; + // 初始加载 useEffect(() => { refetch(); }, []); return ( -
+
{/* 修改这里,使用 h-screen 而不是 h-full */}

培训情况记录

- - { onEdit={handleEdit} onDelete={handleDelete} /> - void; + data: Staff[]; +} + +export const ImportExportButtons: React.FC = ({ + onImportSuccess, + data, +}) => { + const createMutation = api.staff.create.useMutation({ + onSuccess: () => { + // 成功时不显示单条消息,让最终统计来显示 + }, + onError: (error) => { + // 只有当不是用户名重复错误时才显示错误信息 + if (!error.message.includes('Unique constraint failed')) { + message.error('导入失败: ' + error.message); + } + } + }); + + // 添加恢复记录的 mutation + const restoreMutation = api.staff.update.useMutation({ + onSuccess: () => { + // 静默成功,不显示消息 + }, + onError: (error) => { + console.error('恢复记录失败:', error); + } + }); + + const handleImport = async (file: File) => { + try { + const reader = new FileReader(); + reader.onload = async (e) => { + const workbook = XLSX.read(e.target?.result, { type: 'binary' }); + const sheetName = workbook.SheetNames[0]; + const worksheet = workbook.Sheets[sheetName]; + const jsonData = XLSX.utils.sheet_to_json(worksheet); + + let successCount = 0; + let restoredCount = 0; + let errorCount = 0; + + // 转换数据格式 + const staffData = jsonData.map((row: any) => ({ + username: row['用户名'], + showname: row['名称'], + absent: row['是否在位'] === '在位', + })); + + // 批量处理 + for (const staff of staffData) { + try { + // 先尝试创建新记录 + await createMutation.mutateAsync({ + data: staff + }); + successCount++; + } catch (error: any) { + // 如果是用户名重复错误 + if (error.message.includes('Unique constraint failed')) { + try { + // 尝试恢复已删除的记录 + await restoreMutation.mutateAsync({ + where: { + username: staff.username, + }, + data: { + deletedAt: null, + showname: staff.showname, + absent: staff.absent, + } + }); + restoredCount++; + } catch (restoreError) { + errorCount++; + console.error('恢复记录失败:', restoreError); + } + } else { + errorCount++; + console.error('创建记录失败:', error); + } + } + } + + // 显示导入结果 + if (successCount > 0 || restoredCount > 0) { + let successMessage = []; + if (successCount > 0) { + successMessage.push(`新增 ${successCount} 条`); + } + if (restoredCount > 0) { + successMessage.push(`恢复 ${restoredCount} 条`); + } + message.success(`导入完成:${successMessage.join(',')}`); + onImportSuccess(); + } + if (errorCount > 0) { + message.warning(`${errorCount} 条记录导入失败`); + } + }; + reader.readAsBinaryString(file); + } catch (error) { + message.error('文件读取失败'); + } + return false; + }; + + const handleExport = () => { + try { + const exportData = data.map(staff => ({ + '用户名': staff.username, + '名称': staff.showname, + '是否在位': staff.absent ? '是' : '否' + })); + + const worksheet = XLSX.utils.json_to_sheet(exportData); + const workbook = XLSX.utils.book_new(); + XLSX.utils.book_append_sheet(workbook, worksheet, '员工列表'); + + XLSX.writeFile(workbook, `员工列表_${new Date().toLocaleDateString()}.xlsx`); + message.success('导出成功'); + } catch (error) { + message.error('导出失败: ' + (error as Error).message); + } + }; + + return ( +
+ + + + +
+ ); +}; \ No newline at end of file diff --git a/apps/web/src/app/main/Test/components/SearchBar.tsx b/apps/web/src/app/main/Test/components/SearchBar.tsx index 5ec8502..9f1272d 100644 --- a/apps/web/src/app/main/Test/components/SearchBar.tsx +++ b/apps/web/src/app/main/Test/components/SearchBar.tsx @@ -1,12 +1,22 @@ import { Button, Input, Space } from 'antd'; import { SearchOutlined, PlusOutlined } from '@ant-design/icons'; import React from 'react'; +import { ImportExportButtons } from './ImportExportButtons'; + +// Define or import the Staff type +interface Staff { + id: number; + name: string; + username: string; +} interface SearchBarProps { searchText: string; onSearchTextChange: (text: string) => void; onSearch: () => void; onAdd: () => void; + onImportSuccess: () => void; + data: Staff[]; } export const SearchBar: React.FC = ({ @@ -14,22 +24,30 @@ export const SearchBar: React.FC = ({ onSearchTextChange, onSearch, onAdd, + onImportSuccess, + data, }) => { return (
- - onSearchTextChange(e.target.value)} - onPressEnter={onSearch} - prefix={} - /> - - - -
+ + onSearchTextChange(e.target.value)} + onPressEnter={onSearch} + prefix={} + /> + + + + + + +
); }; \ No newline at end of file diff --git a/apps/web/src/app/main/Test/components/StaffTable.tsx b/apps/web/src/app/main/Test/components/StaffTable.tsx index b8e95ea..3fca15f 100644 --- a/apps/web/src/app/main/Test/components/StaffTable.tsx +++ b/apps/web/src/app/main/Test/components/StaffTable.tsx @@ -3,6 +3,7 @@ import { EditOutlined, DeleteOutlined } from '@ant-design/icons'; import React, { useState } from 'react'; import { Staff } from './types'; + interface StaffTableProps { data: Staff[]; total: number; @@ -82,15 +83,35 @@ export const StaffTable: React.FC = ({ }, ]; + // 修改样式定义 + const containerStyle = { + display: 'flex', + flexDirection: 'column' as const, + height: '100%' + }; + + const tableContainerStyle = { + flex: 1, + height: 'calc(100vh - 240px)', // 减去头部、搜索栏和分页的高度 + overflow: 'hidden' + }; + + const tableStyle = { + height: '100%' + }; + return ( -
- +
+
+
+
= ({ )} showSizeChanger={false} /> -
{/* 分隔线 */} +
= ({ className="text-center" />