diff --git a/apps/server/src/models/staff/staff.module.ts b/apps/server/src/models/staff/staff.module.ts index fa681dc..835c5ee 100755 --- a/apps/server/src/models/staff/staff.module.ts +++ b/apps/server/src/models/staff/staff.module.ts @@ -1,15 +1,16 @@ import { Module } from '@nestjs/common'; import { StaffService } from './staff.service'; -import { StaffRouter } from './staff.router'; -import { TrpcService } from '@server/trpc/trpc.service'; -import { DepartmentModule } from '../department/department.module'; import { StaffController } from './staff.controller'; -import { StaffRowService } from './staff.row.service'; +import { DepartmentModule } from '../department/department.module'; +import { TrpcModule } from '../../trpc/trpc.module'; +import { StaffRouter } from './staff.router'; +import { StaffRowService } from './staff.row.service'; +import { TrpcService } from '@server/trpc/trpc.service'; @Module({ imports: [DepartmentModule], - providers: [StaffService, StaffRouter, TrpcService, StaffRowService], - exports: [StaffService, StaffRouter, StaffRowService], - controllers: [StaffController], + providers: [StaffService, StaffRouter, StaffRowService, TrpcService], + exports: [StaffService, StaffRouter], + controllers: [StaffController, ], }) -export class StaffModule { } +export class StaffModule {} diff --git a/apps/server/src/models/staff/staff.router.ts b/apps/server/src/models/staff/staff.router.ts index d93821e..609474a 100755 --- a/apps/server/src/models/staff/staff.router.ts +++ b/apps/server/src/models/staff/staff.router.ts @@ -95,5 +95,50 @@ export class StaffRouter { .query(async ({ input }) => { return await this.staffService.findUnique(input); }), + addCustomField: this.trpc.procedure + .input(z.object({ + name: z.string(), + label: z.string().optional(), + type: z.string(), // text, number, date, select 等 + required: z.boolean().optional(), + order: z.number().optional(), + options: z.any().optional(), // 对于选择类型字段的可选值 + group: z.string().optional(), // 字段分组 + })) + .mutation(({ input }) => { + return this.staffService.addCustomField(input as any); + }), + updateCustomField: this.trpc.procedure + .input(z.object({ + id: z.string(), + name: z.string().optional(), + label: z.string().optional(), + type: z.string().optional(), + required: z.boolean().optional(), + order: z.number().optional(), + options: z.any().optional(), + group: z.string().optional(), + })) + .mutation(({ input }) => { + return this.staffService.updateCustomField(input as any); + }), + deleteCustomField: this.trpc.procedure + .input(z.string()) + .mutation(({ input }) => { + return this.staffService.deleteCustomField(input as any); + }), + getCustomFields: this.trpc.procedure + .query(() => { + return this.staffService.getCustomFields(); + }), + setCustomFieldValue: this.trpc.procedure + .input(z.object({ + staffId: z.string(), + fieldId: z.string(), + value: z.string().optional(), + })) + .mutation(({ input }) => { + return this.staffService.setCustomFieldValue(input as any); + }), }); } diff --git a/apps/server/src/models/staff/staff.service.ts b/apps/server/src/models/staff/staff.service.ts index 57cddd2..c8d75d1 100755 --- a/apps/server/src/models/staff/staff.service.ts +++ b/apps/server/src/models/staff/staff.service.ts @@ -17,6 +17,7 @@ export class StaffService extends BaseService { constructor(private readonly departmentService: DepartmentService) { super(db, ObjectType.STAFF, true); } + /** * 获取某一单位下所有staff的记录 * @param deptId 单位的id @@ -36,30 +37,78 @@ export class StaffService extends BaseService { }); return result; } + async create(args: Prisma.StaffCreateArgs) { - const { data } = args; - await this.validateUniqueFields(data); - const createData = { - ...data, - password: await argon2.hash((data.password || '123456') as string), - }; - const result = await super.create({ ...args, data: createData }); - this.emitDataChangedEvent(result, CrudOperation.CREATED); - return result; + const { data, select } = args; + const { fieldValues, ...staffData } = data as any; + + // 创建员工基本信息 + const staff = await super.create({ + ...args, + data: { + ...staffData, + password: await argon2.hash((staffData.password || '123456') as string), + }, + }); + + // 如果有自定义字段值,创建它们 + if (fieldValues) { + await db.staffFieldValue.createMany({ + data: Object.entries(fieldValues).map(([fieldId, value]) => ({ + staffId: staff.id, + fieldId, + value: String(value), + })), + }); + } + + this.emitDataChangedEvent(staff, CrudOperation.CREATED); + return staff; } + async update(args: Prisma.StaffUpdateArgs) { const { data, where } = args; - await this.validateUniqueFields(data, where.id); - const updateData = { - ...data, - ...(data.password && { - password: await argon2.hash(data.password as string), - }), - }; - const result = await super.update({ ...args, data: updateData }); - this.emitDataChangedEvent(result, CrudOperation.UPDATED); - return result; + const { fieldValues, ...staffData } = data as any; + + // 更新员工基本信息 + const staff = await super.update({ + ...args, + data: { + ...staffData, + ...(staffData.password && { + password: await argon2.hash(staffData.password as string), + }), + }, + }); + + // 如果有自定义字段值,更新它们 + if (fieldValues) { + await Promise.all( + Object.entries(fieldValues).map(([fieldId, value]) => + db.staffFieldValue.upsert({ + where: { + staffId_fieldId: { + staffId: staff.id, + fieldId, + }, + }, + create: { + staffId: staff.id, + fieldId, + value: String(value), + }, + update: { + value: String(value), + }, + }), + ), + ); + } + + this.emitDataChangedEvent(staff, CrudOperation.UPDATED); + return staff; } + private async validateUniqueFields(data: any, excludeId?: string) { const uniqueFields = [ { @@ -119,72 +168,91 @@ export class StaffService extends BaseService { } } - // /** - // * 根据关键词或ID集合查找员工 - // * @param data 包含关键词、域ID和ID集合的对象 - // * @returns 匹配的员工记录列表 - // */ - // async findMany(data: z.infer) { - // const { keyword, domainId, ids, deptId, limit = 30 } = data; - // const idResults = ids - // ? await db.staff.findMany({ - // where: { - // id: { in: ids }, - // deletedAt: null, - // domainId, - // deptId, - // }, - // select: { - // id: true, - // showname: true, - // username: true, - // deptId: true, - // domainId: true, - // department: true, - // domain: true, - // }, - // }) - // : []; + async findUnique(args: Prisma.StaffFindUniqueArgs) { + const staff = await super.findUnique(args); + if (!staff) return null; - // const mainResults = await db.staff.findMany({ - // where: { - // deletedAt: null, - // domainId, - // deptId, - // OR: (keyword || ids) && [ - // { showname: { contains: keyword } }, - // { - // username: { - // contains: keyword, - // }, - // }, - // { phoneNumber: { contains: keyword } }, - // // { - // // id: { in: ids }, - // // }, - // ], - // }, - // select: { - // id: true, - // showname: true, - // username: true, - // deptId: true, - // domainId: true, - // department: true, - // domain: true, - // }, - // orderBy: { order: 'asc' }, - // take: limit !== -1 ? limit : undefined, - // }); - // // Combine results, ensuring no duplicates - // const combinedResults = [ - // ...mainResults, - // ...idResults.filter( - // (idResult) => - // !mainResults.some((mainResult) => mainResult.id === idResult.id), - // ), - // ]; + // 获取自定义字段值 + const fieldValues = await db.staffFieldValue.findMany({ + where: { staffId: staff.id }, + include: { field: true }, + }); - // return combinedResults; - // } + return { + ...staff, + fieldValues: fieldValues.reduce((acc, { field, value }) => ({ + ...acc, + [field.name]: value, + }), {}), + }; + } + + async addCustomField(data: { + name: string; + label?: string; + type: string; + required?: boolean; + order?: number; + options?: any; + group?: string; + }) { + return this.prisma.staffField.create({ + data: { + ...data, + }, + }); + } + + async updateCustomField(data: { + id: string; + name?: string; + label?: string; + type?: string; + required?: boolean; + order?: number; + options?: any; + group?: string; + }) { + const { id, ...updateData } = data; + return this.prisma.staffField.update({ + where: { id }, + data: updateData, + }); + } + + async deleteCustomField(id: string) { + return this.prisma.staffField.delete({ + where: { id }, + }); + } + + async getCustomFields() { + return this.prisma.staffField.findMany({ + orderBy: { order: 'asc' }, + }); + } + + async setCustomFieldValue(data: { + staffId: string; + fieldId: string; + value?: string; + }) { + const { staffId, fieldId, value } = data; + return this.prisma.staffFieldValue.upsert({ + where: { + staffId_fieldId: { + staffId, + fieldId, + } + }, + create: { + staffId, + fieldId, + value, + }, + update: { + value, + }, + }); + } } diff --git a/apps/server/src/trpc/trpc.router.ts b/apps/server/src/trpc/trpc.router.ts index dec98f9..959c61c 100755 --- a/apps/server/src/trpc/trpc.router.ts +++ b/apps/server/src/trpc/trpc.router.ts @@ -37,7 +37,7 @@ export class TrpcRouter { private readonly resource: ResourceRouter, private readonly trainContent: TrainContentRouter, private readonly trainSituation:TrainSituationRouter, - private readonly dailyTrain:DailyTrainRouter + private readonly dailyTrain:DailyTrainRouter, ) {} getRouter() { return; diff --git a/apps/web/src/app/admin/staffinfo-manage/defaultFieldInitializer.tsx b/apps/web/src/app/admin/staffinfo-manage/defaultFieldInitializer.tsx new file mode 100644 index 0000000..4a5745a --- /dev/null +++ b/apps/web/src/app/admin/staffinfo-manage/defaultFieldInitializer.tsx @@ -0,0 +1,80 @@ +import React, { useState } from 'react'; +import { message, Modal, Button } from 'antd'; +import { useStaff } from '@nice/client'; +import { defaultFields } from './defaultFields'; + +type DefaultFieldInitializerProps = { + fields: any[] | undefined; + isProcessing: boolean; + setIsProcessing: (isProcessing: boolean) => void; + isInitConfirmVisible: boolean; + setIsInitConfirmVisible: (isVisible: boolean) => void; +}; + +const checkFieldExists = (fields: any[] | undefined, name: string) => { + return fields?.some(field => field.name === name); +}; + +const DefaultFieldInitializer: React.FC = ({ + fields, + isProcessing, + setIsProcessing, + isInitConfirmVisible, + setIsInitConfirmVisible +}) => { + const { addCustomField } = useStaff(); + + const initializeDefaultFields = async () => { + try { + setIsProcessing(true); + + // 检查是否已有数据 + if (fields && fields.length > 0) { + message.warning('数据库中已存在字段数据,请先清空数据后再初始化'); + return; + } + // 使用 Promise.all 批量处理 + await Promise.all( + defaultFields.map(async (field) => { + try { + if (!checkFieldExists(fields, field.name)) { + await addCustomField.mutateAsync(field); + } + } catch (error) { + console.error(`添加字段 ${field.name} 失败:`, error); + } + }) + ); + + message.success('默认字段初始化成功'); + setIsInitConfirmVisible(false); + } catch (error) { + message.error('初始化失败:' + (error instanceof Error ? error.message : '未知错误')); + } finally { + setIsProcessing(false); + } + }; + + return ( + <> + + setIsInitConfirmVisible(false)} + confirmLoading={isProcessing} + > +

初始化将导入所有预设字段,确定要继续吗?

+
+ + ); +}; + +export default DefaultFieldInitializer; \ No newline at end of file diff --git a/apps/web/src/app/admin/staffinfo-manage/defaultFields.ts b/apps/web/src/app/admin/staffinfo-manage/defaultFields.ts new file mode 100644 index 0000000..db931e8 --- /dev/null +++ b/apps/web/src/app/admin/staffinfo-manage/defaultFields.ts @@ -0,0 +1,174 @@ +export const defaultFields = [ + // 基本信息组 + { name: 'username', label: '用户名', type: 'text',required: true, group: '基本信息', order: 1 }, + { name: 'showname', label: '显示名称', type: 'text', group: '基本信息', order: 2 }, + { name: 'idNumber', label: '身份证号', type: 'text', group: '基本信息', order: 3 }, + { name: 'officerId', label: '警号', type: 'text', group: '基本信息', order: 4 }, + { name: 'phoneNumber', label: '手机号', type: 'text', group: '基本信息', order: 5 }, + { name: 'age', label: '年龄', type: 'number', group: '基本信息', order: 6 }, + { name: 'sex', + label: '性别', + type: 'radio', + options: [ + { label: '男', value: 'male' }, + { label: '女', value: 'female' }, + ], + group: '基本信息', + order: 7 }, + { name: 'bloodType', + label: '血型', + type: 'select', + options: [ + { label: 'A', value: 'A' }, + { label: 'B', value: 'B' }, + { label: 'AB', value: 'AB' }, + { label: 'O', value: 'O' }, + ], + group: '基本信息', + order: 8 }, + { name: 'birthplace', label: '籍贯', type: 'text', group: '基本信息', order: 9 }, + { name: 'source', label: '来源', type: 'text', group: '基本信息', order: 10 }, + + // 政治信息组 + { name: 'politicalStatus', + label: '政治面貌', + type: 'select', + options: [ + { label: '中共党员', value: '中共党员' }, + { label: '中共预备党员', value: '中共预备党员' }, + { label: '共青团员', value: '共青团员' }, + { label: '群众', value: '群众' }, + ], + group: '政治信息', + order: 11 }, + { name: 'partyPosition', label: '党内职务', type: 'text', group: '政治信息', order: 12 }, + + // 职务信息组 + { name: 'rank', + label: '衔职级别', + type: 'select', + options: [ + { label: '', value: '' }, + { label: '', value: '' }, + ], + group: '职务信息', + order: 13 }, + { name: 'rankDate', label: '衔职时间', type: 'date', group: '职务信息', order: 14 }, + { name: 'proxyPosition', label: '代理职务', type: 'text', group: '职务信息', order: 15 }, + { name: 'post', label: '岗位', type: 'text', group: '职务信息', order: 16 }, + + // 入职信息组 + { name: 'hireDate', label: '入职时间', type: 'date', group: '入职信息', order: 17 }, + { name: 'seniority', label: '工龄认定时间', type: 'date', group: '入职信息', order: 18 }, + { name: 'sourceType', label: '来源类型', type: 'text', group: '入职信息', order: 19 }, + { name: 'isReentry', + label: '是否二次入职', + type: 'radio', + options: [ + { label: '是', value: '是' }, + { label: '否', value: '否' }, + ], + group: '入职信息', + order: 20 }, + { name: 'isExtended', + label: '是否延期服役', + type: 'radio', + options: [ + { label: '是', value: '是' }, + { label: '否', value: '否' }, + ], + group: '入职信息', + order: 21 }, + { name: 'currentPositionDate', label: '现岗位开始时间', type: 'date', group: '入职信息', order: 22 }, + + // 教育信息组 + { name: 'education', + label: '学历', + type: 'select', + options: [ + { label: '博士', value: '博士' }, + { label: '硕士', value: '硕士' }, + { label: '本科', value: '本科' }, + { label: '专科', value: '专科' }, + { label: '中专', value: '中专' }, + { label: '高中', value: '高中' }, + { label: '初中', value: '初中' }, + { label: '小学', value: '小学' }, + ], + group: '教育信息', + order: 23 }, + { name: 'educationType', + label: '学历形式', + type: 'select', + options: [ + { label: '全日制', value: '全日制' }, + { label: '非全日制', value: '非全日制' }, + ], + group: '教育信息', + order: 24 }, + { name: 'isGraduated', + label: '是否毕业', + type: 'radio', + options: [ + { label: '是', value: '是' }, + { label: '否', value: '否' }, + ], + group: '教育信息', + order: 25 }, + { name: 'major', label: '专业', type: 'text', group: '教育信息', order: 26 }, + { name: 'foreignLang', label: '外语能力', type: 'text', group: '教育信息', order: 27 }, + + // 培训信息组 + { name: 'trainType', label: '培训类型', type: 'text', group: '培训信息', order: 28 }, + { name: 'trainInstitute', label: '培训机构', type: 'text', group: '培训信息', order: 29 }, + { name: 'trainMajor', label: '培训专业', type: 'text', group: '培训信息', order: 30 }, + { name: 'hasTrain', + label: '是否参加培训', + type: 'radio', + options: [ + { label: '是', value: '是' }, + { label: '否', value: '否' }, + ], + group: '培训信息', + order: 31 }, + + // 鉴定信息组 + { name: 'certRank', label: '鉴定等级', type: 'text', group: '鉴定信息', order: 32 }, + { name: 'certWork', label: '鉴定工种', type: 'text', group: '鉴定信息', order: 33 }, + { name: 'hasCert', + label: '是否参加鉴定', + type: 'radio', + options: [ + { label: '是', value: '是' }, + { label: '否', value: '否' }, + ], + group: '鉴定信息', + order: 34 }, + + // 工作信息组 + { name: 'equipment', label: '操作维护装备', type: 'textarea', group: '工作信息', order: 35 }, + { name: 'projects', label: '演训任务经历', type: 'textarea', group: '工作信息', order: 36 }, + { name: 'awards', label: '奖励信息', type: 'textarea', group: '工作信息', order: 37 }, + { name: 'punishments', label: '处分信息', type: 'textarea', group: '工作信息', order: 38 }, + ]; + + export const FieldTypeOptions = [ + { label: '文本', value: 'text' }, + { label: '数字', value: 'number' }, + { label: '日期', value: 'date' }, + { label: '选择', value: 'select' }, + { label: '单选', value: 'radio' }, + { label: '多行文本', value: 'textarea' }, + ]; + + export const GroupOptions = [ + { label: '个人基本信息', value: '个人基本信息' }, + { label: '政治信息', value: '政治信息' }, + { label: '教育背景', value: '教育背景' }, + { label: '职务信息', value: '职务信息' }, + { label: '入职信息', value: '入职信息' }, + { label: '培训信息', value: '培训信息' }, + { label: '鉴定信息', value: '鉴定信息' }, + { label: '工作信息', value: '工作信息' }, + { label: '其他信息', value: '其他信息' }, + ]; \ No newline at end of file diff --git a/apps/web/src/app/admin/staffinfo-manage/optionCreator.tsx b/apps/web/src/app/admin/staffinfo-manage/optionCreator.tsx new file mode 100644 index 0000000..740ccc3 --- /dev/null +++ b/apps/web/src/app/admin/staffinfo-manage/optionCreator.tsx @@ -0,0 +1,77 @@ +import React, { useState, useEffect } from 'react'; +import { Button, Input, Space, Tag } from 'antd'; +import { DeleteOutlined } from '@ant-design/icons'; + +type OptionCreatorProps = { + options: { [key: string]: string } | { label: string; value: string }[] | undefined; + onChange: (newOptions: { label: string; value: string }[]) => void; +}; + +const OptionCreator: React.FC = ({ options, onChange }) => { + const [localOptions, setLocalOptions] = useState<{ label: string; value: string }[]>([]); + const [inputValue, setInputValue] = useState(''); + + // 监听传入的 options 变化,更新本地状态 + useEffect(() => { + let validOptions: { label: string; value: string }[] = []; + if (Array.isArray(options)) { + validOptions = options; + } else if (typeof options === 'object' && options !== null) { + validOptions = Object.entries(options).map(([value, label]) => ({ label, value })); + } + setLocalOptions(validOptions); + }, [options]); + + const handleInputChange = (e: React.ChangeEvent) => { + setInputValue(e.target.value); + }; + + const handleAddOption = () => { + if (inputValue.trim()) { + const newOption = { label: inputValue, value: inputValue }; + // 检查新选项是否已存在,避免重复添加 + const isDuplicate = localOptions.some(option => option.value === newOption.value); + if (!isDuplicate) { + const newOptions = [...localOptions, newOption]; + setLocalOptions(newOptions); + // 即时调用 onChange 通知父组件更新表单值 + onChange(newOptions); + } + setInputValue(''); + } + }; + + const handleDeleteOption = (value: string) => { + const newOptions = localOptions.filter(option => option.value !== value); + setLocalOptions(newOptions); + // 即时调用 onChange 通知父组件更新表单值 + onChange(newOptions); + }; + + return ( +
+ + + + +
+ {localOptions.map(option => ( + handleDeleteOption(option.value)} + icon={} + > + {option.label} + + ))} +
+
+ ); +}; + +export default OptionCreator; diff --git a/apps/web/src/app/admin/staffinfo-manage/staffFieldManage.tsx b/apps/web/src/app/admin/staffinfo-manage/staffFieldManage.tsx new file mode 100644 index 0000000..d79edff --- /dev/null +++ b/apps/web/src/app/admin/staffinfo-manage/staffFieldManage.tsx @@ -0,0 +1,162 @@ +import React, { useEffect } from 'react'; +import { Button, Space, message } from 'antd'; +import { useState } from 'react'; +import { useStaff } from '@nice/client'; +import { PlusOutlined } from '@ant-design/icons'; +import DefaultFieldInitializer from './defaultFieldInitializer'; +import StaffFieldTable from './staffFieldTable'; +import StaffFieldModal from './staffFieldModal'; +import { useWatch } from 'antd/es/form/Form'; +import { Form } from 'antd'; +const StaffFieldManage = () => { + const [form] = Form.useForm(); + const [isModalVisible, setIsModalVisible] = useState(false); + const [editingField, setEditingField] = useState(null); + const { + addCustomField, + updateCustomField, + deleteCustomField, + useCustomFields + } = useStaff(); + const { data: fields, isLoading } = useCustomFields(); + const optionFieldTypes = ['select', 'radio']; + const [showOptions, setShowOptions] = useState(false); + const typeValue = useWatch('type', form); + + useEffect(() => { + setShowOptions(optionFieldTypes.includes(typeValue)); + }, [typeValue, optionFieldTypes]); + + const [isInitConfirmVisible, setIsInitConfirmVisible] = useState(false); + const [isProcessing, setIsProcessing] = useState(false); + const [selectedRowKeys, setSelectedRowKeys] = useState([]); + + const checkFieldExists = (name: string, excludeId?: string) => { + return fields?.some(field => + field.name === name && field.id !== excludeId + ); + }; + + const handleAdd = () => { + setEditingField(null); + form.resetFields(); + form.setFieldsValue({ options: [] }); + setIsModalVisible(true); + }; + + const handleEdit = (record: any) => { + setEditingField(record); + let optionsToSet = record.options; + if (typeof record.options === 'object' && record.options !== null && !Array.isArray(record.options)) { + optionsToSet = Object.entries(record.options).map(([value, label]) => ({ label, value })); + } + form.setFieldsValue({ ...record, options: optionsToSet }); + setIsModalVisible(true); + }; + + const handleDelete = async (id: string) => { + try { + await deleteCustomField.mutateAsync(id); + message.success('字段删除成功'); + } catch (error) { + message.error('字段删除失败'); + } + }; + + const handleSubmit = async () => { + try { + const values = await form.validateFields(); + + if (checkFieldExists(values.name, editingField?.id)) { + message.error('字段标识已存在,请使用其他标识'); + return; + } + + setIsProcessing(true); + + if (editingField) { + await updateCustomField.mutateAsync({ + id: editingField.id, + ...values, + }); + message.success('字段更新成功'); + } else { + await addCustomField.mutateAsync(values); + message.success('字段添加成功'); + } + setIsModalVisible(false); + } catch (error) { + message.error('操作失败:' + (error instanceof Error ? error.message : '未知错误')); + } finally { + setIsProcessing(false); + } + }; + + const handleBatchDelete = async () => { + if (selectedRowKeys.length === 0) { + message.warning('请选择要删除的字段'); + return; + } + try { + await Promise.all( + selectedRowKeys.map(async (id: string) => { + await deleteCustomField.mutateAsync(id); + }) + ); + message.success('批量删除成功'); + setSelectedRowKeys([]); + } catch (error) { + message.error('批量删除失败'); + } + }; + + return ( +
+
+

字段管理

+ + + + + +
+ + + + +
+ ); +}; + +export default StaffFieldManage; \ No newline at end of file diff --git a/apps/web/src/app/admin/staffinfo-manage/staffFieldModal.tsx b/apps/web/src/app/admin/staffinfo-manage/staffFieldModal.tsx new file mode 100644 index 0000000..614076d --- /dev/null +++ b/apps/web/src/app/admin/staffinfo-manage/staffFieldModal.tsx @@ -0,0 +1,107 @@ +import React from 'react'; +import { Form, Input, Select, InputNumber, Modal, message, FormInstance } from 'antd'; +import OptionCreator from './optionCreator'; +import { FieldTypeOptions, GroupOptions } from './defaultFields'; + +type StaffFieldModalProps = { + form: FormInstance; + isModalVisible: boolean; + setIsModalVisible: (visible: boolean) => void; + editingField: any; + isProcessing: boolean; + handleSubmit: () => Promise; + optionFieldTypes: string[]; + showOptions: boolean; +}; + +const StaffFieldModal: React.FC = ({ + form, + isModalVisible, + setIsModalVisible, + editingField, + isProcessing, + handleSubmit, + optionFieldTypes, + showOptions +}) => { + return ( + setIsModalVisible(false)} + confirmLoading={isProcessing} + > +
+ + + + + + + + + + + + + + ; + case 'number': + return ; + case 'date': + return ; + case 'select': + // 检查 field.options 是否存在且有数据 + if (field.options && field.options.length > 0) { + return ; + case 'radio': + // 检查 field.options 是否存在且有数据 + if (field.options && field.options.length > 0) { + return ; + } + return ; + case 'textarea': + return ; + default: + return ; + } + }; + + const onFinish = async (values: any) => { + try { + setLoading(true); + const formattedValues = { + ...values, + birthplace: values.birthplace?.join('/'), + }; + + await create.mutateAsync({ + data: formattedValues + }); + + message.success("信息提交成功"); + } catch (error) { + console.error('提交出错:', error); + message.error("提交失败,请重试"); + } finally { + setLoading(false); + } + }; + + if (fieldsLoading) { + return
加载中...
; + } + + return ( +
+

人员信息管理

+ + {/* 信息填报表单 */} + + {Object.entries(fieldGroups).map(([groupName, groupFields]) => ( +
+

{groupName}

+
+ {groupFields.map((field: any) => ( + + {renderField(field)} + + ))} +
+
+ ))} + +
+ + +
+ +
+ ); +}; + +export default StaffInfoWrite; \ No newline at end of file diff --git a/apps/web/src/app/main/staffinformation/page.tsx b/apps/web/src/app/main/staffinformation/page.tsx deleted file mode 100644 index 5bf9eaf..0000000 --- a/apps/web/src/app/main/staffinformation/page.tsx +++ /dev/null @@ -1,519 +0,0 @@ -"use client"; - -import { Button, Form, Input, Select, DatePicker, Radio, message, Modal, Cascader, InputNumber } from "antd"; -import { useState } from "react"; -import dayjs from "dayjs"; -import { useStaff } from "@nice/client"; -import DepartmentChildrenSelect from "@web/src/components/models/department/department-children-select"; -import { areaOptions } from './area-options'; -import DepartmentSelect from "@web/src/components/models/department/department-select"; -const { TextArea } = Input; - -const StaffInformation = () => { - const [modalForm] = Form.useForm(); - const [form] = Form.useForm(); - const [loading, setLoading] = useState(false); - const [isModalVisible, setIsModalVisible] = useState(false); - const [modalType, setModalType] = useState<'awards' | 'punishments' | 'equipment' | 'projects'>('awards'); - const [rewardsList, setRewardsList] = useState([]); - const [punishmentsList, setPunishmentsList] = useState([]); - const [equipmentList, setEquipmentList] = useState([]); // 新增装备列表 - const [projectsList, setProjectsList] = useState([]); // 新增任务列表 - const {create, update} = useStaff(); - - const showModal = (type: 'awards' | 'punishments' | 'equipment' | 'projects') => { - setModalType(type); - setIsModalVisible(true); - }; - - const handleModalOk = async () => { - try { - const values = await modalForm.validateFields(); - const value = values[modalType]; - - if (value) { - switch(modalType) { - case 'awards': - setRewardsList([...rewardsList, value]); - break; - case 'punishments': - setPunishmentsList([...punishmentsList, value]); - break; - case 'equipment': - setEquipmentList([...equipmentList, value]); - break; - case 'projects': - setProjectsList([...projectsList, value]); - break; - } - modalForm.resetFields(); - setIsModalVisible(false); - message.success( - modalType === 'awards' ? '奖励信息添加成功' : - modalType === 'punishments' ? '处分信息添加成功' : - modalType === 'equipment' ? '装备信息添加成功' : '任务信息添加成功' - ); - } - } catch (error) { - message.warning('请输入内容'); - } - }; - - const onFinish = async (values: any) => { - console.log('开始提交表单'); - - try { - setLoading(true); - const formattedValues = { - ...values, - birthplace: values.birthplace?.join('/'), // 将数组转换为以'/'分隔的字符串 - awards: rewardsList.join(','), - punishments: punishmentsList.join(','), - equipment: equipmentList.join(','), - projects: projectsList.join(','), - hireDate: values.hireDate?.toISOString(), - seniority: values.seniority?.toISOString(), - currentPositionDate: values.currentPositionDate?.toISOString(), - rankDate: values.rankDate?.toISOString(), // 修改这里 - }; - - await create.mutateAsync( - { - data: formattedValues - } - ); - - console.log('提交的表单数据:', formattedValues); - console.log('奖励列表:', rewardsList); - console.log('处分列表:', punishmentsList); - - message.success("信息提交成功"); - } catch (error) { - console.error('提交出错:', error); - message.error("提交失败,请重试"); - } finally { - setLoading(false); - } - }; - - const handleSubmit = (e: React.MouseEvent) => { - e.preventDefault(); // 阻止默认行为 - console.log('提交按钮被点击'); - form.submit(); - }; - - return ( -
-

人员信息填报

-
{ - console.log('表单验证失败:', errorInfo); - }} - className="space-y-6" - > - {/* 个人基本信息 */} -
-

个人基本信息

-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - { - return path.some(option => - typeof option.label === 'string' && - option.label.toLowerCase().indexOf(inputValue.toLowerCase()) > -1 - ); - } - }} - changeOnSelect - /> - -
-
- - {/* 政治信息 */} -
-

政治信息

-
- - - - - - -
-
- - {/* 职务信息 */} -
-

职务信息

-
- - - - - - - - - - - - - - - -
-
- - {/* 入职信息 */} -
-

入职信息

-
- - - - - - - - - - - - - - - - - - - - - - - - -
-
- - {/* 教育背景 */} -
-

教育背景

-
- - - - - - - - - - - - - - - - - - -
-
- - {/* 培训信息 */} -
-

培训信息

-
- - - - - - - prevValues.hasTrain !== currentValues.hasTrain} - > - {({ getFieldValue }) => ( -
- - - - - - - - - -
- )} -
-
-
- - {/* 鉴定信息 */} -
-

鉴定信息

-
- - - - - - - prevValues.hasCert !== currentValues.hasCert} - > - {({ getFieldValue }) => ( -
- - - - - - -
- )} -
-
-
- - {/* 工作信息 */} -
-

工作信息

-
-
-
- - -
-
- {equipmentList.map((equipment, index) => ( -
- {equipment} - -
- ))} -
-
-
-
- - -
-
- {projectsList.map((mission, index) => ( -
- {mission} - -
- ))} -
-
-
-
- - -
-
- {rewardsList.map((reward, index) => ( -
- {reward} - -
- ))} -
-
-
-
- - -
-
- {punishmentsList.map((punishment, index) => ( -
- {punishment} - -
- ))} -
-
-
-
- -
-
- - -
-
-
- - { - setIsModalVisible(false); - modalForm.resetFields(); - }} - > -
- -