This commit is contained in:
linfeng 2025-03-12 11:45:18 +08:00
parent 2f31b1db29
commit 28aea56c12
5 changed files with 387 additions and 275 deletions

View File

@ -1,10 +1,12 @@
import { Outlet } from "react-router-dom";
import MainHeader from "./MainHeader";
import { Content } from "antd/es/layout/layout";
import { MainProvider } from "./MainProvider";
export default function MainLayout() {
return (
<>
<MainHeader />
<MainProvider>
<MainHeader />
</MainProvider>
</>
)

View File

@ -0,0 +1,65 @@
import { Prisma, StaffDto } from "@nice/common";
import React, {
createContext,
ReactNode,
useContext,
useMemo,
useState,
} from "react";
// import { useDebounce } from "use-debounce";
import { Form, FormInstance } from 'antd';
interface MainContextType {
searchValue?: string;
setSearchValue: React.Dispatch<React.SetStateAction<string>>;
formValue?: {[key: string]: any};
setFormValue: React.Dispatch<React.SetStateAction<{[key: string]: any}>>;
form: FormInstance; // 新增表单实例
visible: boolean;
setVisible: React.Dispatch<React.SetStateAction<boolean>>;
editingRecord?: StaffDto | null;
setEditingRecord: React.Dispatch<React.SetStateAction<StaffDto | null>>;
}
const MainContext = createContext<MainContextType | null>(null);
interface MainProviderProps {
children: ReactNode;
}
export function MainProvider({ children }: MainProviderProps) {
const [searchValue, setSearchValue] = useState<string>("");
const [formValue, setFormValue] = useState<{[key: string]: any}>({});
const [form] = Form.useForm(); // 添加AntD表单实例
const [visible, setVisible] = useState<boolean>(false);
const [editingRecord, setEditingRecord] = useState<StaffDto | null>(null);
return (
<MainContext.Provider
value={{
searchValue,
setSearchValue,
formValue,
setFormValue,
form, // 暴露表单实例
visible,
setVisible,
editingRecord,
setEditingRecord
}}>
<Form
form={form}
onValuesChange={(changed, all) => setFormValue(all)}
initialValues={formValue}
>
{children}
</Form>
</MainContext.Provider>
);
}
export const useMainContext = () => {
const context = useContext(MainContext);
if (!context) {
throw new Error("useMainContext must be used within MainProvider");
}
return context;
};

View File

@ -1,177 +1,38 @@
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";
import { StaffDto, trainSituationDetailSelect } from "@nice/common";
import { useCallback, useEffect, useState } from "react";
import toast from "react-hot-toast";
import _ from "lodash";
import { useMainContext } from "../layout/MainProvider";
import StaffTable from "./stafftable/page";
import StaffModal from "./staffmodal/page";
export default function StaffMessage() {
const initialValues = {
username: "",
deptId: "",
absent: "是",
position: "",
trainSituation: [{"": ""}],
};
const { create, update } = useStaff();
const [searchName, setSearchName] = useState("");
const { data: staffs, isLoading } = api.staff.findMany.useQuery({
where: {
username: {
contains: searchName
}
const {form, formValue, setFormValue, setVisible, setSearchValue} = useMainContext();
useEffect(()=>{
setFormValue(
{
username: "",
deptId: "",
absent: false,
position: "",
trainSituations: "",
}
});
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",
},
{
title: "职务",
dataIndex: "position",
key: "position",
},
{
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>
)
},
{
title: "应时",
dataIndex: "time",
key: "time",
},
{
title: "操作",
key: "action",
render: (_, record) => (
<Button
type="primary"
key={record.id}
onClick={() => handleEdit(record)}></Button>
),
}
];
)
},[])
const handleNew = () => {
form.setFieldsValue(initialValues);
form.setFieldsValue(formValue);
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,
},
data : {
username: values.username,
deptId: values.deptId,
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,
updatedAt: new Date()
} as any
}
);
// 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);
};
// 添加防抖的搜索处理函数
const handleSearch = useCallback(
_.debounce((value: string) => {
setSearchName(value);
setSearchValue(value);
}, 500),
[]
);
return (
<div className="p-2 min-h-screen bg-gradient-to-br">
<Form>
@ -196,122 +57,8 @@ export default function StaffMessage() {
</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}
className=" backdrop-blur-sm border-2
[&_.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"],
pageSize: 12,
}}
// onRow={(record) => ({
// className: "hover:bg-gray-800/50 transition-colors even:bg-gray-800/50 hover:shadow-lg hover:shadow-blue-500/20",
// })}
>
<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
name={"deptId"}
label="部门"
// labelClassName="text-gray-300"
>
<Input className=" rounded-lg" />
</Form.Item>
<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>
</Form>
</Modal>
<StaffTable></StaffTable>
<StaffModal></StaffModal>
</div>
</div>
</Form>

View File

@ -0,0 +1,141 @@
import { useStaff } from "@nice/client";
import { useMainContext } from "../../layout/MainProvider";
import toast from "react-hot-toast";
import { Button, Form, Input, Modal, Select } from "antd";
export default function StaffModal() {
const { form, formValue, setVisible, visible, editingRecord } = useMainContext()
const { create, update } = useStaff();
const handleOk = async () => {
const values = await form.getFieldsValue();
console.log(values.username);
try {
if (editingRecord && editingRecord.id) {
const result = await update.mutateAsync(
{
where: {
id: editingRecord.id,
},
data: {
username: values.username,
deptId: values.deptId,
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 } },
// 其他字段...
},
}))
} : undefined,
updatedAt: new Date()
} as any
}
);
// console.log(result);
} else {
await create.mutateAsync(
{
data: {
username: values.username,
deptId: values.deptId,
createdAt: new Date(),
showname: values.username,
absent: values.absent,
trainSituations: {
create: values.trainSituations.map((situation) => ({
trainContent: { connect: { id: situation.trainContentId } },
mustTrainTime: situation.mustTrainTime,
// 其他必填字段...
}))
}
}
}
);
}
toast.success("保存成功");
setVisible(false);
} catch (error) {
toast.error("保存失败");
throw error;
}
};
const handleCancel = () => {
setVisible(false);
};
return (
<>
{/* 模态框样式更新 */}
<Modal
title="编辑员工信息"
visible={visible}
onOk={handleOk}
onCancel={handleCancel}
>
<Form
form={form}
initialValues={formValue}
>
<Form.Item
name={"username"}
label="姓名"
// labelClassName="text-gray-300"
>
<Input className="rounded-lg" />
</Form.Item>
<Form.Item
name={"deptId"}
label="部门"
// labelClassName="text-gray-300"
>
<Input className=" rounded-lg" />
</Form.Item>
<Form.Item
name={"absent"}
label="在位"
>
<Select className="rounded-lg" >
<Select.Option value={true}></Select.Option>
<Select.Option value={false}></Select.Option>
</Select>
</Form.Item>
<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>
</Form>
</Modal>
</>
)
}

View File

@ -0,0 +1,157 @@
import { Button, Select, Table } from "antd"
import { StaffDto } from "@nice/common";
import { useStaff, api } from "@nice/client";
import { useEffect, useState } from "react";
import toast from "react-hot-toast";
import React from "react";
import { useMainContext } from "../../layout/MainProvider";
export default function StaffTable() {
const{form, setVisible,searchValue} = useMainContext()
const { data: staffs, isLoading } = api.staff.findMany.useQuery({
where: {
username: {
contains: searchValue
}
}
});
const { create, update } = useStaff();
const {editingRecord, setEditingRecord} = useMainContext();
const colnums = [
{
title: "姓名",
dataIndex: "username",
key: "username",
},
{
title: "部门",
dataIndex: "deptId",
key: "deptId",
},
{
title: "职务",
dataIndex: "position",
key: "position",
},
{
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>
)
},
{
title: "应时",
dataIndex: "trainSituation",
key: "trainSituation",
},
{
title: "操作",
key: "action",
render: (_, record) => (
<Button
type="primary"
key={record.id}
onClick={() => handleEdit(record)}></Button>
),
}
];
useEffect(() => {
if (editingRecord) {
form.setFieldsValue(editingRecord);
console.log(editingRecord);
}
}, [editingRecord]);
const handleEdit = (record) => {
setEditingRecord(record);
form.setFieldsValue(editingRecord);
setVisible(true);
};
return (
<>
{
isLoading ? (<div>...</div>) :
(
<Table
key={"username"}
columns={colnums}
dataSource={staffs}
className="table-auto w-full border-collapse border border-gray-300"
tableLayout="fixed"
pagination={{
position: ["bottomCenter"],
className: "flex justify-center mt-4",
pageSize: 12,
}}
onRow={(record) => ({
onMouseEnter: () => {
const row = document.querySelector(`tr[data-row-key="${record.id}"]`);
if (row) {
row.classList.add("bg-gray-100");
}
},
onMouseLeave: () => {
const row = document.querySelector(`tr[data-row-key="${record.id}"]`);
if (row) {
row.classList.remove("bg-gray-100");
}
},
})}
>
<thead className="bg-gray-200">
<tr>
{colnums.map((column) => (
<th
key={column.key}
className="px-4 py-2 border border-gray-300 text-center font-bold"
>
{column.title}
</th>
))}
</tr>
</thead>
<tbody>
{staffs?.map((record) => (
<tr
key={record.id}
className="border border-gray-300"
>
{colnums.map((column) => (
<td
key={column.key}
className="px-4 py-2 border border-gray-300 text-center"
>
{column.render?.(record[column.dataIndex], record) || record[column.dataIndex]}
</td>
))}
</tr>
))}
</tbody>
</Table>
)
}
</>
);
}