2025-03-12 08:23:33 +08:00
|
|
|
import { MagnifyingGlassIcon } from "@heroicons/react/24/outline";
|
|
|
|
import { api, useStaff } from "@nice/client";
|
|
|
|
import { Button, Form, Input, Modal, Select, Table } from "antd";
|
|
|
|
import { StaffDto } from "@nice/common";
|
2025-03-12 09:48:35 +08:00
|
|
|
import { useCallback, useEffect, useState } from "react";
|
2025-03-12 08:23:33 +08:00
|
|
|
import toast from "react-hot-toast";
|
2025-03-12 09:48:35 +08:00
|
|
|
import _ from "lodash";
|
2025-03-12 08:23:33 +08:00
|
|
|
|
|
|
|
export default function StaffMessage() {
|
|
|
|
const initialValues = {
|
|
|
|
username: "",
|
|
|
|
deptId: "",
|
|
|
|
absent: "是",
|
2025-03-12 09:48:35 +08:00
|
|
|
position: "",
|
|
|
|
trainSituation: [{"": ""}],
|
2025-03-12 08:23:33 +08:00
|
|
|
};
|
|
|
|
const { create, update } = useStaff();
|
|
|
|
const [searchName, setSearchName] = useState("");
|
|
|
|
const { data: staffs, isLoading } = api.staff.findMany.useQuery({
|
|
|
|
where: {
|
|
|
|
username: {
|
|
|
|
contains: searchName
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
console.log(staffs);
|
|
|
|
const [form] = Form.useForm();
|
|
|
|
const [visible, setVisible] = useState(false);
|
|
|
|
const [editingRecord, setEditingRecord] = useState<StaffDto | null>(null);
|
|
|
|
const colnums = [
|
|
|
|
{
|
|
|
|
title: "姓名",
|
|
|
|
dataIndex: "username",
|
|
|
|
key: "username",
|
|
|
|
},
|
|
|
|
{
|
|
|
|
title: "部门",
|
|
|
|
dataIndex: "deptId",
|
|
|
|
key: "deptId",
|
|
|
|
},
|
2025-03-12 09:48:35 +08:00
|
|
|
{
|
|
|
|
title: "职务",
|
|
|
|
dataIndex: "position",
|
|
|
|
key: "position",
|
|
|
|
},
|
2025-03-12 08:23:33 +08:00
|
|
|
{
|
|
|
|
title: "在位",
|
|
|
|
dataIndex: "absent",
|
|
|
|
key: "absent",
|
|
|
|
render: (_, record) => (
|
|
|
|
<Select
|
|
|
|
// defaultValue={record.absent ? "是" : "否"}
|
|
|
|
style={{ width: 120 }}
|
|
|
|
onChange={async (value) => {
|
|
|
|
try {
|
|
|
|
await update.mutateAsync({
|
|
|
|
where: { id: record.id },
|
|
|
|
data: { absent: value }
|
|
|
|
});
|
|
|
|
toast.success("状态更新成功");
|
|
|
|
} catch (error) {
|
|
|
|
toast.error("状态更新失败");
|
|
|
|
}
|
|
|
|
}}
|
|
|
|
>
|
|
|
|
<Select.Option value="是">是</Select.Option>
|
|
|
|
<Select.Option value="否">否</Select.Option>
|
|
|
|
</Select>
|
|
|
|
)
|
|
|
|
},
|
2025-03-12 09:48:35 +08:00
|
|
|
{
|
|
|
|
title: "应时",
|
|
|
|
dataIndex: "time",
|
|
|
|
key: "time",
|
|
|
|
},
|
2025-03-12 08:23:33 +08:00
|
|
|
{
|
|
|
|
title: "操作",
|
|
|
|
key: "action",
|
|
|
|
render: (_, record) => (
|
|
|
|
<Button
|
|
|
|
type="primary"
|
|
|
|
key={record.id}
|
|
|
|
onClick={() => handleEdit(record)}>编辑</Button>
|
|
|
|
),
|
|
|
|
}
|
|
|
|
];
|
|
|
|
|
|
|
|
const handleNew = () => {
|
|
|
|
form.setFieldsValue(initialValues);
|
|
|
|
setVisible(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
|
|
if (editingRecord) {
|
|
|
|
form.setFieldsValue(editingRecord);
|
|
|
|
console.log(editingRecord);
|
|
|
|
}
|
|
|
|
}, [editingRecord]);
|
|
|
|
|
|
|
|
const handleEdit = (record) => {
|
|
|
|
setEditingRecord(record);
|
|
|
|
form.setFieldsValue(editingRecord);
|
|
|
|
setVisible(true);
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleOk = async () => {
|
|
|
|
const values = await form.getFieldsValue();
|
|
|
|
console.log(values.username);
|
|
|
|
try {
|
|
|
|
if (editingRecord && editingRecord.id) {
|
|
|
|
// console.log(editingRecord);
|
|
|
|
const result = await update.mutateAsync(
|
|
|
|
{
|
|
|
|
where: {
|
|
|
|
id: editingRecord.id,
|
|
|
|
},
|
2025-03-12 09:48:35 +08:00
|
|
|
data : {
|
2025-03-12 08:23:33 +08:00
|
|
|
username: values.username,
|
|
|
|
deptId: values.deptId,
|
2025-03-12 09:48:35 +08:00
|
|
|
position: values.position,
|
|
|
|
absent: values.absent,
|
|
|
|
trainSituations: values.trainSituations ? {
|
|
|
|
upsert: values.trainSituations.map((situation) => ({
|
|
|
|
where: { id: situation.id || "" },
|
|
|
|
update: {
|
|
|
|
mustTrainTime: situation.mustTrainTime,
|
|
|
|
trainContent: { connect: { id: situation.trainContentId } },
|
|
|
|
// 其他字段...
|
|
|
|
},
|
|
|
|
create: {
|
|
|
|
trainContent: { connect: { id: situation.trainContentId } },
|
|
|
|
mustTrainTime: situation.mustTrainTime,
|
|
|
|
staffId: editingRecord.id,
|
|
|
|
// 其他必填字段...
|
|
|
|
}
|
|
|
|
}))
|
|
|
|
} : undefined,
|
2025-03-12 08:23:33 +08:00
|
|
|
updatedAt: new Date()
|
2025-03-12 09:48:35 +08:00
|
|
|
} as any
|
2025-03-12 08:23:33 +08:00
|
|
|
}
|
|
|
|
);
|
|
|
|
// console.log(result);
|
|
|
|
} else {
|
|
|
|
await create.mutateAsync(
|
|
|
|
{
|
|
|
|
data: {
|
|
|
|
username: values.username,
|
|
|
|
deptId: values.deptId,
|
|
|
|
createdAt: new Date(),
|
|
|
|
showname: values.username,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
toast.success("保存成功");
|
|
|
|
setVisible(false);
|
|
|
|
} catch (error) {
|
|
|
|
toast.error("保存失败");
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const handleCancel = () => {
|
|
|
|
setVisible(false);
|
|
|
|
};
|
|
|
|
|
2025-03-12 09:48:35 +08:00
|
|
|
// 添加防抖的搜索处理函数
|
|
|
|
const handleSearch = useCallback(
|
|
|
|
_.debounce((value: string) => {
|
|
|
|
setSearchName(value);
|
|
|
|
}, 500),
|
|
|
|
[]
|
|
|
|
);
|
2025-03-12 08:23:33 +08:00
|
|
|
|
|
|
|
return (
|
|
|
|
<div className="p-2 min-h-screen bg-gradient-to-br">
|
|
|
|
<Form>
|
|
|
|
<div className="p-4 h-full flex flex-col"> {/* 修改为flex布局 */}
|
|
|
|
<div className="max-w-full mx-auto flex-1 flex flex-col"> {/* 添加flex容器 */}
|
|
|
|
{/* 头部区域保持不变... */}
|
|
|
|
<div className="flex justify-between mb-4 space-x-4 items-center">
|
2025-03-12 09:48:35 +08:00
|
|
|
<div className="text-2xl">人员总览</div>
|
2025-03-12 08:23:33 +08:00
|
|
|
<div className="relative w-1/3">
|
|
|
|
<Input
|
|
|
|
placeholder="输入姓名搜索"
|
2025-03-12 09:48:35 +08:00
|
|
|
onChange={(e) => handleSearch(e.target.value)}
|
2025-03-12 08:23:33 +08:00
|
|
|
className="pl-10 w-full border"
|
|
|
|
/>
|
|
|
|
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 " />
|
|
|
|
</div>
|
|
|
|
<Button
|
|
|
|
type="primary"
|
|
|
|
onClick={handleNew}
|
|
|
|
className=" font-bold py-2 px-4 rounded"
|
|
|
|
>
|
|
|
|
新建人员
|
|
|
|
</Button>
|
|
|
|
</div>
|
|
|
|
{/* 表格容器增加flex布局 */}
|
|
|
|
{isLoading ? (
|
|
|
|
<div className="flex justify-center mt-4">
|
|
|
|
<div className="animate-spin rounded-full h-10 w-10 border-4"></div>
|
|
|
|
</div>
|
|
|
|
) : (
|
|
|
|
<div className="flex-1 min-h-[calc(100vh-200px)]">
|
|
|
|
<Table
|
|
|
|
key={"username"}
|
|
|
|
columns={colnums}
|
|
|
|
dataSource={staffs}
|
2025-03-12 09:48:35 +08:00
|
|
|
className=" backdrop-blur-sm border-2
|
2025-03-12 08:23:33 +08:00
|
|
|
[&_.ant-table-tbody>tr>td]:!text-lg
|
|
|
|
[&_.ant-table-tbody>tr>td]:!py-5
|
|
|
|
[&_.ant-table-thead>tr>th]:!text-lg
|
|
|
|
[&_.ant-table-thead>tr>th]:!py-5
|
|
|
|
h-full"
|
|
|
|
tableLayout="fixed"
|
|
|
|
pagination={{
|
|
|
|
position: ["bottomCenter"],
|
2025-03-12 09:48:35 +08:00
|
|
|
pageSize: 12,
|
2025-03-12 08:23:33 +08:00
|
|
|
}}
|
2025-03-12 09:48:35 +08:00
|
|
|
// onRow={(record) => ({
|
|
|
|
// className: "hover:bg-gray-800/50 transition-colors even:bg-gray-800/50 hover:shadow-lg hover:shadow-blue-500/20",
|
|
|
|
// })}
|
2025-03-12 08:23:33 +08:00
|
|
|
>
|
|
|
|
<thead className="">
|
|
|
|
<tr>
|
|
|
|
{colnums.map((column) => (
|
|
|
|
<th
|
|
|
|
key={column.key}
|
|
|
|
className="px-4 border-b-2" // 简化样式
|
|
|
|
>
|
|
|
|
{column.title}
|
|
|
|
</th>
|
|
|
|
))}
|
|
|
|
</tr>
|
|
|
|
</thead>
|
|
|
|
<tbody> {/* 移除原有text-gray-300 */}
|
|
|
|
{staffs?.map((record) => (
|
|
|
|
<tr
|
|
|
|
key={record.id}
|
|
|
|
className="border-b border-blue-400/10"
|
|
|
|
>
|
|
|
|
{colnums.map((column) => (
|
|
|
|
<td
|
|
|
|
key={column.key}
|
|
|
|
className="px-4 border" // 简化样式
|
|
|
|
>
|
|
|
|
{column.render?.(record[column.dataIndex], record) || record[column.dataIndex]}
|
|
|
|
</td>
|
|
|
|
))}
|
|
|
|
</tr>
|
|
|
|
))}
|
|
|
|
</tbody>
|
|
|
|
</Table>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{/* 模态框样式更新 */}
|
|
|
|
<Modal
|
|
|
|
title="编辑员工信息"
|
|
|
|
visible={visible}
|
|
|
|
onOk={handleOk}
|
|
|
|
onCancel={handleCancel}
|
|
|
|
>
|
|
|
|
<Form
|
|
|
|
form={form}
|
|
|
|
initialValues={initialValues}
|
|
|
|
>
|
|
|
|
<Form.Item
|
|
|
|
name={"username"}
|
|
|
|
label="姓名"
|
|
|
|
// labelClassName="text-gray-300"
|
|
|
|
>
|
|
|
|
<Input className="rounded-lg" />
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
2025-03-12 09:48:35 +08:00
|
|
|
name={"deptId"}
|
|
|
|
label="部门"
|
2025-03-12 08:23:33 +08:00
|
|
|
// labelClassName="text-gray-300"
|
|
|
|
>
|
|
|
|
<Input className=" rounded-lg" />
|
|
|
|
</Form.Item>
|
2025-03-12 09:48:35 +08:00
|
|
|
<Form.List name="trainSituations">
|
|
|
|
{(fields, { add, remove }) => (
|
|
|
|
<>
|
|
|
|
{fields.map(({ key, name, ...restField }) => (
|
|
|
|
<div key={key} className="flex space-x-2">
|
|
|
|
<Form.Item
|
|
|
|
{...restField}
|
|
|
|
name={[name, 'trainContentId']}
|
|
|
|
label="培训内容"
|
|
|
|
className="flex-1"
|
|
|
|
>
|
|
|
|
<Select placeholder="选择培训内容">
|
|
|
|
{/* 这里需要从后端获取培训内容选项 */}
|
|
|
|
</Select>
|
|
|
|
</Form.Item>
|
|
|
|
<Form.Item
|
|
|
|
{...restField}
|
|
|
|
name={[name, 'mustTrainTime']}
|
|
|
|
label="需训时长"
|
|
|
|
className="flex-1"
|
|
|
|
>
|
|
|
|
<Input type="number" />
|
|
|
|
</Form.Item>
|
|
|
|
<Button onClick={() => remove(name)}>删除</Button>
|
|
|
|
</div>
|
|
|
|
))}
|
|
|
|
<Button onClick={() => add()}>添加培训</Button>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</Form.List>
|
2025-03-12 08:23:33 +08:00
|
|
|
</Form>
|
|
|
|
</Modal>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Form>
|
|
|
|
</div>
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|