From 96952ea0cee528cf4a8f818d0aea9363a863292c Mon Sep 17 00:00:00 2001 From: Rao <1227431568@qq.com> Date: Thu, 27 Mar 2025 08:50:22 +0800 Subject: [PATCH] rht --- .../sport-project/sportProject.router.ts | 5 + .../sport-project/sportProject.service.ts | 5 +- .../sport-standard/sportStandard.router.ts | 47 ++- .../sport-standard/sportStandard.service.ts | 74 +++- apps/server/src/models/staff/staff.router.ts | 5 + apps/server/src/models/staff/staff.service.ts | 87 +++++ .../train-content/trainContent.router.ts | 7 +- .../train-content/trainContent.service.ts | 5 +- .../train-situation/trainSituation.module.ts | 4 +- .../train-situation/trainSituation.router.ts | 48 ++- .../train-situation/trainSituation.service.ts | 85 +++- apps/server/src/tasks/init/init.service.ts | 26 +- .../assessmentstandard/assessment-modal.tsx | 6 +- .../assessment-standard-provider.tsx | 4 +- .../sport-create-content.tsx | 21 +- .../standard-create-content.tsx | 3 +- apps/web/src/app/main/sport/page.tsx | 366 ++++-------------- .../web/src/app/main/sport/sportPageModal.tsx | 92 +++++ .../src/app/main/sport/sportPageProvider.tsx | 93 +++++ .../src/app/main/sport/sportUpdateModal.tsx | 41 ++ apps/web/src/routes/index.tsx | 5 +- .../client/src/api/hooks/useTrainSituation.ts | 23 +- packages/common/prisma/schema.prisma | 89 +++-- packages/common/src/models/staff.ts | 6 +- packages/common/src/models/train.ts | 5 + packages/common/src/schema.ts | 4 + 26 files changed, 750 insertions(+), 406 deletions(-) create mode 100644 apps/web/src/app/main/sport/sportPageModal.tsx create mode 100644 apps/web/src/app/main/sport/sportPageProvider.tsx create mode 100644 apps/web/src/app/main/sport/sportUpdateModal.tsx create mode 100644 packages/common/src/models/train.ts diff --git a/apps/server/src/models/sport-project/sportProject.router.ts b/apps/server/src/models/sport-project/sportProject.router.ts index 5b2a746..428445e 100644 --- a/apps/server/src/models/sport-project/sportProject.router.ts +++ b/apps/server/src/models/sport-project/sportProject.router.ts @@ -7,6 +7,7 @@ import { Prisma } from "@nice/common"; const SportProjectArgsSchema:ZodType = z.any() const SportProjectUpdateArgsSchema:ZodType = z.any() const SportProjectFindManyArgsSchema:ZodType = z.any() +const SportProjectFindFirstArgsSchema:ZodType = z.any() @Injectable() export class SportProjectRouter { constructor( @@ -32,6 +33,10 @@ export class SportProjectRouter { .mutation(async ({input})=>{ return this.sportProjectService.softDeleteByIds(input.ids) }), + findFirst:this.trpc.procedure.input(SportProjectFindFirstArgsSchema) + .query(async ({input})=>{ + return this.sportProjectService.findFirst(input) + }), }) } \ No newline at end of file diff --git a/apps/server/src/models/sport-project/sportProject.service.ts b/apps/server/src/models/sport-project/sportProject.service.ts index 57c19b9..859ea5a 100644 --- a/apps/server/src/models/sport-project/sportProject.service.ts +++ b/apps/server/src/models/sport-project/sportProject.service.ts @@ -39,7 +39,10 @@ export class sportProjectService extends BaseService = z.any() -const SportStandardUpdateArgsSchema:ZodType = z.any() -const SportStandardFindManyArgsSchema:ZodType = z.any() -const SportStandardCreateStandardArgsSchema:ZodType = z.any() -const SportStandardUpdateStandardArgsSchema:ZodType = z.any() +const SportStandardArgsSchema: ZodType = z.any() +const SportStandardUpdateArgsSchema: ZodType = z.any() +const SportStandardFindManyArgsSchema: ZodType = z.any() +const SportStandardCreateStandardArgsSchema: ZodType = z.any() +const SportStandardUpdateStandardArgsSchema: ZodType = z.any() +const GetScoreArgsSchema = z.object({ + projectId: z.string().nonempty(), + gender: z.boolean(), + age: z.number().min(0), + performance: z.number().min(0).or(z.string()), + personType: z.string().nonempty(), + projectUnit: z.string().nonempty() +}); + interface AgeRange { start: number | null; end: number | null; @@ -30,16 +39,16 @@ export class SportStandardRouter { // .mutation(async ({input})=>{ // return this.sportStandardService.create(input) // }), - update:this.trpc.procedure.input(SportStandardUpdateArgsSchema) - .mutation(async ({input})=>{ + update: this.trpc.procedure.input(SportStandardUpdateArgsSchema) + .mutation(async ({ input }) => { return this.sportStandardService.update(input) }), - findMany:this.trpc.procedure.input(SportStandardFindManyArgsSchema) - .query(async ({input})=>{ + findMany: this.trpc.procedure.input(SportStandardFindManyArgsSchema) + .query(async ({ input }) => { return this.sportStandardService.findMany(input) }), - createStandard:this.trpc.procedure.input(SportStandardCreateStandardArgsSchema) - .mutation(async ({input})=>{ + createStandard: this.trpc.procedure.input(SportStandardCreateStandardArgsSchema) + .mutation(async ({ input }) => { const data = { projectId: input.data.projectId, gender: input.data.gender, @@ -47,17 +56,23 @@ export class SportStandardRouter { ageRanges: input.data.ageRanges as any as AgeRange[], scoreTable: input.data.scoreTable as Record } - return this.sportStandardService.createStandard(data,input.select,input.include) + return this.sportStandardService.createStandard(data, input.select, input.include) }), - updateStandard:this.trpc.procedure.input(SportStandardUpdateStandardArgsSchema) - .mutation(async ({input})=>{ + updateStandard: this.trpc.procedure.input(SportStandardUpdateStandardArgsSchema) + .mutation(async ({ input }) => { const data = { - id: input.data.id as string, + id: input.data.id as string, ageRanges: input.data.ageRanges as any as AgeRange[], scoreTable: input.data.scoreTable as Record } return this.sportStandardService.updateStandard(data) - }) + }), + + getScore: this.trpc.procedure.input(GetScoreArgsSchema).query(async ({ input }) => { + console.log('计算') + console.log(input) + return this.sportStandardService.getScore(input); + }), }) } \ No newline at end of file diff --git a/apps/server/src/models/sport-standard/sportStandard.service.ts b/apps/server/src/models/sport-standard/sportStandard.service.ts index f5dfae2..1e93b72 100644 --- a/apps/server/src/models/sport-standard/sportStandard.service.ts +++ b/apps/server/src/models/sport-standard/sportStandard.service.ts @@ -3,6 +3,7 @@ import { BaseService } from "../base/base.service"; import { db, ObjectType, Prisma, UserProfile } from "@nice/common"; import EventBus, { CrudOperation } from "@server/utils/event-bus"; import { DefaultArgs } from "@prisma/client/runtime/library"; +import { z } from "zod"; interface AgeRange { start: number | null; @@ -10,13 +11,22 @@ interface AgeRange { label: string; } interface Record { - [key: number]: number[]; + [key: number]: (number | string)[]; } interface ScoreStandard { ageRanges: AgeRange[]; scoreTable: Record; } +const GetScoreArgsSchema = z.object({ + projectId: z.string().nonempty(), + gender: z.boolean(), + age: z.number().min(0), + performance: z.number().min(0).or(z.string()), + personType: z.string().nonempty(), + projectUnit: z.string().nonempty() +}); + @Injectable() export class SportStandardService extends BaseService { constructor() { @@ -125,8 +135,8 @@ export class SportStandardService extends BaseService { const isAboveStart = range.start === null || age > range.start; @@ -143,39 +153,65 @@ export class SportStandardService extends BaseService b - a); + const isTimeUnit = projectUnit.includes('time'); // 假设时间单位包含 '时间单位' 字符串 + for (const score of scores) { - if (performance >= scoreStandard.scoreTable[score][ageRangeIndex]) { - return score; + const standard = scoreStandard.scoreTable[score.toString()][ageRangeIndex]; + if (isTimeUnit) { + // 此时的performance和standard是时间字符串 + // 需要将时间字符串转换为秒 + if (this.timeStringToSeconds(performance) <= this.timeStringToSeconds(standard)) { + return score; + } + } else { + if (performance >= standard) { + return score; + } } } return 0; } - async getScore(data: { - id: string; - projectId: string; - gender: boolean; - age: number; - performance: number; - personType: string; - }) { + async getScore(data: z.infer) { + console.log("传入的参数",data) const standard = await this.findUnique({ where: { - id: data.id, - projectId: data.projectId, - gender: data.gender, - personType: data.personType + projectId_gender_personType: { // 使用复合唯一索引 + projectId: data.projectId, + gender: data.gender, + personType: data.personType + } } }) + console.log("找到的评分标准",standard) if (!standard) { throw new Error('未找到对应的评分标准'); } - + const scoreTable:Record = JSON.parse(String(standard.scoreTable)) + const ageRanges:AgeRange[] = JSON.parse(String(standard.ageRanges)) + const scoreStandard:ScoreStandard = { + ageRanges, + scoreTable + } + console.log("评分标准",scoreStandard) return this.SportScoreCalculator( data.performance, data.age, - standard.scoreTable as any as ScoreStandard + scoreStandard, + data.projectUnit, ) } + // 将 13:45 格式的字符串转换为秒数 + private timeStringToSeconds(timeStr) { + const [minutes, seconds] = timeStr.split(':').map(Number); + return minutes * 60 + seconds; + } + + // 将秒数转换为 13:45 格式的字符串 + private secondsToTimeString(seconds: number) { + const minutesPart = Math.floor(seconds / 60); + const secondsPart = seconds % 60; + return `${String(minutesPart).padStart(2, '0')}:${String(secondsPart).padStart(2, '0')}`; + } } \ No newline at end of file diff --git a/apps/server/src/models/staff/staff.router.ts b/apps/server/src/models/staff/staff.router.ts index d93821e..2842c4f 100755 --- a/apps/server/src/models/staff/staff.router.ts +++ b/apps/server/src/models/staff/staff.router.ts @@ -95,5 +95,10 @@ export class StaffRouter { .query(async ({ input }) => { return await this.staffService.findUnique(input); }), + findSportStaffByDept:this.trpc.procedure + .input(StaffMethodSchema.findSportStaffByDept) + .query(async ({ input }) => { + return await this.staffService.findSportStaffByDept(input); + }), }); } diff --git a/apps/server/src/models/staff/staff.service.ts b/apps/server/src/models/staff/staff.service.ts index 57cddd2..5f0a6d6 100755 --- a/apps/server/src/models/staff/staff.service.ts +++ b/apps/server/src/models/staff/staff.service.ts @@ -12,6 +12,64 @@ import { BaseService } from '../base/base.service'; import * as argon2 from 'argon2'; import EventBus, { CrudOperation } from '@server/utils/event-bus'; +const StaffSelect = { + // 获取 Staff 模型的基础信息 + id: true, + showname: true, + username: true, + avatar: true, + password: true, + phoneNumber: true, + age: true, + sex: true, + absent: true, + order: true, + createdAt: true, + updatedAt: true, + enabled: true, + deletedAt: true, + officerId: true, + registerToken: true, + // 获取关联的 Position 模型的基础信息 + position: { + select: { + id: true, + type: true, + categorize: true, + createdAt: true, + updatedAt: true + } + }, + // 获取关联的 TrainSituation 模型的基础信息 + trainSituations: { + select: { + id: true, + score: true, + value: true, + alreadyTrainTime: true, + groupId:true, + // 获取关联的 TrainContent 模型的基础信息 + trainContent: { + select: { + id: true, + title: true, + type: true, + parentId: true, + deletedAt: true, + createdAt: true, + updatedAt: true + } + } + } + }, + department:{ + select:{ + id:true, + name: true, + } + } +} + @Injectable() export class StaffService extends BaseService { constructor(private readonly departmentService: DepartmentService) { @@ -118,7 +176,36 @@ export class StaffService extends BaseService { return staff; } } + async findSportStaffByDept(data: z.infer) { + const { deptId, domainId } = data; + let queryResult; + if (!deptId) { + // deptId 为空时执行 res 的请求 + queryResult = await db.staff.findMany({ + select: StaffSelect + }); + } else { + // deptId 不为空时执行 result 的请求 + const childDepts = await this.departmentService.getDescendantIds(deptId, true); + queryResult = await db.staff.findMany({ + where: { + deptId: { in: childDepts }, + domainId, + }, + select: StaffSelect + }); + } + + // 筛选出 trainSituations 中 trainContent 的 type 为 'SPORT' 的记录 + const filteredResult = queryResult.filter(staff => { + return staff.trainSituations.some(trainSituation => { + return trainSituation.trainContent.type === 'SPORT'; + }); + }); + + return filteredResult; + } // /** // * 根据关键词或ID集合查找员工 // * @param data 包含关键词、域ID和ID集合的对象 diff --git a/apps/server/src/models/train-content/trainContent.router.ts b/apps/server/src/models/train-content/trainContent.router.ts index 68acd93..15779ad 100644 --- a/apps/server/src/models/train-content/trainContent.router.ts +++ b/apps/server/src/models/train-content/trainContent.router.ts @@ -7,6 +7,7 @@ import { Prisma } from "@nice/common"; const TrainContentArgsSchema:ZodType = z.any() const TrainContentUpdateArgsSchema:ZodType = z.any() const TrainContentFindManyArgsSchema:ZodType = z.any() +const TrainContentFindFirstArgsSchema:ZodType = z.any() @Injectable() export class TrainContentRouter { constructor( @@ -26,7 +27,11 @@ export class TrainContentRouter { findMany:this.trpc.procedure.input(TrainContentFindManyArgsSchema) .query(async ({input})=>{ return this.trainContentService.findMany(input) - }) + }), + findFirst:this.trpc.procedure.input(TrainContentFindFirstArgsSchema) + .query(async ({input})=>{ + return this.trainContentService.findFirst(input) + }), }) } \ No newline at end of file diff --git a/apps/server/src/models/train-content/trainContent.service.ts b/apps/server/src/models/train-content/trainContent.service.ts index 6f57562..9d553fe 100644 --- a/apps/server/src/models/train-content/trainContent.service.ts +++ b/apps/server/src/models/train-content/trainContent.service.ts @@ -28,7 +28,10 @@ export class TrainContentService extends BaseService = z.any() -const TrainSituationUpdateArgsSchema:ZodType = z.any() -const TrainSituationFindManyArgsSchema:ZodType = z.any() +const TrainSituationArgsSchema: ZodType = z.any() +const TrainSituationUpdateArgsSchema: ZodType = z.any() +const TrainSituationFindManyArgsSchema: ZodType = z.any() @Injectable() export class TrainSituationRouter { @@ -14,31 +14,57 @@ export class TrainSituationRouter { private readonly trpc: TrpcService, private readonly trainSituationService: TrainSituationService, ) { } - router = this.trpc.router({ - create:this.trpc.protectProcedure + create: this.trpc.protectProcedure .input(TrainSituationArgsSchema) .mutation(async ({ input }) => { return this.trainSituationService.create(input) }), - update:this.trpc.protectProcedure + update: this.trpc.protectProcedure .input(TrainSituationUpdateArgsSchema) .mutation(async ({ input }) => { return this.trainSituationService.update(input) }), - findMany:this.trpc.protectProcedure + findMany: this.trpc.protectProcedure .input(TrainSituationFindManyArgsSchema) - .query(async ({input}) =>{ + .query(async ({ input }) => { return this.trainSituationService.findMany(input) }), - findManyByDepId:this.trpc.protectProcedure + findManyByDepId: this.trpc.protectProcedure .input(z.object({ deptId: z.string().optional(), domainId: z.string().optional(), trainContentId: z.string().optional() - })) - .query(async ({input}) => { + })) + .query(async ({ input }) => { return this.trainSituationService.findManyByDeptId(input) + }), + createManyTrainSituation: this.trpc.protectProcedure + .input(z.array( + z.object({ + staffId: z.string(), + trainContentId: z.string(), + mustTrainTime: z.number(), + alreadyTrainTime: z.number(), + value: z.string(), + projectUnit: z.string(), + personType: z.string(), + projectId: z.string(), + gender: z.boolean(), + age: z.number(), + performance: z.number().or(z.string()) + }) + )) + .mutation(async ({ input }) => { + console.log(input) + return this.trainSituationService.createManyTrainSituation(input) + }), + deleteSameGroupTrainSituation:this.trpc.protectProcedure + .input(z.object({ + groupId:z.string() + })) + .mutation(async ({input})=>{ + return this.trainSituationService.deleteSameGroupTrainSituation(input) }) }) diff --git a/apps/server/src/models/train-situation/trainSituation.service.ts b/apps/server/src/models/train-situation/trainSituation.service.ts index 26968ce..1f3330f 100644 --- a/apps/server/src/models/train-situation/trainSituation.service.ts +++ b/apps/server/src/models/train-situation/trainSituation.service.ts @@ -4,11 +4,13 @@ import { db, ObjectType, Prisma, UserProfile } from "@nice/common"; import EventBus, { CrudOperation } from "@server/utils/event-bus"; import { DefaultArgs } from "@prisma/client/runtime/library"; import { StaffService } from "../staff/staff.service"; +import { SportStandardService } from "../sport-standard/sportStandard.service"; +import { uuidv4 } from "lib0/random"; @Injectable() export class TrainSituationService extends BaseService { - constructor(private readonly staffService:StaffService) { + constructor(private readonly staffService:StaffService,private readonly sportStandardService:SportStandardService) { super(db,ObjectType.TRAIN_SITUATION,false); } // 创建培训情况 @@ -30,14 +32,7 @@ export class TrainSituationService extends BaseService + async findMany(args: Prisma.TrainSituationFindManyArgs) { const result = await super.findMany(args); return result; @@ -68,7 +63,77 @@ export class TrainSituationService extends BaseService{ + await this.createTrainSituation(item,groupId) + }) + } + async deleteSameGroupTrainSituation(args:{groupId?:string}){ + const {groupId} = args + const result = await super.deleteMany({ + where:{ + groupId + } + }) + this.emitDataChanged(CrudOperation.DELETED,result) + return result + } + // 发送数据变化事件 private emitDataChanged(operation: CrudOperation, data: any) { EventBus.emit('dataChanged', { diff --git a/apps/server/src/tasks/init/init.service.ts b/apps/server/src/tasks/init/init.service.ts index d580289..8f8fe33 100755 --- a/apps/server/src/tasks/init/init.service.ts +++ b/apps/server/src/tasks/init/init.service.ts @@ -19,7 +19,7 @@ export class InitService { private readonly minioService: MinioService, private readonly authService: AuthService, private readonly genDevService: GenDevService, - ) {} + ) { } private async createRoles() { this.logger.log('Checking existing system roles'); for (const role of InitRoles) { @@ -54,12 +54,12 @@ export class InitService { }, }); this.logger.log(`Created new taxonomy: ${taxonomy.name}`); - } else if(process.env.NODE_ENV === 'development'){ + } else if (process.env.NODE_ENV === 'development') { // Check for differences and update if necessary const differences = Object.keys(taxonomy).filter( (key) => taxonomy[key] !== existingTaxonomy[key], ); - + if (differences.length > 0) { await db.taxonomy.update({ where: { id: existingTaxonomy.id }, @@ -117,6 +117,25 @@ export class InitService { this.logger.log('Root account already exists'); } } + private async createTrainContent() { + this.logger.log('Checking for sport train content'); + const existingTrainContent = await db.trainContent.findFirst({ + where: { + type: 'SPORTS', + title: '体能考核' + } + }); + if (!existingTrainContent) { + await db.trainContent.create({ + data: { + type: 'SPORTS', + title: '体能考核' + } + }) + } else { + this.logger.log('Sport train already exists'); + } + } private async createBucket() { await this.minioService.createBucket('app'); } @@ -140,6 +159,7 @@ export class InitService { await this.createRoot(); await this.createOrUpdateTaxonomy(); await this.initAppConfigs(); + await this.createTrainContent(); try { this.logger.log('Initialize minio'); await this.createBucket(); diff --git a/apps/web/src/app/admin/assessmentstandard/assessment-modal.tsx b/apps/web/src/app/admin/assessmentstandard/assessment-modal.tsx index df958d6..150772a 100644 --- a/apps/web/src/app/admin/assessmentstandard/assessment-modal.tsx +++ b/apps/web/src/app/admin/assessmentstandard/assessment-modal.tsx @@ -1,4 +1,4 @@ -import { Form, InputNumber, Modal } from "antd"; +import { Form, Input, InputNumber, Modal } from "antd"; import { useAssessmentStandardContext } from "./assessment-standard-provider"; export default function AssessmentModal() { const { isAgeModalVisible, isScoreModalVisible, ageForm, scoreForm, handleAgeOk, handleAgeCancel, handleScoreOk, handleScoreCancel, ageRanges } = useAssessmentStandardContext(); @@ -28,11 +28,11 @@ export default function AssessmentModal() { >
- + {ageRanges.map((range, index) => ( - + ))}
diff --git a/apps/web/src/app/admin/assessmentstandard/assessment-standard-provider.tsx b/apps/web/src/app/admin/assessmentstandard/assessment-standard-provider.tsx index 6230473..f035184 100644 --- a/apps/web/src/app/admin/assessmentstandard/assessment-standard-provider.tsx +++ b/apps/web/src/app/admin/assessmentstandard/assessment-standard-provider.tsx @@ -15,6 +15,7 @@ interface AssessmentStandardContextType { id: string; name: string; unit: string; + deletedAt?: string | null; }[]; sportProjectLoading: boolean; ageRanges: { start: number; end: number; label: string; }[]; @@ -103,7 +104,8 @@ export function AssessmentStandardProvider({ children }: AssessmentStandardProvi sportProjectList: sportProjectList?.map((item) => ({ id: item.id, name: item.name, - unit: item.unit + unit: item.unit, + deletedAt:item.deletedAt })), sportProjectLoading, ageRanges, diff --git a/apps/web/src/app/admin/assessmentstandard/sport-create-content.tsx b/apps/web/src/app/admin/assessmentstandard/sport-create-content.tsx index 579021f..11584af 100644 --- a/apps/web/src/app/admin/assessmentstandard/sport-create-content.tsx +++ b/apps/web/src/app/admin/assessmentstandard/sport-create-content.tsx @@ -1,20 +1,33 @@ import { Button, Form, Input, Select, Skeleton } from "antd"; import { useAssessmentStandardContext } from "./assessment-standard-provider"; -import { useSport } from "@nice/client"; +import { api, useSport } from "@nice/client"; import toast from "react-hot-toast"; +import create from "@ant-design/icons/lib/components/IconFont"; export default function SportCreateContent() { const { form, sportProjectList, sportProjectLoading } = useAssessmentStandardContext(); const { createSportProject, softDeleteByIds } = useSport(); + const { data: fatherTrainContent } = api.trainContent.findFirst.useQuery({ + where: { + type: "SPORTS", + title: "体能考核" + } + }) const handleCreateProject = async () => { - console.log(form.getFieldsValue().createProjectName) if (form.getFieldsValue().createProjectName && form.getFieldsValue().unit) { await createSportProject.mutateAsync({ data: { name: form.getFieldsValue().createProjectName, type: "sport", unit: form.getFieldsValue().unit, - isAscending: true + isAscending: true, + trainContent: { + create: { + title: form.getFieldsValue().createProjectName, + type: "SPORT", + parentId: fatherTrainContent?.id + } + } } } as any) toast.success("创建项目成功") @@ -52,7 +65,7 @@ export default function SportCreateContent() { {sportProjectLoading ? :
- {sportProjectList?.map((item) => ( + {sportProjectList?.filter(item=>item.deletedAt === null)?.map((item) => (
{item.name}({item.unit})
handleDeleteProject(item.id)}>删除 diff --git a/apps/web/src/app/admin/assessmentstandard/standard-create-content.tsx b/apps/web/src/app/admin/assessmentstandard/standard-create-content.tsx index b80f712..c193166 100644 --- a/apps/web/src/app/admin/assessmentstandard/standard-create-content.tsx +++ b/apps/web/src/app/admin/assessmentstandard/standard-create-content.tsx @@ -114,7 +114,6 @@ export default function StandardCreateContent() { } }, [data]) - return (
@@ -123,7 +122,7 @@ export default function StandardCreateContent() { setSearchValue(e.target.value)} - className="pl-10 w-full border" - /> - -
- -
- {isLoading ? ( -
加载中...
+ {staffsWithScoreLoading ? ( + ) : ( 'editable-row'} - columns={mergedColumns} - dataSource={filteredData} + columns={columns} + dataSource={sportsData} tableLayout="fixed" rowKey="id" pagination={{ @@ -443,6 +232,7 @@ export default function SportPage() { + ); } \ No newline at end of file diff --git a/apps/web/src/app/main/sport/sportPageModal.tsx b/apps/web/src/app/main/sport/sportPageModal.tsx new file mode 100644 index 0000000..c749d49 --- /dev/null +++ b/apps/web/src/app/main/sport/sportPageModal.tsx @@ -0,0 +1,92 @@ +import { Button, Form, Input, InputNumber, Modal, Select } from "antd"; +import { useSportPageContext } from "./sportPageProvider"; +import { api, useTrainSituation } from "@nice/client"; +import toast from "react-hot-toast"; + +export default function SportPageModal() { + const { newScoreForm, isNewScoreModalVisible, handleNewScoreCancel, sportProjectList, staffs, staffsLoading } = useSportPageContext() + const { createManyTrainSituation } = useTrainSituation() + // 修改后的提交处理逻辑 + const handleNewScoreOk = async () => { + const userId = newScoreForm.getFieldValue("user"); + if (!userId) { + console.error("未选择人员"); + toast.error('请选择人员'); + return; + } + const userMsg = staffs?.find((item) => item.id === userId); + const createTrainSituationMsg = sportProjectList?.filter(item=>item.deletedAt === null).map((item) => { + const performanceValue = newScoreForm.getFieldValue(['sports', item.id, 'score']); + if (!performanceValue) { + console.error(`未输入 ${item.name} 的成绩`); + toast.error(`请输入 ${item.name} 的成绩`); + return; + } + // 处理数值类型转换 + const parsedPerformance = item.unit.includes('time') + ? performanceValue // 保持时间字符串 + : Number(performanceValue); + console.log('解析后的成绩:', parsedPerformance); + return { + staffId: userId, + trainContentId: item.trainContentId, + mustTrainTime: 0.0, + alreadyTrainTime: 0.0, + value: String(parsedPerformance), + projectId: item.id, + gender: userMsg?.sex ?? false, // 提供默认值 + age: userMsg?.age ?? 0, // 提供默认值 + performance: parsedPerformance, + personType: userMsg?.position?.categorize || "OFFICER", + projectUnit: item.unit + } + }); + await createManyTrainSituation.mutateAsync([...createTrainSituationMsg]) + + console.log("提交成功", createTrainSituationMsg); + toast.success('成绩提交成功'); + handleNewScoreCancel(); + }; + return ( + +
+ + + + {sportProjectList?.filter(item=>item.deletedAt === null).map((field) => ( +
+ + + +
+ ))} + +
+ ) +} + diff --git a/apps/web/src/app/main/sport/sportPageProvider.tsx b/apps/web/src/app/main/sport/sportPageProvider.tsx new file mode 100644 index 0000000..4661fa8 --- /dev/null +++ b/apps/web/src/app/main/sport/sportPageProvider.tsx @@ -0,0 +1,93 @@ +import React, { + createContext, + ReactNode, + useContext, + useEffect, + useState, +} from "react"; +import { api } from "@nice/client"; +import { Form, FormInstance } from "antd"; +import { useAuth } from "@web/src/providers/auth-provider"; +import { RolePerms, UserProfile } from "@nice/common"; +interface SportPageContextType { + sportProjectList: { + id: string, + name: string, + unit: string, + trainContentId:string, + deletedAt?: string | null + }[], + newScoreForm: FormInstance, + isNewScoreModalVisible: boolean, + staffsWithScoreLoading:boolean, + handleNewScoreCancel: () => void, + handlNewScoreOpen: () => void, + staffs: UserProfile[] | null, + staffsLoading: boolean + staffsWithScore: any[] | null, +} + +const SportPageContext = createContext(null); +interface SportPageProviderProps { + children: ReactNode; +} + +export function SportPageProvider({ children }: SportPageProviderProps) { + const { data: sportProjectList, isLoading: sportProjectLoading } = api.sportProject.findMany.useQuery() + const [newScoreForm] = Form.useForm() + const [isNewScoreModalVisible, setIsNewScoreModalVisible] = useState(false) + const [isUpdateScoreModalVisble,setIsUpdateScoreModalVisible] = useState(false) + const handleNewScoreCancel = () => { + setIsNewScoreModalVisible(false) + } + const handlNewScoreOpen = () => { + newScoreForm.resetFields() + setIsNewScoreModalVisible(true) + } + + + + const { user, isAuthenticated, hasSomePermissions } = useAuth() + // 获取当前员工的单位下的所有staff的记录 + const { data: staffs, isLoading: staffsLoading } = isAuthenticated ? + (hasSomePermissions(RolePerms.MANAGE_ANY_STAFF, RolePerms.MANAGE_DOM_STAFF) ? + api.staff.findMany.useQuery() : + api.staff.findByDept.useQuery({ + deptId: user.deptId + }) + ) + : { data: null, isLoading: false } + // 获取当前有体育成绩的员工的记录 + const {data:staffsWithScore,isLoading:staffsWithScoreLoading} = isAuthenticated ? + api.staff.findSportStaffByDept.useQuery({deptId:user.deptId}) + : { data: null, isLoading: false } + return ( + ({ + id: item.id, + name: item.name, + unit: item.unit, + trainContentId:item.trainContentId, + deletedAt:item.deletedAt, + })) || [], + newScoreForm, + isNewScoreModalVisible, + staffsWithScore, + handlNewScoreOpen, + handleNewScoreCancel, + staffs: staffs as any as UserProfile[], + staffsLoading, + staffsWithScoreLoading + }}> + {children} + + ); +} +export const useSportPageContext = () => { + const context = useContext(SportPageContext); + if (!context) { + throw new Error("useSportPageContext must be used within SportPageProvider"); + } + return context; +}; diff --git a/apps/web/src/app/main/sport/sportUpdateModal.tsx b/apps/web/src/app/main/sport/sportUpdateModal.tsx new file mode 100644 index 0000000..28779a7 --- /dev/null +++ b/apps/web/src/app/main/sport/sportUpdateModal.tsx @@ -0,0 +1,41 @@ +import { Button, Form, Input, InputNumber, Modal, Select } from "antd"; +import { useSportPageContext } from "./sportPageProvider"; +import { api } from "@nice/client"; +import toast from "react-hot-toast"; + +export default function SportPageModal() { + const { newScoreForm, isNewScoreModalVisible, handleNewScoreCancel, sportProjectList, staffs, staffsLoading } = useSportPageContext() + const createManyTrainSituation = api.trainSituation.createManyTrainSituation.useMutation() + // 修改后的提交处理逻辑 + const handleUpdateScoreOk = async () => { + + }; + return ( + +
+ {sportProjectList.map((field) => ( +
+ + + +
+ ))} + +
+ ) +} + diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index 566bb2e..4fc3085 100755 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -16,6 +16,7 @@ import WeekPlanPage from "../app/main/plan/weekplan/page"; import AdminLayout from "../components/layout/admin/AdminLayout"; import { adminRoute } from "./admin-route"; import SportPage from "../app/main/sport/page"; +import { SportPageProvider } from "../app/main/sport/sportPageProvider"; interface CustomIndexRouteObject extends IndexRouteObject { name?: string; breadcrumb?: string; @@ -88,7 +89,9 @@ export const routes: CustomRouteObject[] = [ }, { path:"sportsassessment", - element: + element: + + } ] }, diff --git a/packages/client/src/api/hooks/useTrainSituation.ts b/packages/client/src/api/hooks/useTrainSituation.ts index 8779f65..cf5e0c5 100644 --- a/packages/client/src/api/hooks/useTrainSituation.ts +++ b/packages/client/src/api/hooks/useTrainSituation.ts @@ -2,7 +2,6 @@ import { getQueryKey } from "@trpc/react-query"; import { api } from "../trpc"; // Adjust path as necessary import { useQueryClient } from "@tanstack/react-query"; import { ObjectType, Staff } from "@nice/common"; -import { findQueryData } from "../utils"; import { CrudOperation, emitDataChange } from "../../event"; @@ -23,9 +22,29 @@ export function useTrainSituation(){ emitDataChange(ObjectType.TRAIN_SITUATION,res,CrudOperation.UPDATED) } }) + + const deleteSameGroupTrainSituation = api.trainSituation.deleteSameGroupTrainSituation.useMutation({ + onSuccess:(res:any) => { + queryClient.invalidateQueries({queryKey}) + queryClient.invalidateQueries({queryKey:getQueryKey(api.trainContent)}) + queryClient.invalidateQueries({queryKey:getQueryKey(api.staff)}) + emitDataChange(ObjectType.TRAIN_SITUATION,res,CrudOperation.DELETED) + } + }) + + const createManyTrainSituation = api.trainSituation.createManyTrainSituation.useMutation({ + onSuccess:(res:any) => { + queryClient.invalidateQueries({queryKey}) + queryClient.invalidateQueries({queryKey:getQueryKey(api.trainContent)}) + queryClient.invalidateQueries({queryKey:getQueryKey(api.staff)}) + emitDataChange(ObjectType.TRAIN_SITUATION,res,CrudOperation.CREATED) + } + }) return { create, - update + update, + deleteSameGroupTrainSituation, + createManyTrainSituation } } \ No newline at end of file diff --git a/packages/common/prisma/schema.prisma b/packages/common/prisma/schema.prisma index cbc965c..2436cfb 100755 --- a/packages/common/prisma/schema.prisma +++ b/packages/common/prisma/schema.prisma @@ -341,14 +341,15 @@ model TrainContent { title String @map("title") trainSituations TrainSituation[] trainPlans TrainPlan[] @relation("TrainPlanContent") - - type String @map("type") - parentId String? @map("parent_id") - parent TrainContent? @relation("ContentParent", fields: [parentId], references: [id]) // 指向自身 - children TrainContent[] @relation("ContentParent") // 指向自身 - deletedAt DateTime? @map("deleted_at") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + sportProjectId String? @unique @map("sport_project_id") // 新增字段,用于关联 SportProject + sportProject SportProject? @relation("TrainContentSportProject") // 可选的一对一关系 + type String @map("type") + parentId String? @map("parent_id") + parent TrainContent? @relation("ContentParent", fields: [parentId], references: [id]) // 指向自身 + children TrainContent[] @relation("ContentParent") // 指向自身 + deletedAt DateTime? @map("deleted_at") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") @@map("train_content") } @@ -360,12 +361,18 @@ model TrainSituation { staff Staff @relation(fields: [staffId], references: [id]) trainContentId String @map("train_content_id") trainContent TrainContent @relation(fields: [trainContentId], references: [id]) + groupId String? @map("group_id") score Float @default(0.0) @map("score") + value String? @map("value") mustTrainTime Float @map("must_train_time") alreadyTrainTime Float @map("already_train_time") dailyTrainTime DailyTrainTime[] @relation("DailyTrainSituation") + deletedAt DateTime? @map("deleted_at") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + @@map("train_situation") } @@ -380,12 +387,12 @@ model DailyTrainTime { } model Position { - id String @id @default(cuid()) @map("id") - type String @map("type") - - staff Staff[] @relation("StaffPosition") - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") + id String @id @default(cuid()) @map("id") + type String @map("type") + categorize String @map("categorize") + staff Staff[] @relation("StaffPosition") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") @@map("position") } @@ -427,9 +434,9 @@ model Staff { avatar String? @map("avatar") password String? @map("password") phoneNumber String? @unique @map("phone_number") - age Int? @map("age") - sex Boolean? @map("sex") - absent Boolean? @map("absent")@default(false) + age Int? @default(22) @map("age") + sex Boolean? @default(true) @map("sex") + absent Boolean? @default(false) @map("absent") trainSituations TrainSituation[] position Position? @relation("StaffPosition", fields: [positionId], references: [id]) @@ -487,37 +494,39 @@ model TrainPlan { @@map("train_plan") } - model SportProject { - id String @id @default(cuid()) - name String @map("name") // 项目名称 - type String @map("type") // 项目类型 - description String? @map("description") // 项目描述 - unit String @map("unit") // 成绩单位(如:秒、米、个) - isAscending Boolean @map("is_ascending") // 是否为升序计分 - standards SportStandard[] - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - deletedAt DateTime? @map("deleted_at") + id String @id @default(cuid()) + name String @map("name") // 项目名称 + type String @map("type") // 项目类型 + description String? @map("description") // 项目描述 + unit String @map("unit") // 成绩单位(如:秒、米、个) + isAscending Boolean @map("is_ascending") // 是否为升序计分 + trainContentId String? @unique @map("train_content_id") // 新增字段,用于关联 TrainContent + trainContent TrainContent? @relation("TrainContentSportProject", fields: [trainContentId], references: [id]) // 可选的一对一关系 + standards SportStandard[] + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") + @@map("sport_project") } model SportStandard { - id String @id @default(cuid()) + id String @id @default(cuid()) - projectId String @map("project_id") - project SportProject @relation(fields: [projectId], references: [id]) + projectId String @map("project_id") + project SportProject @relation(fields: [projectId], references: [id]) - gender Boolean @map("gender") // true为男,false为女 - personType String @map("person_type") // 人员类型 - - ageRanges Json @map("age_ranges") // 年龄段定义 - scoreTable Json @map("score_table") // 评分标准表 + gender Boolean @map("gender") // true为男,false为女 + personType String @map("person_type") // 人员类型 - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - deletedAt DateTime? @map("deleted_at") + ageRanges Json @map("age_ranges") // 年龄段定义 + scoreTable Json @map("score_table") // 评分标准表 - @@unique([projectId, gender, personType]) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") + + @@unique([projectId, gender, personType], name: "projectId_gender_personType") @@map("sport_standard") } diff --git a/packages/common/src/models/staff.ts b/packages/common/src/models/staff.ts index 565f847..42757e6 100755 --- a/packages/common/src/models/staff.ts +++ b/packages/common/src/models/staff.ts @@ -1,5 +1,6 @@ -import { Staff, Department } from "@prisma/client"; +import { Staff, Department, Position, TrainSituation } from "@prisma/client"; import { RolePerms } from "../enum"; +import { trainSituationDto } from "./train"; export type StaffRowModel = { avatar: string; @@ -15,11 +16,14 @@ export type UserProfile = Staff & { parentDeptIds: string[]; domain: Department; department: Department; + position: Position; }; export type StaffDto = Staff & { domain?: Department; department?: Department; + position?: Position; + trainSituation?: trainSituationDto; }; export interface AuthDto { token: string; diff --git a/packages/common/src/models/train.ts b/packages/common/src/models/train.ts new file mode 100644 index 0000000..b6d175f --- /dev/null +++ b/packages/common/src/models/train.ts @@ -0,0 +1,5 @@ +import { TrainContent, TrainSituation } from "@prisma/client"; + +export type trainSituationDto = TrainSituation & { + trainContent: TrainContent +} \ No newline at end of file diff --git a/packages/common/src/schema.ts b/packages/common/src/schema.ts index 95b3af3..7201800 100755 --- a/packages/common/src/schema.ts +++ b/packages/common/src/schema.ts @@ -125,6 +125,10 @@ export const StaffMethodSchema = { getRows: RowRequestSchema.extend({ domainId: z.string().nullish(), }), + findSportStaffByDept: z.object({ + deptId: z.string().nullish(), + domainId: z.string().nullish(), + }), }; export const DepartmentMethodSchema = {