diff --git a/.vscode/extensions.json b/.vscode/extensions.json old mode 100644 new mode 100755 diff --git a/.vscode/settings.json b/.vscode/settings.json old mode 100644 new mode 100755 diff --git a/apps/server/src/models/base/base.tree.service.ts b/apps/server/src/models/base/base.tree.service.ts index f62aecc..1702452 100755 --- a/apps/server/src/models/base/base.tree.service.ts +++ b/apps/server/src/models/base/base.tree.service.ts @@ -95,7 +95,7 @@ export class BaseTreeService< ? [ ...( await transaction[this.ancestryType].findMany({ - where: { descendantId: anyArgs.data.parentId }, + where: { descendantId: anyArgs.data.parentId,}, select: { ancestorId: true, relDepth: true }, }) ).map(({ ancestorId, relDepth }) => ({ diff --git a/apps/server/src/models/car/car.controller.ts b/apps/server/src/models/car/car.controller.ts new file mode 100755 index 0000000..62ed5c6 --- /dev/null +++ b/apps/server/src/models/car/car.controller.ts @@ -0,0 +1,9 @@ +import { Controller } from '@nestjs/common'; + + +import { CarService } from './car.service'; + +@Controller('car') +export class CarController { + constructor(private readonly carService: CarService) {} +} diff --git a/apps/server/src/models/car/car.module.ts b/apps/server/src/models/car/car.module.ts new file mode 100755 index 0000000..d3fb14d --- /dev/null +++ b/apps/server/src/models/car/car.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { QueueModule } from '@server/queue/queue.module'; +import { MessageModule } from '../message/message.module'; +import { CarRouter } from './car.router'; +import { CarController } from './car.controller'; +import { CarService } from './car.service'; +import { RoleMapModule } from '../rbac/rbac.module'; +import { DepartmentModule } from '../department/department.module'; +import { DepartmentService } from '../department/department.service'; + +@Module({ + imports: [QueueModule, RoleMapModule, MessageModule, DepartmentModule], + providers: [CarService, CarRouter, TrpcService, DepartmentService], + exports: [CarRouter, CarService], + controllers: [CarController], +}) +export class CarModule {} diff --git a/apps/server/src/models/car/car.router.ts b/apps/server/src/models/car/car.router.ts new file mode 100755 index 0000000..7e5f955 --- /dev/null +++ b/apps/server/src/models/car/car.router.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { CarService } from './car.service'; +import { z, ZodType } from 'zod'; +import { Prisma } from '@nice/common'; + +const CarCreateArgsSchema: ZodType = z.any(); +const CarUpdateArgsSchema: ZodType = z.any(); +const CarFindManyArgsSchema: ZodType = z.any(); +const CarWhereUniqueInputSchema: ZodType = z.any(); + +@Injectable() +export class CarRouter { + constructor( + private readonly trpc: TrpcService, + private readonly carService: CarService, + ) {} + + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(CarCreateArgsSchema) + .mutation(async ({ input }) => { + return await this.carService.create(input); + }), + + update: this.trpc.protectProcedure + .input(CarUpdateArgsSchema) + .mutation(async ({ input }) => { + return await this.carService.update(input); + }), + softDeleteByIds: this.trpc.protectProcedure + .input( + z.object({ + ids: z.array(z.string()), + data: CarFindManyArgsSchema.nullish(), + }), + ) + .mutation(async ({ input }) => { + return await this.carService.softDeleteByIds(input.ids); + }), + delete: this.trpc.protectProcedure + .input(CarWhereUniqueInputSchema) + .mutation(async ({ input }) => { + return await this.carService.delete({ where: input }); + }), + + findMany: this.trpc.procedure + .input(CarFindManyArgsSchema) + .query(async ({ input }) => { + return await this.carService.findMany(input); + }), + + + findWithScores: this.trpc.procedure + .input(z.number()) + .query(async ({ input }) => { + return await this.carService.findWithScores(input); + }), + + search: this.trpc.procedure + .input(z.object({ + name: z.string().optional(), + courseId: z.number().optional(), + limit: z.number().optional() + })) + .query(async ({ input }) => { + return await this.carService.search(input); + }), + }); +} \ No newline at end of file diff --git a/apps/server/src/models/car/car.service.ts b/apps/server/src/models/car/car.service.ts new file mode 100755 index 0000000..b776e3b --- /dev/null +++ b/apps/server/src/models/car/car.service.ts @@ -0,0 +1,85 @@ +import { Injectable } from '@nestjs/common'; +import { + db, + Prisma, + UserProfile, + VisitType, + Post, + PostType, + RolePerms, + ResPerm, + ObjectType, + CourseMethodSchema, +} from '@nice/common'; +import { MessageService } from '../message/message.service'; +import { BaseService } from '../base/base.service'; +import { DepartmentService } from '../department/department.service'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; +import { BaseTreeService } from '../base/base.tree.service'; +@Injectable() +export class CarService extends BaseTreeService { + constructor( + private readonly messageService: MessageService, + private readonly departmentService: DepartmentService, + ) { + super(db, ObjectType.CAR, 'carAncestry', true); + } + async create( + args: Prisma.CarCreateArgs, + params?: { staff?: UserProfile; tx?: Prisma.TransactionClient }, + ) { + const result = await db.car.create(args); + EventBus.emit('dataChanged', { + type: ObjectType.CAR, + operation: CrudOperation.CREATED, + data: result, + }); + return result; + } + async update(args: Prisma.CarUpdateArgs, staff?: UserProfile) { + const result = await db.car.update(args); + EventBus.emit('dataChanged', { + type: ObjectType.CAR, + operation: CrudOperation.UPDATED, + data: result, + }); + return result; + } + /** + * 查找赛车并包含比赛信息 + */ + async findWithScores(clubId: number) { + return await db.car.findUnique({ + where: { id: clubId }, + select: { + name:true, + club:true + } + }); + } + /** + * 搜索 + */ + async search(params: { name?: string, clubId?: number, limit?: number }) { + const { name, clubId, limit = 30 } = params; + + return await db.car.findMany({ + where: { + ...(name && { name: { contains: name } }), + ...(clubId && { clubId }) + }, + select: { + clubId: true, + name:true + }, + take: limit + }); + } + private emitDataChangedEvent(data: any, operation: CrudOperation) { + EventBus.emit("dataChanged", { + type: this.objectType, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/course/course.controller.ts b/apps/server/src/models/course/course.controller.ts new file mode 100755 index 0000000..d729f29 --- /dev/null +++ b/apps/server/src/models/course/course.controller.ts @@ -0,0 +1,9 @@ +import { Controller } from '@nestjs/common'; + + +import { CourseService } from './course.service'; + +@Controller('course') +export class CourseController { + constructor(private readonly courseService: CourseService) {} +} diff --git a/apps/server/src/models/course/course.module.ts b/apps/server/src/models/course/course.module.ts new file mode 100755 index 0000000..95b487d --- /dev/null +++ b/apps/server/src/models/course/course.module.ts @@ -0,0 +1,19 @@ +import { Module } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { QueueModule } from '@server/queue/queue.module'; +import { MessageModule } from '../message/message.module'; +import { CourseRouter } from './course.router'; +import { CourseController } from './course.controller'; +import { CourseService } from './course.service'; +import { RoleMapModule } from '../rbac/rbac.module'; +import { StudentModule } from '../student/student.module'; +import { DepartmentModule } from '../department/department.module'; +import { DepartmentService } from '../department/department.service'; + +@Module({ + imports: [QueueModule, RoleMapModule, MessageModule,StudentModule,DepartmentModule], + providers: [CourseService, CourseRouter, TrpcService,DepartmentService], + exports: [CourseRouter, CourseService], + controllers: [CourseController], +}) +export class CourseModule {} diff --git a/apps/server/src/models/course/course.router.ts b/apps/server/src/models/course/course.router.ts new file mode 100755 index 0000000..b5fca56 --- /dev/null +++ b/apps/server/src/models/course/course.router.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { CourseService } from './course.service'; +import { z, ZodType } from 'zod'; +import { Prisma } from '@nice/common'; + +const CourseCreateArgsSchema: ZodType = z.any(); +const CourseUpdateArgsSchema: ZodType = z.any(); +const CourseFindManyArgsSchema: ZodType = z.any(); +const CourseWhereUniqueInputSchema: ZodType = z.any(); + +@Injectable() +export class CourseRouter { + constructor( + private readonly trpc: TrpcService, + private readonly courseService: CourseService, + ) {} + + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(CourseCreateArgsSchema) + .mutation(async ({ input }) => { + return await this.courseService.create(input); + }), + + update: this.trpc.protectProcedure + .input(CourseUpdateArgsSchema) + .mutation(async ({ input }) => { + return await this.courseService.update(input); + }), + softDeleteByIds: this.trpc.protectProcedure + .input( + z.object({ + ids: z.array(z.string()), + data: CourseUpdateArgsSchema.nullish(), + }), + ) + .mutation(async ({ input }) => { + return await this.courseService.softDeleteByIds(input.ids); + }), + delete: this.trpc.protectProcedure + .input(CourseWhereUniqueInputSchema) + .mutation(async ({ input }) => { + return await this.courseService.delete({ where: input }); + }), + + findMany: this.trpc.procedure + .input(CourseFindManyArgsSchema) + .query(async ({ input }) => { + return await this.courseService.findMany(input); + }), + + + findWithScores: this.trpc.procedure + .input(z.number()) + .query(async ({ input }) => { + return await this.courseService.findWithScores(input); + }), + + search: this.trpc.procedure + .input(z.object({ + name: z.string().optional(), + courseId: z.number().optional(), + limit: z.number().optional() + })) + .query(async ({ input }) => { + return await this.courseService.search(input); + }), + }); +} \ No newline at end of file diff --git a/apps/server/src/models/course/course.service.ts b/apps/server/src/models/course/course.service.ts new file mode 100755 index 0000000..b2bd457 --- /dev/null +++ b/apps/server/src/models/course/course.service.ts @@ -0,0 +1,89 @@ +import { Injectable } from '@nestjs/common'; +import { + db, + Prisma, + UserProfile, + VisitType, + Post, + PostType, + RolePerms, + ResPerm, + ObjectType, + CourseMethodSchema, +} from '@nice/common'; +import { MessageService } from '../message/message.service'; +import { BaseService } from '../base/base.service'; +import { DepartmentService } from '../department/department.service'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; +import { BaseTreeService } from '../base/base.tree.service'; +import { z } from 'zod'; +@Injectable() +export class CourseService extends BaseTreeService { + constructor( + private readonly messageService: MessageService, + private readonly departmentService: DepartmentService, + ) { + super(db, ObjectType.COURSE, 'courseAncestry', true); + } + async create( + args: Prisma.CourseCreateArgs, + params?: { staff?: UserProfile; tx?: Prisma.TransactionClient }, + ) { + const result = await super.create(args); + EventBus.emit('dataChanged', { + type: ObjectType.COURSE, + operation: CrudOperation.CREATED, + data: result, + }); + return result; + } + async update(args: Prisma.CourseUpdateArgs, staff?: UserProfile) { + const result = await super.update(args); + EventBus.emit('dataChanged', { + type: ObjectType.COURSE, + operation: CrudOperation.UPDATED, + data: result, + }); + return result; + } + /** + * 查找学生并包含成绩信息 + */ + async findWithScores(courseId: number) { + return await db.course.findUnique({ + where: { id: courseId }, + include: { + scores: { + include: { + course: true, + student: true, + } + } + } + }); + } + /** + * 搜索学生 + */ + async search(params: { name?: string, courseId?: number, limit?: number }) { + const { name, courseId, limit = 30 } = params; + + return await db.course.findMany({ + where: { + ...(name && { name: { contains: name } }), + ...(courseId && { courseId }) + }, + include: { + scores: true + }, + take: limit + }); + } + private emitDataChangedEvent(data: any, operation: CrudOperation) { + EventBus.emit("dataChanged", { + type: this.objectType, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/driver/driver.controller.ts b/apps/server/src/models/driver/driver.controller.ts new file mode 100755 index 0000000..a647e67 --- /dev/null +++ b/apps/server/src/models/driver/driver.controller.ts @@ -0,0 +1,9 @@ +import { Controller } from '@nestjs/common'; + + +import { DriverService } from './driver.service'; + +@Controller('driver') +export class DriverController { + constructor(private readonly driverService: DriverService) {} +} diff --git a/apps/server/src/models/driver/driver.module.ts b/apps/server/src/models/driver/driver.module.ts new file mode 100755 index 0000000..19070f4 --- /dev/null +++ b/apps/server/src/models/driver/driver.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { QueueModule } from '@server/queue/queue.module'; +import { MessageModule } from '../message/message.module'; +import { DriverRouter } from './driver.router'; +import { DriverController } from './driver.controller'; +import { DriverService } from './driver.service'; +import { RoleMapModule } from '../rbac/rbac.module'; +import { DepartmentModule } from '../department/department.module'; +import { DepartmentService } from '../department/department.service'; + +@Module({ + imports: [QueueModule, RoleMapModule, MessageModule,,DepartmentModule,DriverModule], + providers: [DriverService, DriverRouter, TrpcService,DepartmentService], + exports: [DriverRouter, DriverService], + controllers: [DriverController], +}) +export class DriverModule {} diff --git a/apps/server/src/models/driver/driver.router.ts b/apps/server/src/models/driver/driver.router.ts new file mode 100755 index 0000000..6e9c1b0 --- /dev/null +++ b/apps/server/src/models/driver/driver.router.ts @@ -0,0 +1,70 @@ +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { DriverService } from './driver.service'; +import { z, ZodType } from 'zod'; +import { Prisma } from '@nice/common'; + +const CourseCreateArgsSchema: ZodType = z.any(); +const CourseUpdateArgsSchema: ZodType = z.any(); +const CourseFindManyArgsSchema: ZodType = z.any(); +const CourseWhereUniqueInputSchema: ZodType = z.any(); + +@Injectable() +export class DriverRouter { + constructor( + private readonly trpc: TrpcService, + private readonly driverService: DriverService, + ) {} + + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(CourseCreateArgsSchema) + .mutation(async ({ input }) => { + return await this.driverService.create(input); + }), + + // update: this.trpc.protectProcedure + // .input(CourseUpdateArgsSchema) + // .mutation(async ({ input }) => { + // return await this.driverService.update(input); + // }), + softDeleteByIds: this.trpc.protectProcedure + .input( + z.object({ + ids: z.array(z.string()), + data: CourseUpdateArgsSchema.nullish(), + }), + ) + .mutation(async ({ input }) => { + return await this.driverService.softDeleteByIds(input.ids); + }), + // delete: this.trpc.protectProcedure + // .input(CourseWhereUniqueInputSchema) + // .mutation(async ({ input }) => { + // return await this.driverService.delete({ where: input }); + // }), + + // findMany: this.trpc.procedure + // .input(CourseFindManyArgsSchema) + // .query(async ({ input }) => { + // return await this.driverService.findMany(input); + // }), + + + findWithScores: this.trpc.procedure + .input(z.number()) + .query(async ({ input }) => { + return await this.driverService.findWithScores(input); + }), + + search: this.trpc.procedure + .input(z.object({ + name: z.string().optional(), + courseId: z.number().optional(), + limit: z.number().optional() + })) + .query(async ({ input }) => { + return await this.driverService.search(input); + }), + }); +} \ No newline at end of file diff --git a/apps/server/src/models/driver/driver.service.ts b/apps/server/src/models/driver/driver.service.ts new file mode 100755 index 0000000..141334a --- /dev/null +++ b/apps/server/src/models/driver/driver.service.ts @@ -0,0 +1,86 @@ +import { Injectable } from '@nestjs/common'; +import { + db, + Prisma, + UserProfile, + VisitType, + Post, + PostType, + RolePerms, + ResPerm, + ObjectType, + CourseMethodSchema, +} from '@nice/common'; +import { MessageService } from '../message/message.service'; +import { BaseService } from '../base/base.service'; +import { DepartmentService } from '../department/department.service'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; +import { BaseTreeService } from '../base/base.tree.service'; +import { z } from 'zod'; +@Injectable() +export class DriverService extends BaseTreeService { + constructor( + private readonly messageService: MessageService, + private readonly departmentService: DepartmentService, + ) { + super(db, ObjectType.DRIVER, 'driverAncestry', true); + } + async create( + args: Prisma.DriverCreateArgs, + params?: { staff?: UserProfile; tx?: Prisma.TransactionClient }, + ) { + const result = await db.driver.create(args); + EventBus.emit('dataChanged', { + type: ObjectType.DRIVER, + operation: CrudOperation.CREATED, + data: result, + }); + return result; + } + async update(args: Prisma.DriverUpdateArgs, staff?: UserProfile) { + const result = await db.driver.update(args); + EventBus.emit('dataChanged', { + type: ObjectType.DRIVER, + operation: CrudOperation.UPDATED, + data: result, + }); + return result; + } + /** + * 查找赛车并包含比赛信息 + */ + async findWithScores(clubId: number) { + return await db.driver.findUnique({ + where: { id: clubId }, + select: { + name:true, + club:true + } + }); + } + /** + * 搜索学生 + */ + async search(params: { name?: string, clubId?: number, limit?: number }) { + const { name, clubId, limit = 30 } = params; + + return await db.driver.findMany({ + where: { + ...(name && { name: { contains: name } }), + ...(clubId && { clubId }) + }, + select: { + clubId: true, + name:true + }, + take: limit + }); + } + private emitDataChangedEvent(data: any, operation: CrudOperation) { + EventBus.emit("dataChanged", { + type: this.objectType, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/goods/goods.controller.ts b/apps/server/src/models/goods/goods.controller.ts old mode 100644 new mode 100755 index e259edf..2cb9d91 --- a/apps/server/src/models/goods/goods.controller.ts +++ b/apps/server/src/models/goods/goods.controller.ts @@ -1,19 +1,40 @@ import { Controller, Get, Query, Param } from '@nestjs/common'; - +import { GoodsService } from './goods.service'; +import { useEffect } from 'react'; +// 定义商品相关的控制器,路由前缀为 /goods @Controller('goods') export class GoodsController { - constructor() { - console.log('goods controller') - } + // 构造函数,在控制器实例化时执行 只读实例 + constructor(private readonly goodsService: GoodsService) { + console.log('goods controller'); // 打印日志,用于调试 调用商品服务的方法 + } + // 示例1:基本查询参数 + // GET /goods/hello?name=xxx + @Get('hello') + getHello(@Query('name') name?: string) { + return this.goodsService.getHello(name); + // 返回传入的name参数,如果未传入则返回'Guest' + } + // 示例2:路径参数 + // GET /goods/detail/123 + @Get('detail/:id') + getDetail(@Param('id') id: string) { + return this.goodsService.getDetail(id) // 返回路径参数中的id + } - // 示例1:基本查询参数 - @Get('hello') - getHello(@Query('name') name?: string) { - return { - message: 'Hello World!', - name: name || 'Guest' - }; - } - - -} \ No newline at end of file + // 示例3:多个查询参数 + // GET /goods/search?keyword=xxx&page=2&limit=20 + @Get('search') + searchProducts( + @Query('keyword') keyword: string, // 搜索关键词 + @Query('page') page: number = 1, // 页码,默认值为1 + @Query('limit') limit: number = 10, // 每页数量,默认值为10 + ) { + return { + keyword, // 返回搜索关键词 + page, // 返回当前页码 + limit, // 返回每页数量 + results: [], // 返回搜索结果(示例中为空数组) + }; + } +} diff --git a/apps/server/src/models/goods/goods.module.ts b/apps/server/src/models/goods/goods.module.ts old mode 100644 new mode 100755 index 7605654..c6f5cd6 --- a/apps/server/src/models/goods/goods.module.ts +++ b/apps/server/src/models/goods/goods.module.ts @@ -1,10 +1,11 @@ import { Module } from '@nestjs/common'; import { GoodsService } from './goods.service'; import { GoodsController } from './goods.controller'; - +import { GoodsRouter } from './goods.router'; +import { TrpcService } from '@server/trpc/trpc.service'; @Module({ - providers: [GoodsService], - controllers:[GoodsController] + providers: [GoodsService,GoodsRouter,TrpcService], // 1. 注册服务提供者,使 GoodsService 可在本模块中使用 + controllers:[GoodsController], // 2. 注册控制器,使 GoodsController 的路由可用 + exports:[GoodsRouter] }) - export class GoodsModule {} diff --git a/apps/server/src/models/goods/goods.router.ts b/apps/server/src/models/goods/goods.router.ts new file mode 100755 index 0000000..4d88ddf --- /dev/null +++ b/apps/server/src/models/goods/goods.router.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { GoodsService } from './goods.service'; +import { Prisma } from '@nice/common'; +import { z, ZodType } from 'zod'; +const GoodsUncheckedCreateInputSchema: ZodType = + z.any(); +const GoodsWhereInputSchema: ZodType = z.any(); +const GoodsSelectSchema: ZodType = z.any(); +@Injectable() +export class GoodsRouter { + constructor( + private readonly trpc: TrpcService, + private readonly goodsService: GoodsService, + ) {} + // trpc路由 + router = this.trpc.router({ + // 希望前端传什么参数 + //最简单的trpc写法 + hello: this.trpc.procedure + .input( + z.object({ + name: z.string(), + }), + ) + .query(({ input }) => { + return input.name; + }), + }); +} diff --git a/apps/server/src/models/goods/goods.service.ts b/apps/server/src/models/goods/goods.service.ts old mode 100644 new mode 100755 index 09c472f..66e3eb9 --- a/apps/server/src/models/goods/goods.service.ts +++ b/apps/server/src/models/goods/goods.service.ts @@ -1,4 +1,19 @@ import { Injectable } from '@nestjs/common'; - +// 注解相当于mapper @Injectable() -export class GoodsService {} +export class GoodsService { + getHello(name: string) { + return { + message: 'Hello World!', + name: name || 'Guest', + }; + } + + // GET /goods/detail/123 + getDetail(id: string) { + return { + id: id, // 返回路径参数中的id + detail: `Detail for product ${id}`, // 返回包含id的详细信息 + }; + } +} \ No newline at end of file diff --git a/apps/server/src/models/score/score.controller.ts b/apps/server/src/models/score/score.controller.ts new file mode 100755 index 0000000..1e306f0 --- /dev/null +++ b/apps/server/src/models/score/score.controller.ts @@ -0,0 +1,9 @@ +import { Controller, Get, Query, UseGuards } from '@nestjs/common'; + + +import { ScoreService } from './score.service'; + +@Controller('score') +export class ScoreController { + constructor(private readonly scoreService: ScoreService) {} +} diff --git a/apps/server/src/models/score/score.module.ts b/apps/server/src/models/score/score.module.ts new file mode 100755 index 0000000..7efa492 --- /dev/null +++ b/apps/server/src/models/score/score.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { DepartmentService } from '@server/models/department/department.service'; + +import { QueueModule } from '@server/queue/queue.module'; +import { MessageModule } from '../message/message.module'; +import { ScoreRouter } from './score.router'; +import { ScoreController } from './score.controller'; +import { ScoreService } from './score.service'; +import { RoleMapModule } from '../rbac/rbac.module'; + +@Module({ + imports: [QueueModule, RoleMapModule, MessageModule], + providers: [ScoreService, ScoreRouter, TrpcService, DepartmentService], + exports: [ScoreRouter, ScoreService], + controllers: [ScoreController], +}) +export class ScoreModule {} diff --git a/apps/server/src/models/score/score.router.ts b/apps/server/src/models/score/score.router.ts new file mode 100755 index 0000000..9969ff7 --- /dev/null +++ b/apps/server/src/models/score/score.router.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { ScoreService } from './score.service'; +import { z, ZodType } from 'zod'; +import { Prisma } from '@nice/common'; + +const ScoreCreateArgsSchema: ZodType = z.any(); +const ScoreUpdateArgsSchema: ZodType = z.any(); +const ScoreFindManyArgsSchema: ZodType = z.any(); +const ScoreWhereUniqueInputSchema: ZodType = z.any(); + +@Injectable() +export class ScoreRouter { + constructor( + private readonly trpc: TrpcService, + private readonly scoreService: ScoreService, + ) {} + + router = this.trpc.router({ + create: this.trpc.protectProcedure + .input(ScoreCreateArgsSchema) + .mutation(async ({ input }) => { + return await this.scoreService.create(input); + }), + + update: this.trpc.protectProcedure + .input(ScoreUpdateArgsSchema) + .mutation(async ({ input }) => { + return await this.scoreService.update(input); + }), + softDeleteByIds: this.trpc.protectProcedure + .input( + z.object({ + ids: z.array(z.string()), + data: ScoreUpdateArgsSchema.nullish(), + }), + ) + .mutation(async ({ input }) => { + return await this.scoreService.softDeleteByIds(input.ids); + }), + delete: this.trpc.protectProcedure + .input(ScoreWhereUniqueInputSchema) + .mutation(async ({ input }) => { + return await this.scoreService.delete({ where: input }); + }), + + findMany: this.trpc.procedure + .input(ScoreFindManyArgsSchema) + .query(async ({ input }) => { + return await this.scoreService.findMany(input); + }), + + findByClass: this.trpc.procedure + .input(z.number()) + .query(async ({ input }) => { + return await this.scoreService.findByClass(input); + }), + + findWithScores: this.trpc.procedure + .input(z.number()) + .query(async ({ input }) => { + return await this.scoreService.findWithScores(input); + }), + + search: this.trpc.procedure + .input(z.object({ + name: z.string().optional(), + classId: z.number().optional(), + limit: z.number().optional() + })) + .query(async ({ input }) => { + return await this.scoreService.search(input); + }), + }); +} \ No newline at end of file diff --git a/apps/server/src/models/score/score.service.ts b/apps/server/src/models/score/score.service.ts new file mode 100755 index 0000000..5f0be4f --- /dev/null +++ b/apps/server/src/models/score/score.service.ts @@ -0,0 +1,98 @@ +import { Injectable } from '@nestjs/common'; +import { + db, + Prisma, + UserProfile, + VisitType, + Post, + PostType, + RolePerms, + ResPerm, + ObjectType, + CourseMethodSchema, +} from '@nice/common'; +import { MessageService } from '../message/message.service'; +import { BaseService } from '../base/base.service'; +import { DepartmentService } from '../department/department.service'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; +import { BaseTreeService } from '../base/base.tree.service'; +import { z } from 'zod'; +@Injectable() +export class ScoreService extends BaseTreeService { + constructor( + private readonly messageService: MessageService, + private readonly departmentService: DepartmentService, + ) { + super(db, ObjectType.SCORE, 'scoreAncestry', true); + } + async create( + args: Prisma.ScoreCreateArgs, + params?: { staff?: UserProfile; tx?: Prisma.TransactionClient }, + ) { + const result = await super.create(args); + EventBus.emit('dataChanged', { + type: ObjectType.SCORE, + operation: CrudOperation.CREATED, + data: result, + }); + return result; + } + async update(args: Prisma.ScoreUpdateArgs, staff?: UserProfile) { + const result = await super.update(args); + EventBus.emit('dataChanged', { + type: ObjectType.SCORE, + operation: CrudOperation.UPDATED, + data: result, + }); + return result; + } + async findByClass(scoreId: number) { + return await db.score.findMany({ + where: { id: scoreId }, + include: { + student: true, + course: true, + } + }); + } + /** + * 查找学生并包含成绩信息 + */ + async findWithScores(studentId: number) { + return await db.student.findUnique({ + where: { id: studentId }, + include: { + class: true, + scores: { + include: { + course: true + } + } + } + }); + } + /** + * 搜索学生 + */ + async search(params: { name?: string, classId?: number, limit?: number }) { + const { name, classId, limit = 30 } = params; + + return await db.student.findMany({ + where: { + ...(name && { name: { contains: name } }), + ...(classId && { classId }) + }, + include: { + class: true + }, + take: limit + }); + } + private emitDataChangedEvent(data: any, operation: CrudOperation) { + EventBus.emit("dataChanged", { + type: this.objectType, + operation, + data, + }); + } +} diff --git a/apps/server/src/models/student/student.controller.ts b/apps/server/src/models/student/student.controller.ts new file mode 100755 index 0000000..cdefff3 --- /dev/null +++ b/apps/server/src/models/student/student.controller.ts @@ -0,0 +1,8 @@ +import { Controller, Get, Query, UseGuards } from '@nestjs/common'; + +import { StudentService } from './student.service'; + +@Controller('student') +export class StudentController { + constructor(private readonly studentService: StudentService) {} +} diff --git a/apps/server/src/models/student/student.module.ts b/apps/server/src/models/student/student.module.ts new file mode 100755 index 0000000..9804371 --- /dev/null +++ b/apps/server/src/models/student/student.module.ts @@ -0,0 +1,18 @@ +import { Module } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { DepartmentService } from '@server/models/department/department.service'; + +import { QueueModule } from '@server/queue/queue.module'; +import { MessageModule } from '../message/message.module'; +import { StudentRouter } from './student.router'; +import { StudentController } from './student.controller'; +import { StudentService } from './student.service'; +import { RoleMapModule } from '../rbac/rbac.module'; + +@Module({ + imports: [QueueModule, RoleMapModule, MessageModule], + providers: [StudentService, StudentRouter, TrpcService, DepartmentService], + exports: [StudentRouter, StudentService], + controllers: [StudentController], +}) +export class StudentModule {} diff --git a/apps/server/src/models/student/student.router.ts b/apps/server/src/models/student/student.router.ts new file mode 100755 index 0000000..590b7f2 --- /dev/null +++ b/apps/server/src/models/student/student.router.ts @@ -0,0 +1,75 @@ +import { Injectable } from '@nestjs/common'; +import { TrpcService } from '@server/trpc/trpc.service'; +import { StudentService } from './student.service'; +import { z, ZodType } from 'zod'; +import { Prisma } from '@nice/common'; + +const StudentCreateArgsSchema: ZodType = z.any(); +const StudentUpdateArgsSchema: ZodType = z.any(); +const StudentFindManyArgsSchema: ZodType = z.any(); +const StudentWhereUniqueInputSchema: ZodType = z.any(); + +@Injectable() +export class StudentRouter { + constructor( + private readonly trpc: TrpcService, + private readonly studentService: StudentService, + ) {} + + router = this.trpc.router({ + create: this.trpc.procedure + .input(StudentCreateArgsSchema) + .mutation(async ({ input }) => { + return await this.studentService.create(input); + }), + + update: this.trpc.procedure + .input(StudentUpdateArgsSchema) + .mutation(async ({ input }) => { + return await this.studentService.update(input); + }), + softDeleteByIds: this.trpc.procedure + .input( + z.object({ + ids: z.array(z.string()), + data: StudentUpdateArgsSchema.nullish(), + }), + ) + .mutation(async ({ input }) => { + return await this.studentService.softDeleteByIds(input.ids); + }), + delete: this.trpc.procedure + .input(StudentWhereUniqueInputSchema) + .mutation(async ({ input }) => { + return await this.studentService.delete({ where: input }); + }), + + findMany: this.trpc.procedure + .input(StudentFindManyArgsSchema) + .query(async ({ input }) => { + return await this.studentService.findMany(input); + }), + + findByClass: this.trpc.procedure + .input(z.number()) + .query(async ({ input }) => { + return await this.studentService.findByClass(input); + }), + + findWithScores: this.trpc.procedure + .input(z.number()) + .query(async ({ input }) => { + return await this.studentService.findWithScores(input); + }), + + search: this.trpc.procedure + .input(z.object({ + name: z.string().optional(), + classId: z.number().optional(), + limit: z.number().optional() + })) + .query(async ({ input }) => { + return await this.studentService.search(input); + }), + }); +} \ No newline at end of file diff --git a/apps/server/src/models/student/student.service.ts b/apps/server/src/models/student/student.service.ts new file mode 100755 index 0000000..045bdaf --- /dev/null +++ b/apps/server/src/models/student/student.service.ts @@ -0,0 +1,101 @@ +import { Injectable } from '@nestjs/common'; +import { + db, + Prisma, + UserProfile, + VisitType, + Post, + PostType, + RolePerms, + ResPerm, + ObjectType, + CourseMethodSchema, +} from '@nice/common'; +import { MessageService } from '../message/message.service'; +import { BaseService } from '../base/base.service'; +import { DepartmentService } from '../department/department.service'; +import EventBus, { CrudOperation } from '@server/utils/event-bus'; +import { BaseTreeService } from '../base/base.tree.service'; +import { z } from 'zod'; +@Injectable() +export class StudentService extends BaseTreeService { + constructor( + private readonly messageService: MessageService, + private readonly departmentService: DepartmentService, + ) { + super(db, ObjectType.STUDENT, 'studentAncestry', true); + } + async create( + args: Prisma.StudentCreateArgs, + params?: { staff?: UserProfile; tx?: Prisma.TransactionClient }, + ) { + const result = await db.student.create(args); + EventBus.emit('dataChanged', { + type: ObjectType.STUDENT, + operation: CrudOperation.CREATED, + data: result, + }); + return result; + } + async update(args: Prisma.StudentUpdateArgs, staff?: UserProfile) { + const result = await db.student.update(args); + EventBus.emit('dataChanged', { + type: ObjectType.STUDENT, + operation: CrudOperation.UPDATED, + data: result, + }); + return result; + } + async findByClass(classId: number) { + return await db.student.findMany({ + where: { classId }, + include: { + class: true, + scores: { + include: { + course: true + } + } + } + }); + } + /** + * 查找学生并包含成绩信息 + */ + async findWithScores(studentId: number) { + return await db.student.findUnique({ + where: { id: studentId }, + include: { + class: true, + scores: { + include: { + course: true + } + } + } + }); + } + /** + * 搜索学生 + */ + async search(params: { name?: string, classId?: number, limit?: number }) { + const { name, classId, limit = 30 } = params; + + return await db.student.findMany({ + where: { + ...(name && { name: { contains: name } }), + }, + include: { + class: true + }, + take: limit + }); + } + private emitDataChangedEvent(data: any, operation: CrudOperation) { + EventBus.emit("dataChanged", { + type: this.objectType, + operation, + data, + }); + } +} diff --git a/apps/server/src/trpc/trpc.module.ts b/apps/server/src/trpc/trpc.module.ts index e63f1b1..f63523d 100755 --- a/apps/server/src/trpc/trpc.module.ts +++ b/apps/server/src/trpc/trpc.module.ts @@ -9,14 +9,17 @@ import { TaxonomyModule } from '@server/models/taxonomy/taxonomy.module'; import { AuthModule } from '@server/auth/auth.module'; import { AppConfigModule } from '@server/models/app-config/app-config.module'; import { MessageModule } from '@server/models/message/message.module'; -import { PostModule } from '@server/models/post/post.module'; import { VisitModule } from '@server/models/visit/visit.module'; import { WebSocketModule } from '@server/socket/websocket.module'; import { RoleMapModule } from '@server/models/rbac/rbac.module'; import { TransformModule } from '@server/models/transform/transform.module'; - +import { PostModule } from '@server/models/post/post.module'; import { ResourceModule } from '@server/models/resource/resource.module'; import { GoodsModule } from '@server/models/goods/goods.module'; +import { CarModule } from '@server/models/car/car.module'; +import { StudentModule } from '@server/models/student/student.module'; +import { CourseModule } from '@server/models/course/course.module'; +import { ScoreModule } from '@server/models/score/score.module'; @Module({ imports: [ @@ -35,6 +38,10 @@ import { GoodsModule } from '@server/models/goods/goods.module'; WebSocketModule, ResourceModule, GoodsModule, + CarModule, + StudentModule, + CourseModule, + ScoreModule, ], controllers: [], providers: [TrpcService, TrpcRouter, Logger], diff --git a/apps/server/src/trpc/trpc.router.ts b/apps/server/src/trpc/trpc.router.ts index 7550867..9cef942 100755 --- a/apps/server/src/trpc/trpc.router.ts +++ b/apps/server/src/trpc/trpc.router.ts @@ -8,13 +8,17 @@ import * as trpcExpress from '@trpc/server/adapters/express'; import ws, { WebSocketServer } from 'ws'; import { AppConfigRouter } from '@server/models/app-config/app-config.router'; import { MessageRouter } from '@server/models/message/message.router'; -import { PostRouter } from '@server/models/post/post.router'; import { VisitRouter } from '@server/models/visit/visit.router'; import { RoleMapRouter } from '@server/models/rbac/rolemap.router'; import { TransformRouter } from '@server/models/transform/transform.router'; import { RoleRouter } from '@server/models/rbac/role.router'; import { ResourceRouter } from '../models/resource/resource.router'; - +import { GoodsRouter } from '../models/goods/goods.router'; +import { PostRouter } from '@server/models/post/post.router'; +import { CarRouter } from '@server/models/car/car.router'; +import { StudentRouter } from '@server/models/student/student.router'; +import { CourseRouter } from '@server/models/course/course.router'; +import { ScoreRouter } from '@server/models/score/score.router'; @Injectable() export class TrpcRouter { logger = new Logger(TrpcRouter.name); @@ -32,6 +36,11 @@ export class TrpcRouter { private readonly message: MessageRouter, private readonly visitor: VisitRouter, private readonly resource: ResourceRouter, + private readonly goods: GoodsRouter, + private readonly car: CarRouter, + private readonly student: StudentRouter, + private readonly course: CourseRouter, + private readonly score: ScoreRouter, ) {} getRouter() { return; @@ -49,6 +58,11 @@ export class TrpcRouter { app_config: this.app_config.router, visitor: this.visitor.router, resource: this.resource.router, + goods: this.goods.router, + car:this.car.router, + student:this.student.router, + course:this.course.router, + score:this.score.router }); wss: WebSocketServer = undefined; diff --git a/apps/web/nginx.conf b/apps/web/nginx.conf old mode 100644 new mode 100755 diff --git a/apps/web/src/App.css b/apps/web/src/App.css old mode 100644 new mode 100755 diff --git a/apps/web/src/app/main/car/Club.tsx b/apps/web/src/app/main/car/Club.tsx new file mode 100755 index 0000000..a6e584f --- /dev/null +++ b/apps/web/src/app/main/car/Club.tsx @@ -0,0 +1,45 @@ +'use client'; +import React, { useState, useEffect } from 'react'; +import Header from '../component/Header'; +import StudentSearch from '../component/StudentSearch'; +import ScoreForm from '../component/ScoreForm'; +import ScoreTable from '../component/ScoreTable'; +import { api } from '@nice/client'; +import bgImage from '../../../assets/student.png'; +const ClubPage = () => { + + const [studentName, setStudentName] = useState(''); + const [data, setdata] = useState([]) + const [editStudentId, setEditStudentId] = useState(null); + const [editStudentName, setEditStudentName] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + + // const { + // data: car = [], + // isLoading, + // error, + // refetch + // } = api.car.findMany.useQuery({ + // select: { + // id: true, + // name: true, + // } + // }); + // console.log(car); + + return ( + <> +
+
+
+

车辆管理

+ +
+
+
+ + ); +}; + +export default ClubPage; diff --git a/apps/web/src/app/main/car/Driver.tsx b/apps/web/src/app/main/car/Driver.tsx new file mode 100755 index 0000000..262ea17 --- /dev/null +++ b/apps/web/src/app/main/car/Driver.tsx @@ -0,0 +1,38 @@ +'use client'; +import React, { useState, useEffect } from 'react'; +import Header from '../component/Header'; +import StudentSearch from '../component/StudentSearch'; +import ScoreForm from '../component/ScoreForm'; +import ScoreTable from '../component/ScoreTable'; +import { api } from '@nice/client'; +import bgImage from '../../../assets/student.png'; +const DriverPage = () => { + + // const { + // data: car = [], + // isLoading, + // error, + // refetch + // } = api.car.findMany.useQuery({ + // select: { + // id: true, + // name: true, + // } + // }); + // console.log(car); + + return ( + <> +
+
+
+

车辆管理

+ +
+
+
+ + ); +}; + +export default DriverPage; diff --git a/apps/web/src/app/main/car/Game.tsx b/apps/web/src/app/main/car/Game.tsx new file mode 100755 index 0000000..7619b0f --- /dev/null +++ b/apps/web/src/app/main/car/Game.tsx @@ -0,0 +1,45 @@ +'use client'; +import React, { useState, useEffect } from 'react'; +import Header from '../component/Header'; +import StudentSearch from '../component/StudentSearch'; +import ScoreForm from '../component/ScoreForm'; +import ScoreTable from '../component/ScoreTable'; +import { api } from '@nice/client'; +import bgImage from '../../../assets/student.png'; +const GamePage = () => { + + const [studentName, setStudentName] = useState(''); + const [data, setdata] = useState([]) + const [editStudentId, setEditStudentId] = useState(null); + const [editStudentName, setEditStudentName] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + + // const { + // data: car = [], + // isLoading, + // error, + // refetch + // } = api.car.findMany.useQuery({ + // select: { + // id: true, + // name: true, + // } + // }); + // console.log(car); + + return ( + <> +
+
+
+

车辆管理

+ +
+
+
+ + ); +}; + +export default GamePage; diff --git a/apps/web/src/app/main/car/Sortie.tsx b/apps/web/src/app/main/car/Sortie.tsx new file mode 100755 index 0000000..c3f785a --- /dev/null +++ b/apps/web/src/app/main/car/Sortie.tsx @@ -0,0 +1,33 @@ +'use client'; +import React, { useState, useEffect } from 'react'; +import Header from '../component/Header'; +import StudentSearch from '../component/StudentSearch'; +import ScoreForm from '../component/ScoreForm'; +import ScoreTable from '../component/ScoreTable'; +import { api } from '@nice/client'; +import bgImage from '../../../assets/student.png'; +const SortiePage = () => { + + const [studentName, setStudentName] = useState(''); + const [data, setdata] = useState([]) + const [editStudentId, setEditStudentId] = useState(null); + const [editStudentName, setEditStudentName] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + + + return ( + <> +
+
+
+

车辆管理

+ +
+
+
+ + ); +}; + +export default SortiePage; diff --git a/apps/web/src/app/main/car/page.tsx b/apps/web/src/app/main/car/page.tsx new file mode 100755 index 0000000..1f3a19a --- /dev/null +++ b/apps/web/src/app/main/car/page.tsx @@ -0,0 +1,101 @@ +'use client'; +import React, { useState, useEffect } from 'react'; +import Header from '../component/Header'; +import StudentSearch from '../component/StudentSearch'; +import ScoreForm from '../component/ScoreForm'; +import ScoreTable from '../component/ScoreTable'; +import { api } from '@nice/client'; +import bgImage from '../../../assets/student.png'; +import { number } from 'zod'; +import Pagination from '../component/Pagination'; +export interface CarData { + id:number + clubId:number + name:string + club:string + score:number +} +const CarPage = () => { + const [data, setdata] = useState([]) + const [FilterCar, setFilterCar] = useState([]); + const [driver, setDriver] = useState([]); + const [searchParams, setSearchParams] = useState({ term: '', type: 'name' }); + const [currentPage, setCurrentPage] = useState(1); + const itemsPerPage = 5; + const mock: CarData[] = [ + { id: 1, clubId: 1, name: "user1", club:"chinese",score: 20}, + { id: 2, clubId: 2, name: "user2", club:"chinese",score: 20}, + { id: 3, clubId: 3, name: "user3", club:"chinese",score: 20}, + { id: 4, clubId: 4, name: "user4", club:"chinese",score: 20}, + { id: 5, clubId: 5, name: "user5", club:"chinese",score: 20}, + { id: 6, clubId: 6, name: "user6", club:"chinese",score: 20}, + ] + // const { + // data: car = [], + // isLoading, + // error, + // refetch + // } = api.car.findMany.useQuery({ + // select: { + // id: true, + // name: true, + // } + // }); + // console.log(car);//查询car数据 + //搜索 + const handleSearch = (term, type) => { + if (!term.trim()) { + setFilterCar(mock); + return; + } + let filtered = []; + const lowerTerm = term.toLowerCase(); + switch (type) { + case 'name': + filtered = mock.filter(mock => { + const car = driver.find(s => s.id === mock.clubId); + return car && car.name.toLowerCase().includes(lowerTerm); + }); + break; + } + }; + + return ( + <> +
+
+
+

车辆管理

+ {/* // 遍历 data 并渲染每个元素 */} + + + + + + + + + + + + {mock?.map((i) => ( + + + + + + + + ))} + +
idclubIdnameclubscore
{i.id}{i.clubId}{i.name}{i.club}{i.score}
+ +
+
+ +
+ + ); +}; + +export default CarPage; diff --git a/apps/web/src/app/main/component/Header.tsx b/apps/web/src/app/main/component/Header.tsx new file mode 100755 index 0000000..44e708f --- /dev/null +++ b/apps/web/src/app/main/component/Header.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import bgImage from '../../../assets/student.png'; +const Header = () => { + return ( +
+
+
+

学生成绩管理系统

+
+
+
+ ); +}; + +export default Header; \ No newline at end of file diff --git a/apps/web/src/app/main/component/Pagination.tsx b/apps/web/src/app/main/component/Pagination.tsx new file mode 100755 index 0000000..fb851c5 --- /dev/null +++ b/apps/web/src/app/main/component/Pagination.tsx @@ -0,0 +1,52 @@ +import React from 'react'; + +const Pagination = ({ currentPage, totalPages, onPageChange }) => { + const pages = []; + for (let i = 1; i <= totalPages; i++) { + pages.push(i); + } + return ( +
+ +
+ ); +}; + +export default Pagination; \ No newline at end of file diff --git a/apps/web/src/app/main/component/People.tsx b/apps/web/src/app/main/component/People.tsx old mode 100644 new mode 100755 diff --git a/apps/web/src/app/main/component/ScoreForm.tsx b/apps/web/src/app/main/component/ScoreForm.tsx new file mode 100755 index 0000000..9fd4080 --- /dev/null +++ b/apps/web/src/app/main/component/ScoreForm.tsx @@ -0,0 +1,155 @@ +import React, { useState, useEffect } from 'react'; + +const ScoreForm = ({ onSubmit, editingScore = null, students, courses }) => { + const [formData, setFormData] = useState({ + studentId: '', + courseId: '', + score: '', + semester: '' + }); + const [students1, setStudents1] = useState([]); + const [courses1, setCourses1] = useState([]); + const [loading, setLoading] = useState(false); + + useEffect(() => { + // 如果是编辑模式,使用editingScore填充表单 + if (editingScore) { + setFormData({ + studentId: editingScore.studentId, + courseId: editingScore.courseId, + score: editingScore.score, + semester: editingScore.semester + }); + } + fetchStudentsAndCourses(); + }, [editingScore]); + + const fetchStudentsAndCourses = async () => { + setLoading(true); + try { + // 模拟API调用 + setTimeout(() => { + setStudents1(students); + setCourses1(courses); + setLoading(false); + }, 500); + } catch (error) { + console.error('获取数据失败', error); + setLoading(false); + } + }; + + const handleChange = (e) => { + const { name, value } = e.target; + setFormData({ + ...formData, + [name]: name === 'score' ? parseFloat(value) : value + }); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + onSubmit(formData); + // 重置表单 + if (!editingScore) { + setFormData({ + studentId: '', + courseId: '', + score: '', + semester: '' + }); + } + }; + + return ( +
+

{editingScore ? '编辑成绩' : '添加成绩'}

+ {loading ? ( +
加载中...
+ ) : ( +
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+
+ )} +
+ ); +}; + +export default ScoreForm; \ No newline at end of file diff --git a/apps/web/src/app/main/component/ScoreTable.tsx b/apps/web/src/app/main/component/ScoreTable.tsx new file mode 100755 index 0000000..da384c2 --- /dev/null +++ b/apps/web/src/app/main/component/ScoreTable.tsx @@ -0,0 +1,102 @@ +import React from 'react'; +import Pagination from './Pagination'; +const ScoreTable = ({ scores, students, courses, currentPage, totalPages, onPageChange, onEdit, onDelete }) => { + // 获取学生和课程名称的辅助函数 + const getStudentName = (studentId) => { + if (!students || !Array.isArray(students)) return '未知学生'; + if (studentId === undefined || studentId === null) return '未知学生'; + + const student = students.find(s => s.id === studentId); + return student && student.name ? student.name : '未知学生'; + }; + const getCourseName = (courseId) => { + if (!courses || !Array.isArray(courses)) return '未知课程'; + if (courseId === undefined || courseId === null) return '未知课程'; + + const course = courses.find(c => c.id === courseId); + return course && course.name ? course.name : '未知课程'; + }; + + + + return ( +
+

成绩列表

+
+ + + + + + + + + + + {scores.length > 0 ? ( + scores.map((score) => ( + + + + + + + )) + ) : ( + + + + )} + +
+ 学生姓名 + + 课程 + + 分数 + + 操作 +
+ {getStudentName(score.studentId)} + + {getCourseName(score.courseId)} + + = 90 ? 'bg-green-100 text-green-800' : + score.score >= 60 ? 'bg-blue-100 text-blue-800' : + 'bg-red-100 text-red-800' + }`}> + {score.score} + + + + +
+ 没有找到成绩记录 +
+
+ + {scores.length > 0 && ( +
+ +
+ )} +
+ ); +}; + +export default ScoreTable; \ No newline at end of file diff --git a/apps/web/src/app/main/component/StudentSearch.tsx b/apps/web/src/app/main/component/StudentSearch.tsx new file mode 100755 index 0000000..d935c93 --- /dev/null +++ b/apps/web/src/app/main/component/StudentSearch.tsx @@ -0,0 +1,46 @@ +import React, { useState } from 'react'; + +const StudentSearch = ({ onSearch }) => { + const [searchTerm, setSearchTerm] = useState(''); + const [searchType, setSearchType] = useState('name'); // 'name', 'class', 'course' + + const handleSearch = () => { + onSearch(searchTerm, searchType); + }; + + return ( +
+

学生搜索

+
+
+ setSearchTerm(e.target.value)} + /> +
+
+ +
+ +
+
+ ); +}; + +export default StudentSearch; \ No newline at end of file diff --git a/apps/web/src/app/main/home/page.tsx b/apps/web/src/app/main/home/page.tsx index 74836b4..a52f5fa 100755 --- a/apps/web/src/app/main/home/page.tsx +++ b/apps/web/src/app/main/home/page.tsx @@ -1,79 +1,33 @@ -<<<<<<< HEAD -import { api } from "@nice/client"; -import { apiClient } from "@web/src/utils"; - -import { Button, Tag } from "antd"; - -import { useEffect, useMemo, useState } from "react"; -function HomePage() { - // 使用 useQuery 钩子从 API 获取数据 - - const { data } = api.staff.findMany.useQuery({ - take: 10 - }); - - // 定义 counter 状态和更新函数 - const [counter, setCounter] = useState(0); - - // 使用 useMemo 记忆化 counterText,仅在 counter 变化时重新计算 - const counterText = useMemo(() => { - return `当前计数为: ${counter}`; - }, [counter]); - - const getData = async()=>{ - const res = @wait apiClient.get(*/goods/hello*) - console.log(res) - } - - // 使用 useEffect 在 data 变化时打印 data - useEffect(() => { - apiClient.get(“/goods/hello”) - }, [data]); - - return ( -
- {counterText} -
- - -
- {/* 如果 data 存在,遍历并渲染每个项目的 username */} - {data?.map((item) => ( -
- {item.username} -
- ))} -
- ); -} - -export default HomePage; -======= import { api } from "@nice/client" import { apiClient } from "@web/src/utils" import { Button, Tag } from "antd" import { useEffect, useMemo, useState } from "react" function HomePage() { - const { data } = api.staff.findMany.useQuery({ + const { data: staffData } = api.staff.findMany.useQuery({ take: 10 }) + // 建议为不同的查询结果使用不同的变量名 + const { data: goodsData } = api.goods.hello.useQuery({ + name: "123" + }) + const [counter, setCounter] = useState(0) const counterText = useMemo(() => { return `当前计数为:${counter}` }, [counter]) const getData = async () => { - const res = await apiClient.get("/goods/hello") - console.log(res) + // 第一种写法 + // const res = await apiClient.get("/goods/hello") + // console.log(res) + //第二种写法 + apiClient.get("/goods/hello") + .then(res => { + console.log(res) + }) } - useEffect(() => { - getData() - }, []) + useEffect(() => { getData() }, []) return
{counterText}
@@ -87,15 +41,15 @@ function HomePage() { >减1
- { - data?.map(i => { - return
+
{ + staffData?.map(i => { + return
{i.username}
}) - } + }
+ {goodsData}
} // export { HomePage } export default HomePage ->>>>>>> de6e632ec69dd408a6c4e85d5cda01a1aa8e8276 diff --git a/apps/web/src/app/main/layout/MainLayout.tsx b/apps/web/src/app/main/layout/MainLayout.tsx new file mode 100755 index 0000000..fd34928 --- /dev/null +++ b/apps/web/src/app/main/layout/MainLayout.tsx @@ -0,0 +1,87 @@ +import React from 'react'; +import { Outlet, Link, useLocation } from 'react-router-dom'; + +const MainLayout: React.FC = () => { + const location = useLocation(); + const pathnames = location.pathname.split('/').filter((x) => x); + + return ( +
+ {/* 侧边栏 */} +
+ +
+ {/* 主内容区域 */} +
+ {/* 顶部导航栏 */} +
+
+ + 首页 + + {pathnames.map((value, index) => { + const to = `/${pathnames.slice(0, index + 1).join('/')}`; + const isLast = index === pathnames.length - 1; + return isLast ? ( + + / {value} + + ) : ( + + / {value} + + ); + })} +
+
+ {/* 主要内容 */} +
+ +
+ {/* 底部 */} +
+ Footer +
+
+
+ ); +}; + +export default MainLayout; \ No newline at end of file diff --git a/apps/web/src/app/main/layout/index.tsx b/apps/web/src/app/main/layout/index.tsx old mode 100644 new mode 100755 diff --git a/apps/web/src/app/main/student/StudentTestPage.tsx b/apps/web/src/app/main/student/StudentTestPage.tsx new file mode 100755 index 0000000..da36f5f --- /dev/null +++ b/apps/web/src/app/main/student/StudentTestPage.tsx @@ -0,0 +1,251 @@ +'use client'; +import { api } from '@nice/client'; +import { useEffect, useState } from 'react'; +import { Modal, Form, Input, Button } from 'antd'; +const StudentTestPage = () => { + const [studentName, setStudentName] = useState(''); + const [data, setdata] = useState([]) + const [editStudentId, setEditStudentId] = useState(null); + const [editStudentName, setEditStudentName] = useState(''); + const [isModalOpen, setIsModalOpen] = useState(false); + const [searchTerm, setSearchTerm] = useState(''); + + const { + data: students = [], + isLoading, + error, + refetch + } = api.student.findMany.useQuery({ + select: { + id: true, + name: true, + } + }); + // setdata(students) + console.log('students', students); + useEffect(() => { + setdata(students) + }, [students]) + const filteredStudents = students.filter(student => + student.name.toLowerCase().includes(searchTerm.toLowerCase()) || + student.id.toString().includes(searchTerm) + + ); + // const {data:students, isLoading, error} = api.student.findMany.useQuery({ + // where: { + // id: 1 + // }, + // select: { + // name: true, + // id: true, + // } + // }); + // console.log('students', students); + + // const {data:courses, isLoading, error} = api.course.findMany.useQuery({ + // select: { + // id: true, + // name: true, + // scores: true, + // } + // }); + // console.log('courses',courses); + + // const {data:scores, isLoading, error} = api.score.findMany.useQuery({ + // select: { + // id: true, + // studentId: true, + // courseId: true, + // score: true, + // } + // }); + // console.log('scores',scores); + + // const handleAddStudent = api.student.create.useMutation({ + // onSuccess: () => { + // console.log('添加学生成功'); + // }, + // onError: (error) => { + // console.log('添加分数失败', error); + // } + // }) + // handleAddStudent.mutate({ + // data: { + // name: '张三', + // classId: 10, + // } + // }) + + const deleteStudentMutation = api.student.delete.useMutation({ + onSuccess: () => { + console.log('删除成功'); + refetch() + }, + onError: (error) => { + console.error('删除失败:', error); + } + }); + const handleDeleteStudent = (studentId: number) => { + deleteStudentMutation.mutateAsync({ id: studentId }); + }; + const editStudentMutation = api.student.update.useMutation({ + onSuccess: () => { + console.log('编辑成功'); + refetch() + }, + onError: (error) => { + console.error('编辑失败:', error); + alert(`编辑失败: ${error.message}`); + } + }); + const handleEditStudent = (studentId?: number, name?: string) => { + if (studentId ) { + const studentIdNumber = Number(studentId); + editStudentMutation.mutate({ where: { id: studentIdNumber }, data: { name: name } }); + setEditStudentId(studentIdNumber); + setEditStudentName(name); + setIsModalOpen(true); + refetch() + } + }; + const handleOk = (studentId?: number, name?: string) => { + if (studentId && editStudentName) { + const studentIdNumber = Number(editStudentId); + editStudentMutation.mutate({ + where: { id: editStudentId }, + data: { name: editStudentName } + }); + } + setIsModalOpen(false); + setEditStudentId(null); + setEditStudentName(''); + refetch() + }; + const handleCancel = () => { + setIsModalOpen(false); + refetch() + }; + + // 增加 + const addStudentMutation = api.student.create.useMutation({ + onSuccess: (data) => { + console.log('添加成功:', data); + setStudentName(''); // 清空输入 + // 刷新列表 + refetch() + }, + onError: (error) => { + console.error('添加失败:', error); + alert(`添加失败: ${error.message}`); + } + }); + const handleAddStudent = () => { + + // 正确构造Prisma创建参数 + addStudentMutation.mutateAsync({ + data: { + name: studentName + } + }); + }; + + + return ( +
+

API测试

+ {/* 添加搜索框 */} +
+ setSearchTerm(e.target.value)} + onSearch={setSearchTerm} + className="max-w-md" + /> +
+
+ setStudentName(e.target.value)} + placeholder="学生姓名" + className="border rounded px-3 py-2 flex-1" + /> + +
+ + {isLoading &&
加载中...
} + + {/* 使用过滤后的学生列表 */} + {filteredStudents.length > 0 ? ( + filteredStudents.map((student) => ( +
+
+

ID: {student.id}

+

姓名: {student.name}

+
+
+ + +
+
+ )) + ) : ( +
未找到匹配的学生
+ )} + {/* {isLoading &&
加载中...
} + {error && ( +
+

错误

+

{error.message}

+
+ )} + {!isLoading && !error && ( +
+

成功获取 {scores.length} 分数:

+
+                        {JSON.stringify(scores, null, 2)}
+                    
+
+ )} */} + {/* {isLoading &&
加载中...
} + {students && students.length > 0 && + students.map((student) => ( +
+

ID: {student.id}

+

姓名: {student.name}

+ +
+ +
+
+ )) + } */} + + handleOk(editStudentId, editStudentName)} + onCancel={handleCancel} + > + setEditStudentName(e.target.value)} + placeholder="输入学生姓名" + /> + + +
+ ); + +}; + +export default StudentTestPage; \ No newline at end of file diff --git a/apps/web/src/app/main/student/page.tsx b/apps/web/src/app/main/student/page.tsx new file mode 100755 index 0000000..6907c6d --- /dev/null +++ b/apps/web/src/app/main/student/page.tsx @@ -0,0 +1,189 @@ +'use client'; +import React, { useState, useEffect } from 'react'; +import Header from '../component/Header'; +import StudentSearch from '../component/StudentSearch'; +import ScoreForm from '../component/ScoreForm'; +import ScoreTable from '../component/ScoreTable'; +import { api } from '@nice/client'; +const StudentPage = () => { + const [scores, setScores] = useState([]); + const [filteredScores, setFilteredScores] = useState([]); + const [students, setStudents] = useState([]); + const [courses, setCourses] = useState([]); + const [loading, setLoading] = useState(true); + const [currentPage, setCurrentPage] = useState(1); + const [editingScore, setEditingScore] = useState(null); + const [searchParams, setSearchParams] = useState({ term: '', type: 'name' }); + const itemsPerPage = 5; + const { + data: studentsData = [], + isLoading, + error } = api.student.findMany.useQuery({ + select: { + id: true, + name: true, + classId: true, + } + }); + console.log('学生',studentsData); + // 模拟API调用获取数据 + const {data:coursesData} = api.course.findMany.useQuery({ + select: { + id: true, + name: true, + scores: true, + } + }); + console.log('课程',coursesData); + const {data:scoresData} = api.score.findMany.useQuery({ + select: { + id: true, + studentId: true, + courseId: true, + score: true, + } + }); + console.log('成绩',scoresData); + useEffect(() => { + const fetchData = async () => { + try { + // 这里应该替换为实际的API调用 + setTimeout(async () => { + setStudents(studentsData); + setCourses(coursesData); + setScores(scoresData); + setFilteredScores(scoresData); + setLoading(false); + }, 1000); + } catch (error) { + console.error('获取数据失败', error); + setLoading(false); + } + }; + fetchData(); + }, []); + // 处理搜索 + const handleSearch = (term, type) => { + setSearchParams({ term, type }); + if (!term.trim()) { + setFilteredScores(scores); + return; + } + let filtered = []; + const lowerTerm = term.toLowerCase(); + switch (type) { + case 'name': + filtered = scores.filter(score => { + const student = students.find(s => s.id === score.studentId); + return student && student.name.toLowerCase().includes(lowerTerm); + }); + break; + case 'class': + filtered = scores.filter(score => { + const student = students.find(s => s.id === score.studentId); + const className = student ? student.classId : null; + return className && className.toString().includes(lowerTerm); + }); + break; + case 'course': + filtered = scores.filter(score => { + const course = courses.find(c => c.id === score.courseId); + return course && course.name.toLowerCase().includes(lowerTerm); + }); + break; + default: + filtered = scores; + } + setFilteredScores(filtered); + setCurrentPage(1); // 重置到第一页 + }; + // // 处理添加/编辑成绩 + const handleScoreSubmit = (formData) => { + if (editingScore) { + // 更新成绩 + const updatedScores = scoresData.map(score => + score.id === editingScore.id ? { ...score, ...formData } : score + ); + setScores(updatedScores); + setFilteredScores( + filteredScores.map(score => + score.id === editingScore.id ? { ...score, ...formData } : score + ) + ); + setEditingScore(null); + } else { + // 添加新成绩 + const newScore = { + id: scoresData.length + 1, // 实际项目中应该由后端生成 + ...formData + }; + setScores([...scoresData, newScore]); + setFilteredScores([...filteredScores, newScore]); + } + }; + // // 处理编辑成绩 + const handleEditScore = (score) => { + setEditingScore(score); + // 滚动到表单位置 + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + + // 处理删除成绩 + const handleDeleteScore = (id) => { + const updatedScores = scoresData.filter(score => score.id !== id); + setScores(updatedScores); + setFilteredScores(filteredScores.filter(score => score.id !== id)); + }; + + // 分页相关 + const indexOfLastItem = currentPage * itemsPerPage; + const indexOfFirstItem = indexOfLastItem - itemsPerPage; + const currentItems = Array.isArray(filteredScores) + ? filteredScores.slice(indexOfFirstItem, indexOfLastItem) + : []; + const totalPages = Math.ceil((filteredScores?.length || 0) / itemsPerPage); + const handlePageChange = (pageNumber) => { + setCurrentPage(pageNumber); + }; + + if (loading) { + return ( +
+
+
+
+
加载中...
+
+
+
+ ); + } + + return ( +
+
+
+
+
+ +
+
+ + +
+
+
+
+ ); +}; + +export default StudentPage; diff --git a/apps/web/src/assets/student.png b/apps/web/src/assets/student.png new file mode 100755 index 0000000..3c17d20 Binary files /dev/null and b/apps/web/src/assets/student.png differ diff --git a/apps/web/src/components/common/container/CollapsibleContent.tsx b/apps/web/src/components/common/container/CollapsibleContent.tsx old mode 100644 new mode 100755 diff --git a/apps/web/src/components/common/input/InputList.tsx b/apps/web/src/components/common/input/InputList.tsx old mode 100644 new mode 100755 diff --git a/apps/web/src/routes/index.tsx b/apps/web/src/routes/index.tsx index 4b79ed9..08f3f07 100755 --- a/apps/web/src/routes/index.tsx +++ b/apps/web/src/routes/index.tsx @@ -7,25 +7,61 @@ import LoginPage from "../app/login"; import { adminRoute } from "./admin-route"; import { CustomRouteObject } from "./types"; import { MainRoute } from "./main-route"; - +import HomePage from "../app/main/home/page"; +import StudentPage from "../app/main/student/page"; +import StudentTestPage from "../app/main/student/StudentTestPage"; +import CarPage from "../app/main/car/page"; +import MainLayout from "../app/main/layout/MainLayout"; +import ClubPage from "../app/main/car/Club"; +import DriverPage from "../app/main/car/Driver"; +import GamePage from "../app/main/car/Game"; +import SortiePage from "../app/main/car/Sortie"; export const routes: CustomRouteObject[] = [ { path: "/", - errorElement: , - handle: { - crumb() { - return 主页; - }, - }, + element: , children: [ - MainRoute, - adminRoute, + { + path: "/login", + element: , + }, + { + path: "/", + element: , + }, + { + path: "/student-test", + element: , + }, + { + path: "/student", + element: , + }, + { + path: "/car", + element: , + }, + { + path: "/club", + element: , + }, + { + path: "/driver", + element: , + }, + { + path: "/game", + element: , + }, + { + path: "/sortie", + element: , + }, + ], }, - { - path: "/login", - element: , - }, + + ]; -export const router = createBrowserRouter(routes); +export const router = createBrowserRouter(routes); \ No newline at end of file diff --git a/apps/web/src/routes/main-route.tsx b/apps/web/src/routes/main-route.tsx old mode 100644 new mode 100755 diff --git a/apps/web/src/utils/index.ts b/apps/web/src/utils/index.ts old mode 100644 new mode 100755 diff --git a/config/nginx/entrypoint.sh b/config/nginx/entrypoint.sh index ca557f8..542b552 100755 --- a/config/nginx/entrypoint.sh +++ b/config/nginx/entrypoint.sh @@ -14,7 +14,7 @@ done # Check if the index.html file exists before processing if [ -f "/usr/share/nginx/html/index.html" ]; then # Use envsubst to replace environment variable placeholders - envsubst < /usr/share/nginx/html/index.html > /usr/share/nginx/html/index.html.tmp + envsubst < /usr/share/nginx/html/index.template > /usr/share/nginx/html/index.html.tmp mv /usr/share/nginx/html/index.html.tmp /usr/share/nginx/html/index.html else echo "Info: /usr/share/nginx/html/index.html does not exist , skip replace env" diff --git a/docker-compose.example.yml b/docker-compose.example.yml index bb75926..e8e7f48 100755 --- a/docker-compose.example.yml +++ b/docker-compose.example.yml @@ -35,7 +35,7 @@ services: pgadmin: image: dpage/pgadmin4 ports: - - "8082:80" + - "8082:8090" environment: - PGADMIN_DEFAULT_EMAIL=insiinc@outlook.com - PGADMIN_DEFAULT_PASSWORD=Letusdoit000 @@ -64,7 +64,7 @@ services: # extra_hosts: # - "host.docker.internal:host-gateway" nginx: - image: nice-nginx:latest + image: nice-nginx:2.0 ports: - "80:80" volumes: @@ -74,7 +74,8 @@ services: - ./web-dist:/usr/share/nginx/html # 添加前端构建文件的挂载 - ./config/nginx/entrypoint.sh:/docker-entrypoint.sh environment: - - SERVER_IP=host.docker.internal + - SERVER_IP=192.168.0.14 + - SERVER_NAME=nice-playground entrypoint: ["/docker-entrypoint.sh"] extra_hosts: - "host.docker.internal:host-gateway" @@ -131,4 +132,4 @@ services: networks: default: - name: remooc + name: nice-network diff --git a/packages/common/prisma/schema.prisma b/packages/common/prisma/schema.prisma index 6f088a9..167d782 100755 --- a/packages/common/prisma/schema.prisma +++ b/packages/common/prisma/schema.prisma @@ -245,8 +245,8 @@ model PostAncestry { // 复合索引优化 // 索引建议 - } + model Message { id String @id @default(cuid()) url String? @@ -323,52 +323,157 @@ model Resource { @@index([type]) @@index([createdAt]) @@map("resource") - } -<<<<<<< HEAD - -======= - - ->>>>>>> de6e632ec69dd408a6c4e85d5cda01a1aa8e8276 //商品表 model Goods { id String @id @default(cuid()) // 商品ID name String @unique // 商品名称 - description String? // 商品描述 + description String? // 商品描述 price Float @default(0.0) // 商品价格 images String[] @default([]) // 商品图片 tags Tag[] @relation("GoodsTags") // 多对多关系 - reviews Review[] // 一对多关系 + reviews Review[] // 一对多关系 createdAt DateTime @default(now()) @map("created_at") // 创建时间 updatedAt DateTime @updatedAt @map("updated_at") // 更新时间 deletedAt DateTime? @map("deleted_at") // 删除时间,可为空 + @@index([name]) @@map("goods") } // 标签表 model Tag { - id String @id @default(cuid()) - name String @unique - goods Goods[] @relation("GoodsTags") - 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 @unique + goods Goods[] @relation("GoodsTags") + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") + @@map("tag") } model Review { - id String @id @default(cuid()) - content String - rating Int @default(0) - goodsId String @map("goods_id") - goods Goods @relation(fields: [goodsId], references: [id]) - createdAt DateTime @default(now()) @map("created_at") - updatedAt DateTime @updatedAt @map("updated_at") - deletedAt DateTime? @map("deleted_at") + id String @id @default(cuid()) + content String + rating Int @default(0) + goodsId String @map("goods_id") + goods Goods @relation(fields: [goodsId], references: [id]) + createdAt DateTime @default(now()) @map("created_at") + updatedAt DateTime @updatedAt @map("updated_at") + deletedAt DateTime? @map("deleted_at") + @@index([goodsId]) @@index([rating]) @@map("review") -} \ No newline at end of file +} + +model Club { + id Int @id @default(autoincrement()) + name String? @unique + introduce String? + car Car[] + driver Driver[] + Game Game[] @relation("ClubGame") + createdAt DateTime? @default(now()) + updatedAt DateTime? @updatedAt + + @@index([name]) + @@map("club") +} + +model Driver { + id Int @id @default(autoincrement()) + name String? + sex String? + username String? + password String? + club Club? @relation(fields: [clubId], references: [id]) + clubId Int? @map("club_id") + parentId Int? + createdAt DateTime? @default(now()) + updatedAt DateTime? @updatedAt + + @@map("driver") + @@index([name]) +} + +model Car { + id Int @id @default(autoincrement()) + clubId Int? + name String? @unique + type String? + club Club? @relation(fields: [clubId], references: [id]) + createdAt DateTime? @default(now()) + updatedAt DateTime? @updatedAt + + @@map("car") + @@index([name]) +} + +model Sortie { + totaltime Float @unique + carId Int? + driverId Int? + score Float? @unique + car String? + driver String? + + @@map("sortie") + @@index([driverId]) +} + +model Game { + id Int @id @default(autoincrement()) + name String + club Club[] @relation("ClubGame") + clubId Int? + courseId Int? + score Float? + createdAt DateTime? @default(now()) + updatedAt DateTime? @updatedAt + + @@map("game") + @@index([name]) +} +model Class { + id Int @id @default(autoincrement()) + name String @unique + students Student[] + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt +} + +model Student { + id Int @id @default(autoincrement()) + name String? + class Class? @relation(fields: [classId], references: [id]) + classId Int? + scores Score[] + parentId Int? + createdAt DateTime? @default(now()) + updatedAt DateTime? @updatedAt +} + +model Course { + id Int @id @default(autoincrement()) + name String? @unique + scores Score[] + createdAt DateTime? @default(now()) + updatedAt DateTime? @updatedAt +} + +model Score { + id Int @id @default(autoincrement()) + student Student? @relation(fields: [studentId], references: [id]) + studentId Int? + course Course? @relation(fields: [courseId], references: [id]) + courseId Int? + score Float? + semester String? + createdAt DateTime? @default(now()) + updatedAt DateTime? @updatedAt + + @@unique([studentId, courseId, semester]) +} diff --git a/packages/common/src/enum.ts b/packages/common/src/enum.ts index 7f6630f..2d51872 100755 --- a/packages/common/src/enum.ts +++ b/packages/common/src/enum.ts @@ -46,6 +46,14 @@ export enum ObjectType { POST = "post", VISIT = "visit", RESOURCE = "resource", + STUDENT="student", + COURSE="course", + SCORE="score", + CAR="car", + DRIVER="driver", + CLUB="club", + GAME="game", + SORTIE="sortie" } export enum RolePerms { // Create Permissions 创建权限 diff --git a/packages/config/src/index.ts b/packages/config/src/index.ts old mode 100644 new mode 100755 diff --git a/web-dist/index.html b/web-dist/index.html new file mode 100755 index 0000000..e94bca8 --- /dev/null +++ b/web-dist/index.html @@ -0,0 +1,25 @@ + + + + + + + + 信箱 + + + + + +
+ + +