This commit is contained in:
Rao 2025-03-27 08:50:22 +08:00
parent 656ba75ce7
commit 96952ea0ce
26 changed files with 750 additions and 406 deletions

View File

@ -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)
}),
})
}

View File

@ -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)

View File

@ -4,11 +4,20 @@ import { SportStandardService } from "./sportStandard.service";
import { z, ZodType } from "zod";
import { Prisma } from "@nice/common";
const SportStandardArgsSchema:ZodType<Prisma.SportStandardCreateArgs> = z.any()
const SportStandardUpdateArgsSchema:ZodType<Prisma.SportStandardUpdateArgs> = z.any()
const SportStandardFindManyArgsSchema:ZodType<Prisma.SportStandardFindManyArgs> = z.any()
const SportStandardCreateStandardArgsSchema:ZodType<Prisma.SportStandardCreateArgs> = z.any()
const SportStandardUpdateStandardArgsSchema:ZodType<Prisma.SportStandardUpdateArgs> = z.any()
const SportStandardArgsSchema: ZodType<Prisma.SportStandardCreateArgs> = z.any()
const SportStandardUpdateArgsSchema: ZodType<Prisma.SportStandardUpdateArgs> = z.any()
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;
@ -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);
}),
})
}

View File

@ -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() {
@ -125,8 +135,8 @@ export class SportStandardService extends BaseService<Prisma.SportStandardDelega
this.emitDataChanged(CrudOperation.UPDATED, result)
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]) {
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<typeof GetScoreArgsSchema>) {
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')}`;
}
}

View File

@ -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);
}),
});
}

View File

@ -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集合的对象

View File

@ -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)
}),
})
}

View File

@ -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', {

View File

@ -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],

View File

@ -4,9 +4,9 @@ import { TrainSituationService } from "./trainSituation.service";
import { z, ZodType } from "zod";
import { Prisma } from "@nice/common";
const TrainSituationArgsSchema:ZodType<Prisma.TrainSituationCreateArgs> = z.any()
const TrainSituationUpdateArgsSchema:ZodType<Prisma.TrainSituationUpdateArgs> = z.any()
const TrainSituationFindManyArgsSchema:ZodType<Prisma.TrainSituationFindManyArgs> = z.any()
const TrainSituationArgsSchema: ZodType<Prisma.TrainSituationCreateArgs> = z.any()
const TrainSituationUpdateArgsSchema: ZodType<Prisma.TrainSituationUpdateArgs> = z.any()
const TrainSituationFindManyArgsSchema: ZodType<Prisma.TrainSituationFindManyArgs> = 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)
})
})

View File

@ -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,7 +63,77 @@ 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) {
EventBus.emit('dataChanged', {

View File

@ -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();

View File

@ -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>

View File

@ -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,

View File

@ -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>

View File

@ -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>

View File

@ -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,38 +126,30 @@ export default function SportPage() {
}
});
};
const components = {
body: {
row: EditableRow,
cell: EditableCell,
},
};
// 导出成绩为Excel
const handleExport = () => {
// 创建工作簿
const wb = XLSX.utils.book_new();
// 准备导出数据移除id字段
const exportData = sportsData.map(({ id, ...rest }) => rest);
// 转换为工作表
const ws = XLSX.utils.json_to_sheet(exportData);
// 将工作表添加到工作簿
XLSX.utils.book_append_sheet(wb, ws, "成绩数据");
// 生成Excel文件并下载
XLSX.writeFile(wb, "体育成绩表.xlsx");
toast.success("成绩导出成功");
};
// 导入成绩
const handleImport = (file) => {
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = new Uint8Array(e.target.result);
@ -368,7 +165,7 @@ export default function SportPage() {
// 确保totalScore是计算所有项目成绩的和
totalScore: calculateTotalScore(item)
}));
// 更新数据
setSportsData([...sportsData, ...importedData]);
toast.success(`成功导入${importedData.length}条记录`);
@ -377,37 +174,30 @@ export default function SportPage() {
toast.error("导入失败,请检查文件格式");
}
};
reader.readAsArrayBuffer(file);
// 防止自动上传
return false;
};
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
<Upload
beforeUpload={handleImport}
showUploadList={false}
accept=".xlsx,.xls"
>
<Button icon={<UploadOutlined />}></Button>
</Upload>
<Button
<Button
icon={<DownloadOutlined />}
onClick={handleExport}
>
@ -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>
);
}

View File

@ -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>
)
}

View File

@ -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;
};

View File

@ -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>
)
}

View File

@ -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>
}
]
},

View File

@ -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
}
}

View File

@ -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")
}

View File

@ -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;

View File

@ -0,0 +1,5 @@
import { TrainContent, TrainSituation } from "@prisma/client";
export type trainSituationDto = TrainSituation & {
trainContent: TrainContent
}

View File

@ -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 = {