rht
This commit is contained in:
parent
656ba75ce7
commit
96952ea0ce
|
@ -7,6 +7,7 @@ import { Prisma } from "@nice/common";
|
|||
const SportProjectArgsSchema:ZodType<Prisma.SportProjectCreateArgs> = z.any()
|
||||
const SportProjectUpdateArgsSchema:ZodType<Prisma.SportProjectUpdateArgs> = z.any()
|
||||
const SportProjectFindManyArgsSchema:ZodType<Prisma.SportProjectFindManyArgs> = z.any()
|
||||
const SportProjectFindFirstArgsSchema:ZodType<Prisma.SportProjectFindFirstArgs> = 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)
|
||||
}),
|
||||
})
|
||||
|
||||
}
|
|
@ -39,7 +39,10 @@ export class sportProjectService extends BaseService<Prisma.SportProjectDelegate
|
|||
const result = await super.findMany(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
async findFirst(args: Prisma.SportProjectFindFirstArgs) {
|
||||
const result = await super.findFirst(args)
|
||||
return result
|
||||
}
|
||||
async softDeleteByIds(ids: string[]) {
|
||||
const result = await super.softDeleteByIds(ids);
|
||||
this.emitDataChanged(CrudOperation.DELETED,result)
|
||||
|
|
|
@ -9,6 +9,15 @@ const SportStandardUpdateArgsSchema:ZodType<Prisma.SportStandardUpdateArgs> = z.
|
|||
const SportStandardFindManyArgsSchema: ZodType<Prisma.SportStandardFindManyArgs> = z.any()
|
||||
const SportStandardCreateStandardArgsSchema: ZodType<Prisma.SportStandardCreateArgs> = z.any()
|
||||
const SportStandardUpdateStandardArgsSchema: ZodType<Prisma.SportStandardUpdateArgs> = 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;
|
||||
|
@ -57,7 +66,13 @@ export class SportStandardRouter {
|
|||
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);
|
||||
}),
|
||||
})
|
||||
|
||||
}
|
|
@ -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<Prisma.SportStandardDelegate> {
|
||||
constructor() {
|
||||
|
@ -126,7 +136,7 @@ export class SportStandardService extends BaseService<Prisma.SportStandardDelega
|
|||
return result
|
||||
}
|
||||
|
||||
public SportScoreCalculator(performance: number, age: number, scoreStandard: ScoreStandard): number {
|
||||
public SportScoreCalculator(performance: number | string, age: number, scoreStandard: ScoreStandard, projectUnit: string, ): number {
|
||||
// 1. 找到对应的年龄段索引
|
||||
const ageRangeIndex = scoreStandard.ageRanges.findIndex(range => {
|
||||
const isAboveStart = range.start === null || age > range.start;
|
||||
|
@ -143,39 +153,65 @@ export class SportStandardService extends BaseService<Prisma.SportStandardDelega
|
|||
.map(Number)
|
||||
.sort((a, b) => b - a);
|
||||
|
||||
const isTimeUnit = projectUnit.includes('time'); // 假设时间单位包含 '时间单位' 字符串
|
||||
|
||||
for (const score of scores) {
|
||||
if (performance >= scoreStandard.scoreTable[score][ageRangeIndex]) {
|
||||
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<typeof GetScoreArgsSchema>) {
|
||||
console.log("传入的参数",data)
|
||||
const standard = await this.findUnique({
|
||||
where: {
|
||||
id: data.id,
|
||||
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')}`;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
|
|
@ -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<Prisma.StaffDelegate> {
|
||||
constructor(private readonly departmentService: DepartmentService) {
|
||||
|
@ -118,7 +176,36 @@ export class StaffService extends BaseService<Prisma.StaffDelegate> {
|
|||
return staff;
|
||||
}
|
||||
}
|
||||
async findSportStaffByDept(data: z.infer<typeof StaffMethodSchema.findSportStaffByDept>) {
|
||||
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集合的对象
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Prisma } from "@nice/common";
|
|||
const TrainContentArgsSchema:ZodType<Prisma.TrainContentCreateArgs> = z.any()
|
||||
const TrainContentUpdateArgsSchema:ZodType<Prisma.TrainContentUpdateArgs> = z.any()
|
||||
const TrainContentFindManyArgsSchema:ZodType<Prisma.TrainContentFindManyArgs> = z.any()
|
||||
const TrainContentFindFirstArgsSchema:ZodType<Prisma.TrainContentFindFirstArgs> = 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)
|
||||
}),
|
||||
})
|
||||
|
||||
}
|
|
@ -28,7 +28,10 @@ export class TrainContentService extends BaseService<Prisma.TrainContentDelegate
|
|||
const result = await super.findMany(args);
|
||||
return result;
|
||||
}
|
||||
|
||||
async findFirst(args: Prisma.TrainContentFindFirstArgs) {
|
||||
const result = await super.findFirst(args)
|
||||
return result
|
||||
}
|
||||
|
||||
private emitDataChanged(operation: CrudOperation, data: any) {
|
||||
EventBus.emit('dataChanged', {
|
||||
|
|
|
@ -4,9 +4,9 @@ import { StaffModule } from '../staff/staff.module';
|
|||
import { TrainSituationController } from './trainSituation.controller';
|
||||
import { TrainSituationRouter } from './trainSituation.router';
|
||||
import { TrpcService } from '@server/trpc/trpc.service';
|
||||
|
||||
import { SportStandardModule } from '../sport-standard/sportStandard.module';
|
||||
@Module({
|
||||
imports: [StaffModule],
|
||||
imports: [StaffModule,SportStandardModule],
|
||||
controllers: [TrainSituationController],
|
||||
providers: [TrainSituationService,TrainSituationRouter,TrpcService],
|
||||
exports: [TrainSituationService,TrainSituationRouter],
|
||||
|
|
|
@ -14,7 +14,6 @@ export class TrainSituationRouter {
|
|||
private readonly trpc: TrpcService,
|
||||
private readonly trainSituationService: TrainSituationService,
|
||||
) { }
|
||||
|
||||
router = this.trpc.router({
|
||||
create: this.trpc.protectProcedure
|
||||
.input(TrainSituationArgsSchema)
|
||||
|
@ -39,6 +38,33 @@ export class TrainSituationRouter {
|
|||
}))
|
||||
.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)
|
||||
})
|
||||
})
|
||||
|
||||
|
|
|
@ -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<Prisma.TrainSituationDelegate> {
|
||||
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<Prisma.TrainSituationDele
|
|||
}
|
||||
|
||||
// 查找培训情况
|
||||
async findMany(args: Prisma.TrainSituationFindManyArgs): Promise<{
|
||||
id: string;
|
||||
staffId: string;
|
||||
trainContentId: string;
|
||||
mustTrainTime: number;
|
||||
alreadyTrainTime: number;
|
||||
score: number;
|
||||
}[]>
|
||||
async findMany(args: Prisma.TrainSituationFindManyArgs)
|
||||
{
|
||||
const result = await super.findMany(args);
|
||||
return result;
|
||||
|
@ -68,6 +63,76 @@ export class TrainSituationService extends BaseService<Prisma.TrainSituationDele
|
|||
return result
|
||||
}
|
||||
//async createDailyTrainTime()
|
||||
async createTrainSituation(args:{
|
||||
staffId?:string,
|
||||
trainContentId?:string,
|
||||
mustTrainTime?:number,
|
||||
alreadyTrainTime?:number,
|
||||
value?:string,
|
||||
|
||||
projectUnit?:string,
|
||||
personType?:string,
|
||||
projectId?:string,
|
||||
gender?:boolean,
|
||||
age?:number,
|
||||
performance?:number|string,
|
||||
},groupId?:string){
|
||||
console.log("传入的参数",args)
|
||||
const score = await this.sportStandardService.getScore({
|
||||
projectId:args.projectId,
|
||||
gender:args.gender,
|
||||
age:args.age,
|
||||
performance:args.performance,
|
||||
personType:args.personType,
|
||||
projectUnit:args.projectUnit
|
||||
})
|
||||
console.log("计算出的分数",score)
|
||||
const data : Prisma.TrainSituationCreateArgs = {
|
||||
data:{
|
||||
staffId:args.staffId,
|
||||
trainContentId:args.trainContentId,
|
||||
mustTrainTime:args.mustTrainTime,
|
||||
alreadyTrainTime:args.alreadyTrainTime,
|
||||
value:args.value,
|
||||
score:score,
|
||||
groupId:groupId
|
||||
}
|
||||
}
|
||||
console.log("创建的数据",data)
|
||||
const result = await super.create(data);
|
||||
this.emitDataChanged(CrudOperation.CREATED, result);
|
||||
return result;
|
||||
}
|
||||
async createManyTrainSituation(args:{
|
||||
staffId?:string,
|
||||
trainContentId?:string,
|
||||
mustTrainTime?:number,
|
||||
alreadyTrainTime?:number,
|
||||
value?:string,
|
||||
|
||||
projectUnit?:string,
|
||||
personType?:string,
|
||||
projectId?:string,
|
||||
gender?:boolean,
|
||||
age?:number,
|
||||
performance?:number|string,
|
||||
}[]){
|
||||
console.log("传入的参数",args)
|
||||
const groupId = uuidv4();
|
||||
args.forEach(async (item)=>{
|
||||
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) {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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() {
|
|||
>
|
||||
<Form form={scoreForm} onFinish={handleScoreOk}>
|
||||
<Form.Item name="score" label="分数" rules={[{ required: true }]}>
|
||||
<InputNumber min={0} />
|
||||
<Input min={0} />
|
||||
</Form.Item>
|
||||
{ageRanges.map((range, index) => (
|
||||
<Form.Item key={index} name={`standards_${index}`} label={range.label}>
|
||||
<InputNumber min={0} />
|
||||
<Input min={0} />
|
||||
</Form.Item>
|
||||
))}
|
||||
</Form>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 ?
|
||||
<Skeleton></Skeleton> :
|
||||
<div className='w-1/3 my-3 max-h-48 overflow-y-auto'>
|
||||
{sportProjectList?.map((item) => (
|
||||
{sportProjectList?.filter(item=>item.deletedAt === null)?.map((item) => (
|
||||
<div key={item.id} className='w-full flex justify-between p-4 mt-2 bg-white rounded-md'>
|
||||
<div className='font-bold'>{item.name}({item.unit})</div>
|
||||
<span className='text-red-500 cursor-pointer' onClick={() => handleDeleteProject(item.id)}>删除</span>
|
||||
|
|
|
@ -114,7 +114,6 @@ export default function StandardCreateContent() {
|
|||
}
|
||||
}, [data])
|
||||
|
||||
|
||||
return (
|
||||
<Form form={form} layout="vertical">
|
||||
|
||||
|
@ -123,7 +122,7 @@ export default function StandardCreateContent() {
|
|||
<Select
|
||||
style={{ width: 200 }}
|
||||
placeholder="选择考核项目"
|
||||
options={sportProjectList?.map((item) => ({ value: item.id, label: `${item.name}(${item.unit})` })) || []}
|
||||
options={sportProjectList?.filter(item=>item.deletedAt === null)?.map((item) => ({ value: item.id, label: `${item.name}(${item.unit})` })) || []}
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
|
|
|
@ -1,200 +1,54 @@
|
|||
import { Button, Select, Table, Modal, Form, Input, InputNumber, Upload, message } from "antd";
|
||||
import { MagnifyingGlassIcon, ArrowUpTrayIcon, ArrowDownTrayIcon } from "@heroicons/react/24/outline";
|
||||
import { useEffect, useState, useCallback, useContext } from "react";
|
||||
import { Button, Table, Modal, Form, Upload, Skeleton } from "antd";
|
||||
import { useEffect, useState,} from "react";
|
||||
import toast from "react-hot-toast";
|
||||
import React from "react";
|
||||
import _ from "lodash";
|
||||
import { useMainContext } from "../layout/MainProvider";
|
||||
import { EditableRow, EditableContext, EditableCell } from '../sport/context/EditableContext';
|
||||
import * as XLSX from 'xlsx';
|
||||
import { UploadOutlined, DownloadOutlined } from '@ant-design/icons';
|
||||
import { useSportPageContext } from "./sportPageProvider";
|
||||
import SportPageModal from "./sportPageModal";
|
||||
import { api, useTrainSituation } from "@nice/client";
|
||||
|
||||
export default function SportPage() {
|
||||
const { form, setVisible, searchValue, setSearchValue } = useMainContext();
|
||||
const { editingRecord, setEditingRecord } = useMainContext();
|
||||
// 模拟数据,实际使用时应替换为API调用
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [sportsData, setSportsData] = useState([
|
||||
{
|
||||
id: 1,
|
||||
name: "张三",
|
||||
gender: "男",
|
||||
age: 25,
|
||||
unit: "信息部",
|
||||
threeKm: "85",
|
||||
pullUp: 72,
|
||||
shuttle: "65",
|
||||
sitUp: 90,
|
||||
bodyType: "标准",
|
||||
totalScore: 85
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: "李四",
|
||||
gender: "女",
|
||||
age: 22,
|
||||
unit: "市场部",
|
||||
threeKm: "79",
|
||||
pullUp: 58,
|
||||
shuttle: "81",
|
||||
sitUp: 63,
|
||||
bodyType: "偏瘦",
|
||||
totalScore: 78
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: "王五",
|
||||
gender: "男",
|
||||
age: 28,
|
||||
unit: "技术部",
|
||||
threeKm: "92",
|
||||
pullUp: 85,
|
||||
shuttle: "77",
|
||||
sitUp: 88,
|
||||
bodyType: "标准",
|
||||
totalScore: 90
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
name: "赵六",
|
||||
gender: "女",
|
||||
age: 24,
|
||||
unit: "人力资源部",
|
||||
threeKm: "75",
|
||||
pullUp: 56,
|
||||
shuttle: "71",
|
||||
sitUp: 67,
|
||||
bodyType: "偏瘦",
|
||||
totalScore: 75
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
name: "钱七",
|
||||
gender: "男",
|
||||
age: 30,
|
||||
unit: "财务部",
|
||||
threeKm: "68",
|
||||
pullUp: 77,
|
||||
shuttle: "59",
|
||||
sitUp: 82,
|
||||
bodyType: "偏胖",
|
||||
totalScore: 82
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: "孙八",
|
||||
gender: "男",
|
||||
age: 26,
|
||||
unit: "销售部",
|
||||
threeKm: "93",
|
||||
pullUp: 88,
|
||||
shuttle: "84",
|
||||
sitUp: 95,
|
||||
bodyType: "标准",
|
||||
totalScore: 92
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
name: "周九",
|
||||
gender: "女",
|
||||
age: 23,
|
||||
unit: "客服部",
|
||||
threeKm: "73",
|
||||
pullUp: 60,
|
||||
shuttle: "65",
|
||||
sitUp: 75,
|
||||
bodyType: "标准",
|
||||
totalScore: 79
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
name: "吴十",
|
||||
gender: "男",
|
||||
age: 32,
|
||||
unit: "研发部",
|
||||
threeKm: "82",
|
||||
pullUp: 70,
|
||||
shuttle: "68",
|
||||
sitUp: 79,
|
||||
bodyType: "偏胖",
|
||||
totalScore: 80
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
name: "郑十一",
|
||||
gender: "女",
|
||||
age: 27,
|
||||
unit: "市场营销部",
|
||||
threeKm: "62",
|
||||
pullUp: 53,
|
||||
shuttle: "69",
|
||||
sitUp: 71,
|
||||
bodyType: "偏瘦",
|
||||
totalScore: 76
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
name: "刘十二",
|
||||
gender: "男",
|
||||
age: 29,
|
||||
unit: "产品部",
|
||||
threeKm: "87",
|
||||
pullUp: 82,
|
||||
shuttle: "78",
|
||||
sitUp: 86,
|
||||
bodyType: "标准",
|
||||
totalScore: 88
|
||||
}
|
||||
]);
|
||||
// 新增搜索功能
|
||||
const filteredData = sportsData.filter(item =>
|
||||
item.name.toLowerCase().includes(searchValue.toLowerCase()) ||
|
||||
item.unit.toLowerCase().includes(searchValue.toLowerCase())
|
||||
);
|
||||
const handleNew = () => {
|
||||
form.resetFields();
|
||||
setEditingRecord(null);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
// 计算总成绩的函数
|
||||
const calculateTotalScore = (record) => {
|
||||
// 确保所有值都转为数字
|
||||
const threeKmScore = parseInt(record.threeKm, 10) || 0;
|
||||
const pullUpScore = parseInt(record.pullUp, 10) || 0;
|
||||
const shuttleScore = parseInt(record.shuttle, 10) || 0;
|
||||
const sitUpScore = parseInt(record.sitUp, 10) || 0;
|
||||
|
||||
// 计算总分
|
||||
return threeKmScore + pullUpScore + shuttleScore + sitUpScore;
|
||||
};
|
||||
|
||||
// 初始化时计算所有记录的总成绩
|
||||
const [sportsData, setSportsData] = useState([]);
|
||||
const { sportProjectList, handlNewScoreOpen, staffsWithScore, staffsWithScoreLoading } = useSportPageContext()
|
||||
const {deleteSameGroupTrainSituation} = useTrainSituation()
|
||||
useEffect(() => {
|
||||
const updatedData = sportsData.map(record => ({
|
||||
...record,
|
||||
totalScore: calculateTotalScore(record)
|
||||
}));
|
||||
setSportsData(updatedData);
|
||||
}, []);
|
||||
|
||||
const handleSave = (row) => {
|
||||
const newData = [...sportsData];
|
||||
const index = newData.findIndex(item => row.id === item.id);
|
||||
const item = newData[index];
|
||||
|
||||
// 创建更新后的记录
|
||||
const updatedRecord = { ...item, ...row };
|
||||
|
||||
// 重新计算总成绩
|
||||
updatedRecord.totalScore = calculateTotalScore(updatedRecord);
|
||||
|
||||
// 更新数据
|
||||
newData.splice(index, 1, updatedRecord);
|
||||
setSportsData(newData);
|
||||
toast.success("保存成功");
|
||||
if (!staffsWithScore) return;
|
||||
const newSportsData = []
|
||||
staffsWithScore.forEach(item => {
|
||||
const groupedTrainSituations = {};
|
||||
item.trainSituations.forEach(train => {
|
||||
if(train.groupId === null) return
|
||||
const groupId = train.groupId;
|
||||
if (!groupedTrainSituations[groupId]) {
|
||||
groupedTrainSituations[groupId] = {
|
||||
totalScore: 0,
|
||||
trainScore: {},
|
||||
situations: []
|
||||
};
|
||||
}
|
||||
groupedTrainSituations[groupId].totalScore += train.score;
|
||||
groupedTrainSituations[groupId].trainScore[train.trainContent.title] = `${train.score}分(${train.value})`;
|
||||
groupedTrainSituations[groupId].situations.push(train);
|
||||
console.log(groupedTrainSituations[groupId])
|
||||
newSportsData.push({
|
||||
id: item.id,
|
||||
name: item.username,
|
||||
gender: item.sex ? "男" : "女",
|
||||
bodyType: "标准",
|
||||
age: item.age,
|
||||
unit: item.department?.name,
|
||||
situationIds: groupedTrainSituations[groupId].situations.map(train => train.id),
|
||||
totalScore:groupedTrainSituations[groupId].totalScore,
|
||||
...groupedTrainSituations[groupId].trainScore,
|
||||
groupId
|
||||
})
|
||||
});
|
||||
|
||||
});
|
||||
console.log(newSportsData)
|
||||
setSportsData(newSportsData);
|
||||
}, [staffsWithScore])
|
||||
const columns = [
|
||||
{
|
||||
title: "姓名",
|
||||
|
@ -216,30 +70,12 @@ export default function SportPage() {
|
|||
dataIndex: "unit",
|
||||
key: "unit",
|
||||
},
|
||||
{
|
||||
title: "三公里",
|
||||
dataIndex: "threeKm",
|
||||
key: "threeKm",
|
||||
...sportProjectList?.filter(item=>item.deletedAt === null).map((item) => ({
|
||||
title: item.name,
|
||||
dataIndex: item.name,
|
||||
key: item.name,
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
title: "单杠",
|
||||
dataIndex: "pullUp",
|
||||
key: "pullUp",
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
title: "30x2折返跑",
|
||||
dataIndex: "shuttle",
|
||||
key: "shuttle",
|
||||
editable: true,
|
||||
},
|
||||
{
|
||||
title: "仰卧卷腹",
|
||||
dataIndex: "sitUp",
|
||||
key: "sitUp",
|
||||
editable: true,
|
||||
},
|
||||
})),
|
||||
{
|
||||
title: "BMI",
|
||||
dataIndex: "bodyType",
|
||||
|
@ -252,58 +88,22 @@ export default function SportPage() {
|
|||
key: "totalScore",
|
||||
editable: true,
|
||||
},
|
||||
|
||||
{
|
||||
title: "操作",
|
||||
key: "action",
|
||||
render: (_, record) => (
|
||||
<div className="flex space-x-2">
|
||||
<Button
|
||||
type="primary"
|
||||
key={record.id}
|
||||
onClick={() => handleEdit(record)}
|
||||
>
|
||||
编辑
|
||||
</Button>
|
||||
<Button
|
||||
danger
|
||||
onClick={() => handleDelete(record.id)}
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
<Button type="primary" key={record.groupId} onClick={() => handleEdit(record)}>编辑</Button>
|
||||
<Button danger onClick={() => handleDelete(record)}>删除</Button>
|
||||
</div>
|
||||
),
|
||||
}
|
||||
];
|
||||
|
||||
const mergedColumns = columns.map(col => {
|
||||
if (!col.editable) {
|
||||
return col;
|
||||
}
|
||||
return {
|
||||
...col,
|
||||
onCell: record => ({
|
||||
record,
|
||||
editable: col.editable,
|
||||
dataIndex: col.dataIndex,
|
||||
title: col.title,
|
||||
handleSave,
|
||||
}),
|
||||
};
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (editingRecord) {
|
||||
form.setFieldsValue(editingRecord);
|
||||
}
|
||||
}, [editingRecord, form]);
|
||||
|
||||
const handleEdit = (record) => {
|
||||
setEditingRecord(record);
|
||||
form.setFieldsValue(record);
|
||||
setVisible(true);
|
||||
};
|
||||
|
||||
const handleDelete = (id) => {
|
||||
};
|
||||
const handleDelete = (record) => {
|
||||
Modal.confirm({
|
||||
title: '确认删除',
|
||||
content: '确定要删除该记录吗?',
|
||||
|
@ -312,7 +112,12 @@ export default function SportPage() {
|
|||
onOk: async () => {
|
||||
try {
|
||||
// 模拟删除操作
|
||||
setSportsData(sportsData.filter(item => item.id !== id));
|
||||
console.log(record)
|
||||
await deleteSameGroupTrainSituation.mutateAsync(
|
||||
{
|
||||
groupId:record.groupId
|
||||
}
|
||||
)
|
||||
toast.success("删除成功");
|
||||
} catch (error) {
|
||||
console.error('删除记录时出错:', error);
|
||||
|
@ -321,14 +126,6 @@ export default function SportPage() {
|
|||
}
|
||||
});
|
||||
};
|
||||
|
||||
const components = {
|
||||
body: {
|
||||
row: EditableRow,
|
||||
cell: EditableCell,
|
||||
},
|
||||
};
|
||||
|
||||
// 导出成绩为Excel
|
||||
const handleExport = () => {
|
||||
// 创建工作簿
|
||||
|
@ -386,19 +183,12 @@ export default function SportPage() {
|
|||
|
||||
return (
|
||||
<div className="p-2 min-h-screen bg-gradient-to-br">
|
||||
{/* {JSON.stringify(staffsWithScore)} */}
|
||||
<Form>
|
||||
<div className="p-4 h-full flex flex-col">
|
||||
<div className="max-w-full mx-auto flex-1 flex flex-col">
|
||||
<div className="flex justify-between mb-4 space-x-4 items-center">
|
||||
<div className="text-2xl">成绩总览</div>
|
||||
<div className="relative w-1/3">
|
||||
<Input
|
||||
placeholder="输入姓名搜索"
|
||||
onChange={(e) => setSearchValue(e.target.value)}
|
||||
className="pl-10 w-full border"
|
||||
/>
|
||||
<MagnifyingGlassIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 h-5 w-5 " />
|
||||
</div>
|
||||
<div className="flex space-x-2">
|
||||
<Upload
|
||||
beforeUpload={handleImport}
|
||||
|
@ -415,7 +205,7 @@ export default function SportPage() {
|
|||
</Button>
|
||||
<Button
|
||||
type="primary"
|
||||
onClick={handleNew}
|
||||
onClick={handlNewScoreOpen}
|
||||
className="font-bold py-2 px-4 rounded"
|
||||
>
|
||||
新建成绩
|
||||
|
@ -423,14 +213,13 @@ export default function SportPage() {
|
|||
</div>
|
||||
</div>
|
||||
|
||||
{isLoading ? (
|
||||
<div>加载中...</div>
|
||||
{staffsWithScoreLoading ? (
|
||||
<Skeleton></Skeleton>
|
||||
) : (
|
||||
<Table
|
||||
components={components}
|
||||
rowClassName={() => 'editable-row'}
|
||||
columns={mergedColumns}
|
||||
dataSource={filteredData}
|
||||
columns={columns}
|
||||
dataSource={sportsData}
|
||||
tableLayout="fixed"
|
||||
rowKey="id"
|
||||
pagination={{
|
||||
|
@ -443,6 +232,7 @@ export default function SportPage() {
|
|||
</div>
|
||||
</div>
|
||||
</Form>
|
||||
<SportPageModal />
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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 (
|
||||
<Modal
|
||||
title="添加考核成绩"
|
||||
onOk={newScoreForm.submit}
|
||||
visible={isNewScoreModalVisible}
|
||||
onCancel={handleNewScoreCancel}
|
||||
>
|
||||
<Form
|
||||
form={newScoreForm}
|
||||
name="dynamic_form"
|
||||
onFinish={handleNewScoreOk}
|
||||
className="p-4 space-y-4"
|
||||
>
|
||||
<Form.Item
|
||||
name="user"
|
||||
label="选择人员"
|
||||
rules={[{ required: true, message: '请选择人员!' }]}
|
||||
>
|
||||
<Select
|
||||
placeholder="选择人员"
|
||||
options={staffsLoading ? [] : staffs?.map((item) => ({
|
||||
label: item.username,
|
||||
value: item.id
|
||||
})) || []}
|
||||
>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
{sportProjectList?.filter(item=>item.deletedAt === null).map((field) => (
|
||||
<div key={field.id} className="flex w-full space-x-4">
|
||||
<Form.Item
|
||||
label={`${field.name}(${field.unit})`}
|
||||
name={['sports', field.id, 'score']}
|
||||
rules={[{ required: true, message: '请输入成绩!' }]}
|
||||
>
|
||||
<Input className="w-full" placeholder="请输入成绩" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
))}
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
|
@ -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<SportPageContextType | null>(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 (
|
||||
<SportPageContext.Provider
|
||||
value={{
|
||||
sportProjectList: sportProjectList?.map((item) => ({
|
||||
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}
|
||||
</SportPageContext.Provider>
|
||||
);
|
||||
}
|
||||
export const useSportPageContext = () => {
|
||||
const context = useContext(SportPageContext);
|
||||
if (!context) {
|
||||
throw new Error("useSportPageContext must be used within SportPageProvider");
|
||||
}
|
||||
return context;
|
||||
};
|
|
@ -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 (
|
||||
<Modal
|
||||
title="更改考核成绩"
|
||||
onOk={newScoreForm.submit}
|
||||
visible={isNewScoreModalVisible}
|
||||
onCancel={handleNewScoreCancel}
|
||||
>
|
||||
<Form
|
||||
form={newScoreForm}
|
||||
name="dynamic_form"
|
||||
onFinish={handleUpdateScoreOk}
|
||||
className="p-4 space-y-4"
|
||||
>
|
||||
{sportProjectList.map((field) => (
|
||||
<div key={field.id} className="flex w-full space-x-4">
|
||||
<Form.Item
|
||||
label={`${field.name}(${field.unit})`}
|
||||
name={['sports', field.id, 'score']}
|
||||
rules={[{ required: true, message: '请输入成绩!' }]}
|
||||
>
|
||||
<Input className="w-full" placeholder="请输入成绩" />
|
||||
</Form.Item>
|
||||
</div>
|
||||
))}
|
||||
</Form>
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
|
@ -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:<SportPage></SportPage>
|
||||
element:<SportPageProvider>
|
||||
<SportPage></SportPage>
|
||||
</SportPageProvider>
|
||||
}
|
||||
]
|
||||
},
|
||||
|
|
|
@ -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";
|
||||
|
||||
|
||||
|
@ -24,8 +23,28 @@ export function useTrainSituation(){
|
|||
}
|
||||
})
|
||||
|
||||
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
|
||||
}
|
||||
}
|
|
@ -341,7 +341,8 @@ model TrainContent {
|
|||
title String @map("title")
|
||||
trainSituations TrainSituation[]
|
||||
trainPlans TrainPlan[] @relation("TrainPlanContent")
|
||||
|
||||
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]) // 指向自身
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
@ -382,7 +389,7 @@ model DailyTrainTime {
|
|||
model Position {
|
||||
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")
|
||||
|
@ -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,7 +494,6 @@ model TrainPlan {
|
|||
@@map("train_plan")
|
||||
}
|
||||
|
||||
|
||||
model SportProject {
|
||||
id String @id @default(cuid())
|
||||
name String @map("name") // 项目名称
|
||||
|
@ -495,10 +501,13 @@ model SportProject {
|
|||
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")
|
||||
}
|
||||
|
||||
|
@ -518,6 +527,6 @@ model SportStandard {
|
|||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
@@unique([projectId, gender, personType])
|
||||
@@unique([projectId, gender, personType], name: "projectId_gender_personType")
|
||||
@@map("sport_standard")
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
import { TrainContent, TrainSituation } from "@prisma/client";
|
||||
|
||||
export type trainSituationDto = TrainSituation & {
|
||||
trainContent: TrainContent
|
||||
}
|
|
@ -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 = {
|
||||
|
|
Loading…
Reference in New Issue