This commit is contained in:
Your Name 2025-05-29 10:39:39 +08:00
parent 944fc48568
commit fbbc919ca4
7 changed files with 576 additions and 191 deletions

View File

@ -1,34 +1,21 @@
import { api } from '@nice/client';
import { Button, Input, Pagination, Space, Modal, Form, message, Switch } from 'antd';
import { SearchOutlined, EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import React, { useState, useRef, useEffect } from 'react';
// 定义 Staff 接口
interface Staff {
id: string;
username: string;
showname: string;
absent: boolean;
// trainSituations: TrainSituation[];
}
interface PaginatedResponse {
items: Staff[];
total: number;
}
import { message, Form, Modal } from 'antd';
import React, { useState, useEffect } from 'react';
import { SearchBar } from './components/SearchBar';
import { StaffTable } from './components/StaffTable';
import { StaffModal } from './components/StaffModal';
import { Staff, PaginatedResponse } from './components/types';
const TestPage: React.FC = () => {
const [currentPage, setCurrentPage] = useState(1);
// 状态定义
const [searchText, setSearchText] = useState('');
const [currentPage, setCurrentPage] = useState(1);
const [isModalVisible, setIsModalVisible] = useState(false);
const [editingStaff, setEditingStaff] = useState<Staff | null>(null);
const [form] = Form.useForm();
const pageSize = 10;
// 添加 useRef 处理查询
const searchRef = useRef(false);
// 优化查询逻辑
// API 调用
const { data, isLoading, refetch } = api.staff.findManyWithPagination.useQuery<PaginatedResponse>({
page: currentPage,
pageSize: pageSize,
@ -45,16 +32,12 @@ const TestPage: React.FC = () => {
showname: true,
absent: true,
}
}, {
enabled: searchRef.current, // 控制查询启用
refetchOnWindowFocus: false,
keepPreviousData: true, // 保持之前的数据直到新数据加载完成
});
// 删除方法
const deleteMutation = api.staff.softDeleteByIds.useMutation({
onSuccess: () => {
message.success('删除成功');
message.success('删除成功');
refetch();
},
onError: (error) => {
@ -80,190 +63,95 @@ message.success('删除成功');
}
});
// 处理函数
const handleSearch = () => {
setCurrentPage(1);
searchRef.current = true;
refetch();
};
// 修改分页处理
const handlePageChange = (page: number) => {
setCurrentPage(page);
searchRef.current = true;
refetch();
};
// 添加数据监听
useEffect(() => {
if (data && searchRef.current) {
searchRef.current = false;
}
}, [data]);
// 处理删除的函数
const handleDelete = async (id: string) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这条记录吗?',
onOk: async () => {
try {
await deleteMutation.mutateAsync({
ids: [id],
data: {} // 添加空的 data 对象
});
} catch (error) {
console.error('删除失败:', error);
}
}
});
};
const handleEdit = (staff: Staff) => {
setEditingStaff(staff);
form.setFieldsValue(staff);
setIsModalVisible(true);
};
const handleAdd = () => {
setEditingStaff(null);
form.resetFields();
setIsModalVisible(true);
};
const handleModalOk = () => {
form.validateFields().then(values => {
const handleEdit = (staff: Staff) => {
setEditingStaff(staff);
form.setFieldsValue(staff);
setIsModalVisible(true);
};
const handleDelete = (id: string) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这条记录吗?',
onOk: async () => {
try {
await deleteMutation.mutateAsync({ ids: [id] });
} catch (error) {
console.error('删除失败:', error);
}
}
});
};
const handlePageChange = (page: number) => {
setCurrentPage(page);
refetch(); // 确保切换页面时重新获取数据
};
const handleModalOk = async () => {
try {
const values = await form.validateFields();
if (editingStaff) {
updateMutation.mutate({
await updateMutation.mutateAsync({
where: { id: editingStaff.id },
data: values
});
} else {
createMutation.mutate({
await createMutation.mutateAsync({
data: values
});
}
});
} catch (error) {
message.error('操作失败:' + error.message);
}
};
// 初始加载
useEffect(() => {
refetch();
}, []);
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4 text-center"></h1>
{/* 搜索和添加按钮 */}
<div className="mb-4 flex justify-between">
<Space>
<Input
placeholder="搜索员工姓名或用户名"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onPressEnter={handleSearch}
prefix={<SearchOutlined />}
/>
<Button type="primary" onClick={handleSearch}></Button>
</Space>
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
</Button>
</div>
<SearchBar
searchText={searchText}
onSearchTextChange={setSearchText}
onSearch={handleSearch}
onAdd={handleAdd}
/>
{/* 修改表格,添加操作列 */}
<div className="overflow-x-auto">
<table className="min-w-full bg-white shadow-md rounded-lg">
<thead>
<tr className="bg-gray-100 border-b">
<th className="px-6 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider"></th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider"></th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider"></th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider">
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{(data?.items || []).map((staff) => (
<tr key={staff.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-center">
{staff.username}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
{staff.showname}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
{staff.absent ? '在位' : '不在位'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
<Space>
<Button
type="primary"
icon={<EditOutlined />}
onClick={() => handleEdit(staff)}
size="small"
>
</Button>
<Button
type="primary"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(staff.id)}
size="small"
>
</Button>
</Space>
</td>
</tr>
))}
</tbody>
</table>
</div>
<StaffTable
data={data?.items || []}
total={data?.total || 0}
currentPage={currentPage}
pageSize={pageSize}
isLoading={isLoading}
onPageChange={handlePageChange}
onEdit={handleEdit}
onDelete={handleDelete}
/>
{/* 编辑/添加模态框 */}
<Modal
title={editingStaff ? "编辑员工" : "添加员工"}
open={isModalVisible}
<StaffModal
visible={isModalVisible}
editingStaff={editingStaff}
form={form}
onOk={handleModalOk}
onCancel={() => setIsModalVisible(false)}
>
<Form form={form} layout="vertical">
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input />
</Form.Item>
<Form.Item
name="showname"
label="名称"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input />
</Form.Item>
<Form.Item
name="absent"
label="是否在位"
valuePropName="checked"
>
<Switch
checkedChildren="在位"
unCheckedChildren="不在位"
/>
</Form.Item>
</Form>
</Modal>
<div className="mt-4 flex justify-center">
<Pagination
current={currentPage}
total={data?.total || 0}
pageSize={pageSize}
onChange={handlePageChange}
showTotal={(total) => `${total} 条记录`}
showSizeChanger={false}
showQuickJumper
/>
</div>
/>
</div>
);
};

View File

@ -0,0 +1,35 @@
import { Button, Input, Space } from 'antd';
import { SearchOutlined, PlusOutlined } from '@ant-design/icons';
import React from 'react';
interface SearchBarProps {
searchText: string;
onSearchTextChange: (text: string) => void;
onSearch: () => void;
onAdd: () => void;
}
export const SearchBar: React.FC<SearchBarProps> = ({
searchText,
onSearchTextChange,
onSearch,
onAdd,
}) => {
return (
<div className="mb-4 flex justify-between">
<Space>
<Input
placeholder="搜索员工姓名或用户名"
value={searchText}
onChange={(e) => onSearchTextChange(e.target.value)}
onPressEnter={onSearch}
prefix={<SearchOutlined />}
/>
<Button type="primary" onClick={onSearch}></Button>
</Space>
<Button type="primary" icon={<PlusOutlined />} onClick={onAdd}>
</Button>
</div>
);
};

View File

@ -0,0 +1,55 @@
import { Modal, Form, Input, Switch } from 'antd';
import React from 'react';
import { Staff } from './types';
interface StaffModalProps {
visible: boolean;
editingStaff: Staff | null;
form: any;
onOk: () => void;
onCancel: () => void;
}
export const StaffModal: React.FC<StaffModalProps> = ({
visible,
editingStaff,
form,
onOk,
onCancel,
}) => {
return (
<Modal
title={editingStaff ? "编辑员工" : "添加员工"}
open={visible}
onOk={onOk}
onCancel={onCancel}
>
<Form form={form} layout="vertical">
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input />
</Form.Item>
<Form.Item
name="showname"
label="名称"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input />
</Form.Item>
<Form.Item
name="absent"
label="是否在位"
valuePropName="checked"
>
<Switch
checkedChildren="在位"
unCheckedChildren="不在位"
/>
</Form.Item>
</Form>
</Modal>
);
};

View File

@ -0,0 +1,131 @@
import { Button, Space, Table, Tag, Input, Pagination, message } from 'antd';
import { EditOutlined, DeleteOutlined } from '@ant-design/icons';
import React, { useState } from 'react';
import { Staff } from './types';
interface StaffTableProps {
data: Staff[];
total: number;
currentPage: number;
pageSize: number;
isLoading?: boolean;
onPageChange: (page: number) => void;
onEdit: (staff: Staff) => void;
onDelete: (id: string) => void;
}
export const StaffTable: React.FC<StaffTableProps> = ({
data,
total,
currentPage,
pageSize,
isLoading = false,
onPageChange,
onEdit,
onDelete,
}) => {
const [jumpPage, setJumpPage] = useState('');
const handleJumpPage = () => {
const page = parseInt(jumpPage);
if (!isNaN(page) && page > 0 && page <= Math.ceil(total / pageSize)) {
onPageChange(page);
setJumpPage('');
} else {
message.error('请输入有效的页码');
}
};
const columns = [
{
title: '用户名',
dataIndex: 'username',
key: 'username',
},
{
title: '名称',
dataIndex: 'showname',
key: 'showname',
},
{
title: '是否在位',
dataIndex: 'absent',
key: 'absent',
render: (absent: boolean) => (
<Tag color={absent ? 'success' : 'error'}>
{absent ? '在位' : '不在位'}
</Tag>
),
},
{
title: '操作',
key: 'action',
render: (_: any, record: Staff) => (
<Space size="middle">
<Button
type="link"
icon={<EditOutlined />}
onClick={() => onEdit(record)}
>
</Button>
<Button
type="link"
icon={<DeleteOutlined />}
danger
onClick={() => onDelete(record.id)}
>
</Button>
</Space>
),
},
];
return (
<div>
<Table
dataSource={data}
columns={columns}
rowKey="id"
pagination={false}
loading={isLoading}
/>
<div className="mt-6 flex justify-center items-center">
<div className="bg-white px-6 py-3 rounded-lg shadow-sm flex items-center space-x-6">
<Pagination
current={currentPage}
total={total}
pageSize={pageSize}
onChange={onPageChange}
showTotal={(total) => (
<span className="text-gray-600">
<span className="font-medium text-gray-900">{total}</span>
</span>
)}
showSizeChanger={false}
/>
<div className="h-6 w-px bg-gray-200" /> {/* 分隔线 */}
<div className="flex items-center space-x-2">
<Input
size="small"
style={{ width: 60 }}
value={jumpPage}
onChange={(e) => setJumpPage(e.target.value)}
onPressEnter={handleJumpPage}
placeholder="页码"
className="text-center"
/>
<Button
type="primary"
size="small"
onClick={handleJumpPage}
>
</Button>
</div>
</div>
</div>
</div>
);
};

View File

@ -0,0 +1,11 @@
export interface Staff {
id: string;
username: string;
showname: string;
absent: boolean;
}
export interface PaginatedResponse {
items: Staff[];
total: number;
}

View File

@ -0,0 +1,265 @@
import { api } from '@nice/client';
import { Button, Input, Pagination, Space, Modal, Form, message, Switch } from 'antd';
import { SearchOutlined, EditOutlined, DeleteOutlined, PlusOutlined } from '@ant-design/icons';
import React, { useState, useRef, useEffect } from 'react';
// 定义 Staff 接口
interface Staff {
id: string;
username: string;
showname: string;
absent: boolean;
// trainSituations: TrainSituation[];
}
interface PaginatedResponse {
items: Staff[];
total: number;
}
const TestPage: React.FC = () => {
const [currentPage, setCurrentPage] = useState(1);
const [searchText, setSearchText] = useState('');
const [isModalVisible, setIsModalVisible] = useState(false);
const [editingStaff, setEditingStaff] = useState<Staff | null>(null);
const [form] = Form.useForm();
const pageSize = 10;
// 修改查询逻辑
const { data, isLoading, refetch } = api.staff.findManyWithPagination.useQuery<PaginatedResponse>({
page: currentPage,
pageSize: pageSize,
where: {
deletedAt: null,
OR: searchText ? [
{ username: { contains: searchText } },
{ showname: { contains: searchText } }
] : undefined
},
select: {
id: true,
username: true,
showname: true,
absent: true,
}
}, {
// 移除 enabled 控制
refetchOnWindowFocus: false,
keepPreviousData: true,
});
// 删除方法
const deleteMutation = api.staff.softDeleteByIds.useMutation({
onSuccess: () => {
message.success('删除成功');
refetch();
},
onError: (error) => {
message.error('删除失败:' + error.message);
}
});
// 更新方法
const updateMutation = api.staff.update.useMutation({
onSuccess: () => {
message.success('更新成功');
setIsModalVisible(false);
refetch();
}
});
// 创建方法
const createMutation = api.staff.create.useMutation({
onSuccess: () => {
message.success('创建成功');
setIsModalVisible(false);
refetch();
}
});
// 修改搜索处理函数
const handleSearch = () => {
setCurrentPage(1);
refetch();
};
// 修改分页处理
const handlePageChange = (page: number) => {
setCurrentPage(page);
refetch();
};
useEffect(() => {
// 组件首次加载时执行查询
refetch();
}, []);
// 处理删除的函数
const handleDelete = async (id: string) => {
Modal.confirm({
title: '确认删除',
content: '确定要删除这条记录吗?',
onOk: async () => {
try {
await deleteMutation.mutateAsync({
ids: [id],
data: {} // 添加空的 data 对象
});
} catch (error) {
console.error('删除失败:', error);
}
}
});
};
const handleEdit = (staff: Staff) => {
setEditingStaff(staff);
form.setFieldsValue(staff);
setIsModalVisible(true);
};
const handleAdd = () => {
setEditingStaff(null);
form.resetFields();
setIsModalVisible(true);
};
const handleModalOk = () => {
form.validateFields().then(values => {
if (editingStaff) {
updateMutation.mutate({
where: { id: editingStaff.id },
data: values
});
} else {
createMutation.mutate({
data: values
});
}
});
};
return (
<div className="p-6">
<h1 className="text-2xl font-bold mb-4 text-center">培训情况记录</h1>
{/* 搜索和添加按钮 */}
<div className="mb-4 flex justify-between">
<Space>
<Input
placeholder="搜索员工姓名或用户名"
value={searchText}
onChange={(e) => setSearchText(e.target.value)}
onPressEnter={handleSearch}
prefix={<SearchOutlined />}
/>
<Button type="primary" onClick={handleSearch}>搜索</Button>
</Space>
<Button type="primary" icon={<PlusOutlined />} onClick={handleAdd}>
添加员工
</Button>
</div>
{/* 修改表格,添加操作列 */}
<div className="overflow-x-auto">
<table className="min-w-full bg-white shadow-md rounded-lg">
<thead>
<tr className="bg-gray-100 border-b">
<th className="px-6 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider">用户名</th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider">名称</th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider">是否在位</th>
<th className="px-6 py-3 text-center text-xs font-medium text-gray-600 uppercase tracking-wider">
操作
</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200">
{(data?.items || []).map((staff) => (
<tr key={staff.id} className="hover:bg-gray-50">
<td className="px-6 py-4 whitespace-nowrap text-center">
{staff.username}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
{staff.showname}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
{staff.absent ? '在位' : '不在位'}
</td>
<td className="px-6 py-4 whitespace-nowrap text-center">
<Space>
<Button
type="primary"
icon={<EditOutlined />}
onClick={() => handleEdit(staff)}
size="small"
>
编辑
</Button>
<Button
type="primary"
danger
icon={<DeleteOutlined />}
onClick={() => handleDelete(staff.id)}
size="small"
>
删除
</Button>
</Space>
</td>
</tr>
))}
</tbody>
</table>
</div>
{/* 编辑/添加模态框 */}
<Modal
title={editingStaff ? "编辑员工" : "添加员工"}
open={isModalVisible}
onOk={handleModalOk}
onCancel={() => setIsModalVisible(false)}
>
<Form form={form} layout="vertical">
<Form.Item
name="username"
label="用户名"
rules={[{ required: true, message: '请输入用户名' }]}
>
<Input />
</Form.Item>
<Form.Item
name="showname"
label="名称"
rules={[{ required: true, message: '请输入名称' }]}
>
<Input />
</Form.Item>
<Form.Item
name="absent"
label="是否在位"
valuePropName="checked"
>
<Switch
checkedChildren="在位"
unCheckedChildren="不在位"
/>
</Form.Item>
</Form>
</Modal>
<div className="mt-4 flex justify-center">
<Pagination
current={currentPage}
total={data?.total || 0}
pageSize={pageSize}
onChange={handlePageChange}
showTotal={(total) => `共 ${total} 条记录`}
showSizeChanger={false}
showQuickJumper
/>
</div>
</div>
);
};
export default TestPage;

View File

@ -2,7 +2,7 @@ server {
# 监听80端口
listen 80;
# 服务器域名/IP地址使用环境变量
server_name 192.168.217.194;
server_name 192.168.239.194;
# 基础性能优化配置
# 启用tcp_nopush以优化数据发送
@ -100,7 +100,7 @@ server {
# 仅供内部使用
internal;
# 代理到认证服务
proxy_pass http://192.168.217.194:3001/auth/file;
proxy_pass http://192.168.239.194:3001/auth/file;
# 请求优化:不传递请求体
proxy_pass_request_body off;