add
This commit is contained in:
parent
d817b3e8fe
commit
42a0800e41
|
@ -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 }) => ({
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
|
||||
|
||||
import { CarService } from './car.service';
|
||||
|
||||
@Controller('car')
|
||||
export class CarController {
|
||||
constructor(private readonly carService: CarService) {}
|
||||
}
|
|
@ -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 {}
|
|
@ -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<Prisma.CarCreateArgs> = z.any();
|
||||
const CarUpdateArgsSchema: ZodType<Prisma.CarUpdateArgs> = z.any();
|
||||
const CarFindManyArgsSchema: ZodType<Prisma.CarFindManyArgs> = z.any();
|
||||
const CarWhereUniqueInputSchema: ZodType<Prisma.CarWhereUniqueInput> = 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);
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -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<Prisma.CarDelegate> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
|
||||
|
||||
import { CourseService } from './course.service';
|
||||
|
||||
@Controller('course')
|
||||
export class CourseController {
|
||||
constructor(private readonly courseService: CourseService) {}
|
||||
}
|
|
@ -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 {}
|
|
@ -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<Prisma.CourseCreateArgs> = z.any();
|
||||
const CourseUpdateArgsSchema: ZodType<Prisma.CourseUpdateArgs> = z.any();
|
||||
const CourseFindManyArgsSchema: ZodType<Prisma.CourseFindManyArgs> = z.any();
|
||||
const CourseWhereUniqueInputSchema: ZodType<Prisma.CourseWhereUniqueInput> = 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);
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -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<Prisma.CourseDelegate> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
import { Controller } from '@nestjs/common';
|
||||
|
||||
|
||||
import { DriverService } from './driver.service';
|
||||
|
||||
@Controller('driver')
|
||||
export class DriverController {
|
||||
constructor(private readonly driverService: DriverService) {}
|
||||
}
|
|
@ -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 {}
|
|
@ -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<Prisma.CarCreateArgs> = z.any();
|
||||
const CourseUpdateArgsSchema: ZodType<Prisma.CarUpdateArgs> = z.any();
|
||||
const CourseFindManyArgsSchema: ZodType<Prisma.CarFindManyArgs> = z.any();
|
||||
const CourseWhereUniqueInputSchema: ZodType<Prisma.CarWhereUniqueInput> = 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);
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -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<Prisma.DriverDelegate> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
// 示例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 {
|
||||
message: 'Hello World!',
|
||||
name: name || 'Guest'
|
||||
keyword, // 返回搜索关键词
|
||||
page, // 返回当前页码
|
||||
limit, // 返回每页数量
|
||||
results: [], // 返回搜索结果(示例中为空数组)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -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 {}
|
||||
|
|
|
@ -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<Prisma.GoodsUncheckedCreateInput> =
|
||||
z.any();
|
||||
const GoodsWhereInputSchema: ZodType<Prisma.GoodsWhereInput> = z.any();
|
||||
const GoodsSelectSchema: ZodType<Prisma.GoodsSelect> = 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;
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -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的详细信息
|
||||
};
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
|
@ -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 {}
|
|
@ -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<Prisma.ScoreCreateArgs> = z.any();
|
||||
const ScoreUpdateArgsSchema: ZodType<Prisma.ScoreUpdateArgs> = z.any();
|
||||
const ScoreFindManyArgsSchema: ZodType<Prisma.ScoreFindManyArgs> = z.any();
|
||||
const ScoreWhereUniqueInputSchema: ZodType<Prisma.ScoreWhereUniqueInput> = 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);
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -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<Prisma.ScoreDelegate> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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) {}
|
||||
}
|
|
@ -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 {}
|
|
@ -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<Prisma.StudentCreateArgs> = z.any();
|
||||
const StudentUpdateArgsSchema: ZodType<Prisma.StudentUpdateArgs> = z.any();
|
||||
const StudentFindManyArgsSchema: ZodType<Prisma.StudentFindManyArgs> = z.any();
|
||||
const StudentWhereUniqueInputSchema: ZodType<Prisma.StudentWhereUniqueInput> = 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);
|
||||
}),
|
||||
});
|
||||
}
|
|
@ -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<Prisma.StudentDelegate> {
|
||||
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,
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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],
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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<number | null>(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 (
|
||||
<>
|
||||
<div className="w-full bg-cover bg-center h-64" style={{ backgroundImage: `url(${bgImage})` }}>
|
||||
<div className="flex items-center justify-center h-full w-full bg-opacity-50 bg-black">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-white leading-tight">车辆管理</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default ClubPage;
|
|
@ -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 (
|
||||
<>
|
||||
<div className="w-full bg-cover bg-center h-64" style={{ backgroundImage: `url(${bgImage})` }}>
|
||||
<div className="flex items-center justify-center h-full w-full bg-opacity-50 bg-black">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-white leading-tight">车辆管理</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default DriverPage;
|
|
@ -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<number | null>(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 (
|
||||
<>
|
||||
<div className="w-full bg-cover bg-center h-64" style={{ backgroundImage: `url(${bgImage})` }}>
|
||||
<div className="flex items-center justify-center h-full w-full bg-opacity-50 bg-black">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-white leading-tight">车辆管理</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GamePage;
|
|
@ -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<number | null>(null);
|
||||
const [editStudentName, setEditStudentName] = useState('');
|
||||
const [isModalOpen, setIsModalOpen] = useState(false);
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="w-full bg-cover bg-center h-64" style={{ backgroundImage: `url(${bgImage})` }}>
|
||||
<div className="flex items-center justify-center h-full w-full bg-opacity-50 bg-black">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-white leading-tight">车辆管理</h1>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default SortiePage;
|
|
@ -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 (
|
||||
<>
|
||||
<div className="w-full bg-cover bg-center h-64" style={{ backgroundImage: `url(${bgImage})` }}>
|
||||
<div className="flex items-center justify-center h-full w-full bg-opacity-50 bg-black">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-white leading-tight mb-10">车辆管理</h1>
|
||||
{/* // 遍历 data 并渲染每个元素 */}
|
||||
<table className='table-auto w-full'>
|
||||
<thead>
|
||||
<tr className='bg-white'>
|
||||
<th className='px-4 py-2'>id</th>
|
||||
<th className='px-4 py-2'>clubId</th>
|
||||
<th className='px-4 py-2'>name</th>
|
||||
<th className='px-4 py-2'>club</th>
|
||||
<th className='px-4 py-2'>score</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mock?.map((i) => (
|
||||
<tr key={i.id} className='hover:bg-gray-100 bg-yellow-700'>
|
||||
<td className='border px-4 py-2'>{i.id}</td>
|
||||
<td className='border px-4 py-2'>{i.clubId}</td>
|
||||
<td className='border px-4 py-2'>{i.name}</td>
|
||||
<td className='border px-4 py-2'>{i.club}</td>
|
||||
<td className='border px-4 py-2'>{i.score}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
<StudentSearch onSearch={handleSearch} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CarPage;
|
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
import bgImage from '../../../assets/student.png';
|
||||
const Header = () => {
|
||||
return (
|
||||
<div className="w-full bg-cover bg-center h-64" style={{ backgroundImage: `url(${bgImage})` }}>
|
||||
<div className="flex items-center justify-center h-full w-full bg-opacity-50 bg-black">
|
||||
<div className="text-center">
|
||||
<h1 className="text-4xl font-bold text-white leading-tight">学生成绩管理系统</h1>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Header;
|
|
@ -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 (
|
||||
<div className="flex justify-center mt-4">
|
||||
<nav className="flex items-center">
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage - 1)}
|
||||
disabled={currentPage === 1}
|
||||
className={`mx-1 px-3 py-1 rounded-md ${
|
||||
currentPage === 1
|
||||
? 'text-gray-400 cursor-not-allowed'
|
||||
: 'text-blue-500 hover:bg-blue-100'
|
||||
}`}
|
||||
>
|
||||
上一页
|
||||
</button>
|
||||
{pages.map(page => (
|
||||
<button
|
||||
key={page}
|
||||
onClick={() => onPageChange(page)}
|
||||
className={`mx-1 px-3 py-1 rounded-md ${
|
||||
currentPage === page
|
||||
? 'bg-blue-500 text-white'
|
||||
: 'text-blue-500 hover:bg-blue-100'
|
||||
}`}
|
||||
>
|
||||
{page}
|
||||
</button>
|
||||
))}
|
||||
|
||||
<button
|
||||
onClick={() => onPageChange(currentPage + 1)}
|
||||
disabled={currentPage === totalPages}
|
||||
className={`mx-1 px-3 py-1 rounded-md ${
|
||||
currentPage === totalPages
|
||||
? 'text-gray-400 cursor-not-allowed'
|
||||
: 'text-blue-500 hover:bg-blue-100'
|
||||
}`}
|
||||
>
|
||||
下一页
|
||||
</button>
|
||||
</nav>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Pagination;
|
|
@ -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 (
|
||||
<div className="bg-white p-6 rounded-lg shadow mb-6">
|
||||
<h2 className="text-xl font-semibold mb-4">{editingScore ? '编辑成绩' : '添加成绩'}</h2>
|
||||
{loading ? (
|
||||
<div className="text-center py-4">加载中...</div>
|
||||
) : (
|
||||
<form onSubmit={handleSubmit}>
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">学生</label>
|
||||
<select
|
||||
name="studentId"
|
||||
value={formData.studentId}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
>
|
||||
<option value="">选择学生</option>
|
||||
{students.map(student => (
|
||||
<option key={student.id} value={student.id}>
|
||||
{student.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">课程</label>
|
||||
<select
|
||||
name="courseId"
|
||||
value={formData.courseId}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
>
|
||||
<option value="">选择课程</option>
|
||||
{courses.map(course => (
|
||||
<option key={course.id} value={course.id}>
|
||||
{course.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">分数</label>
|
||||
<input
|
||||
type="number"
|
||||
name="score"
|
||||
value={formData.score}
|
||||
onChange={handleChange}
|
||||
placeholder="输入分数"
|
||||
min="0"
|
||||
max="100"
|
||||
step="0.1"
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-1">学期</label>
|
||||
<select
|
||||
name="semester"
|
||||
value={formData.semester}
|
||||
onChange={handleChange}
|
||||
className="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
required
|
||||
>
|
||||
<option value="">选择学期</option>
|
||||
<option value="2023-春季">2023-春季</option>
|
||||
<option value="2023-秋季">2023-秋季</option>
|
||||
<option value="2024-春季">2024-春季</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-md transition duration-200"
|
||||
>
|
||||
{editingScore ? '更新成绩' : '添加成绩'}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScoreForm;
|
|
@ -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 (
|
||||
<div className="bg-white rounded-lg shadow overflow-hidden">
|
||||
<h2 className="text-xl font-semibold p-4 border-b">成绩列表</h2>
|
||||
<div className="overflow-x-auto">
|
||||
<table className="min-w-full divide-y divide-gray-200">
|
||||
<thead className="bg-gray-50">
|
||||
<tr>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
学生姓名
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
课程
|
||||
</th>
|
||||
<th className="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
分数
|
||||
</th>
|
||||
<th className="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider">
|
||||
操作
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{scores.length > 0 ? (
|
||||
scores.map((score) => (
|
||||
<tr key={score.id} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
{getStudentName(score.studentId)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
{getCourseName(score.courseId)}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`px-2 py-1 rounded-full text-sm ${
|
||||
score.score >= 90 ? 'bg-green-100 text-green-800' :
|
||||
score.score >= 60 ? 'bg-blue-100 text-blue-800' :
|
||||
'bg-red-100 text-red-800'
|
||||
}`}>
|
||||
{score.score}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
|
||||
<button
|
||||
onClick={() => onEdit(score)}
|
||||
className="text-indigo-600 hover:text-indigo-900 mr-4"
|
||||
>
|
||||
编辑
|
||||
</button>
|
||||
<button
|
||||
onClick={() => onDelete(score.id)}
|
||||
className="text-red-600 hover:text-red-900"
|
||||
>
|
||||
删除
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan="5" className="px-6 py-4 text-center text-gray-500">
|
||||
没有找到成绩记录
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{scores.length > 0 && (
|
||||
<div className="px-4 py-3 border-t border-gray-200">
|
||||
<Pagination
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={onPageChange}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ScoreTable;
|
|
@ -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 (
|
||||
<div className="bg-white p-4 rounded-lg shadow mb-6">
|
||||
<h2 className="text-xl font-semibold mb-4">学生搜索</h2>
|
||||
<div className="flex flex-col md:flex-row gap-4">
|
||||
<div className="flex-1">
|
||||
<input
|
||||
type="text"
|
||||
placeholder="输入搜索关键词..."
|
||||
className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="w-full md:w-48">
|
||||
<select
|
||||
className="w-full px-4 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
|
||||
value={searchType}
|
||||
onChange={(e) => setSearchType(e.target.value)}
|
||||
>
|
||||
<option value="name">按学生姓名</option>
|
||||
<option value="class">按班级</option>
|
||||
<option value="course">按课程</option>
|
||||
</select>
|
||||
</div>
|
||||
<button
|
||||
className="bg-blue-500 hover:bg-blue-600 text-white font-medium py-2 px-6 rounded-md transition duration-200"
|
||||
onClick={handleSearch}
|
||||
>
|
||||
搜索
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudentSearch;
|
|
@ -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<number>(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 (
|
||||
<div className="p-2 space-y-2">
|
||||
<Tag>{counterText}</Tag>
|
||||
<div className="space-x-2">
|
||||
<Button type="primary" onClick={() => setCounter(counter + 1)}>
|
||||
加1
|
||||
</Button>
|
||||
<Button danger onClick={() => setCounter(counter - 1)}>
|
||||
减1
|
||||
</Button>
|
||||
</div>
|
||||
{/* 如果 data 存在,遍历并渲染每个项目的 username */}
|
||||
{data?.map((item) => (
|
||||
<div className="p-2 rounded border shadow" key={item.username}>
|
||||
<Tag>{item.username}</Tag>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
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<number>(0)
|
||||
const counterText = useMemo(() => {
|
||||
return `当前计数为:${counter}`
|
||||
}, [counter])
|
||||
|
||||
const getData = async () => {
|
||||
const res = await apiClient.get("/goods/hello")
|
||||
// 第一种写法
|
||||
// const res = await apiClient.get("/goods/hello")
|
||||
// console.log(res)
|
||||
//第二种写法
|
||||
apiClient.get("/goods/hello")
|
||||
.then(res => {
|
||||
console.log(res)
|
||||
})
|
||||
}
|
||||
useEffect(() => {
|
||||
getData()
|
||||
}, [])
|
||||
useEffect(() => { getData() }, [])
|
||||
return <div className="p-2 space-y-2">
|
||||
<Tag>{counterText}</Tag>
|
||||
<div className="space-x-2" >
|
||||
|
@ -87,15 +41,15 @@ function HomePage() {
|
|||
>减1</Button>
|
||||
</div>
|
||||
|
||||
{
|
||||
data?.map(i => {
|
||||
return <div className="p-2 rounded border shadow">
|
||||
<div className="flex flex-wrap">{
|
||||
staffData?.map(i => {
|
||||
return <div className="p-2 rounded border shadow" key={i.id}>
|
||||
<Tag>{i.username}</Tag>
|
||||
</div>
|
||||
})
|
||||
}
|
||||
} </div>
|
||||
<Tag className="text-2xl">{goodsData}</Tag>
|
||||
</div>
|
||||
}
|
||||
// export { HomePage }
|
||||
export default HomePage
|
||||
>>>>>>> de6e632ec69dd408a6c4e85d5cda01a1aa8e8276
|
||||
|
|
|
@ -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 (
|
||||
<div className="min-h-screen flex">
|
||||
{/* 侧边栏 */}
|
||||
<div className="w-64 bg-gray-800 text-white p-4">
|
||||
<nav className="space-y-2">
|
||||
<Link
|
||||
to="/"
|
||||
className="block px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
学生管理
|
||||
</Link>
|
||||
<Link
|
||||
to="/car"
|
||||
className="block px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
车辆管理
|
||||
</Link>
|
||||
<Link
|
||||
to="/club"
|
||||
className="block px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
俱乐部管理
|
||||
</Link>
|
||||
<Link
|
||||
to="/driver"
|
||||
className="block px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
车手管理
|
||||
</Link>
|
||||
<Link
|
||||
to="/game"
|
||||
className="block px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
比赛管理
|
||||
</Link>
|
||||
<Link
|
||||
to="/sortie"
|
||||
className="block px-4 py-2 text-sm font-medium rounded-md hover:bg-gray-700 transition-colors"
|
||||
>
|
||||
车次管理
|
||||
</Link>
|
||||
</nav>
|
||||
</div>
|
||||
{/* 主内容区域 */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{/* 顶部导航栏 */}
|
||||
<div className="bg-blue-600 text-white p-4">
|
||||
<div className="flex items-center space-x-2">
|
||||
<Link to="/" className="hover:text-gray-200">
|
||||
首页
|
||||
</Link>
|
||||
{pathnames.map((value, index) => {
|
||||
const to = `/${pathnames.slice(0, index + 1).join('/')}`;
|
||||
const isLast = index === pathnames.length - 1;
|
||||
return isLast ? (
|
||||
<span key={to} className="font-medium">
|
||||
/ {value}
|
||||
</span>
|
||||
) : (
|
||||
<Link key={to} to={to} className="hover:text-gray-200">
|
||||
/ {value}
|
||||
</Link>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
{/* 主要内容 */}
|
||||
<div className="flex-1 p-4">
|
||||
<Outlet />
|
||||
</div>
|
||||
{/* 底部 */}
|
||||
<div className="bg-gray-800 text-white p-4">
|
||||
Footer
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default MainLayout;
|
|
@ -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<number | null>(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 (
|
||||
<div className="p-5">
|
||||
<h1 className="text-xl font-bold mb-4">API测试</h1>
|
||||
{/* 添加搜索框 */}
|
||||
<div className="mb-4">
|
||||
<Input.Search
|
||||
placeholder="搜索学生姓名或ID"
|
||||
allowClear
|
||||
enterButton
|
||||
value={searchTerm}
|
||||
onChange={(e) => setSearchTerm(e.target.value)}
|
||||
onSearch={setSearchTerm}
|
||||
className="max-w-md"
|
||||
/>
|
||||
</div>
|
||||
<div className="mb-4">
|
||||
<input
|
||||
type="text"
|
||||
value={studentName}
|
||||
onChange={(e) => setStudentName(e.target.value)}
|
||||
placeholder="学生姓名"
|
||||
className="border rounded px-3 py-2 flex-1"
|
||||
/>
|
||||
<button onClick={() => handleAddStudent()} className='bg-blue-500 text-white px-3 py-1 rounded-md hover:bg-blue-600'>添加</button>
|
||||
</div>
|
||||
|
||||
{isLoading && <div>加载中...</div>}
|
||||
|
||||
{/* 使用过滤后的学生列表 */}
|
||||
{filteredStudents.length > 0 ? (
|
||||
filteredStudents.map((student) => (
|
||||
<div key={student.id} className="mb-3 p-3 border rounded flex justify-between items-center">
|
||||
<div>
|
||||
<p className="font-medium">ID: {student.id}</p>
|
||||
<p>姓名: {student.name}</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => handleEditStudent(student.id, student.name)}
|
||||
className='bg-blue-500 text-white px-3 py-1 rounded-md hover:bg-blue-600'>
|
||||
编辑
|
||||
</button>
|
||||
<button onClick={() => handleDeleteStudent(student.id)}
|
||||
className='bg-red-500 text-white px-3 py-1 rounded-md hover:bg-red-600'>
|
||||
删除
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
) : (
|
||||
<div className="text-gray-500 my-4">未找到匹配的学生</div>
|
||||
)}
|
||||
{/* {isLoading && <div className="text-gray-600">加载中...</div>}
|
||||
{error && (
|
||||
<div className="bg-red-100 border-l-4 border-red-500 p-4 mb-5">
|
||||
<p className="font-bold">错误</p>
|
||||
<p>{error.message}</p>
|
||||
</div>
|
||||
)}
|
||||
{!isLoading && !error && (
|
||||
<div>
|
||||
<p className="mb-2">成功获取 {scores.length} 分数:</p>
|
||||
<pre className="bg-gray-100 p-4 rounded overflow-auto max-h-96">
|
||||
{JSON.stringify(scores, null, 2)}
|
||||
</pre>
|
||||
</div>
|
||||
)} */}
|
||||
{/* {isLoading && <div>加载中...</div>}
|
||||
{students && students.length > 0 &&
|
||||
students.map((student) => (
|
||||
<div key={student.id}>
|
||||
<p>ID: {student.id}</p>
|
||||
<p>姓名: {student.name}</p>
|
||||
<button onClick={() => handleDeleteStudent(student.id)} className='bg-red-500 text-white px-4 py-2 rounded-md'>删除</button>
|
||||
<div>
|
||||
<button onClick={() => handleEditStudent(student.id,student.name)} className='bg-blue-500 text-white px-4 py-2 rounded-md'>编辑</button>
|
||||
</div>
|
||||
</div>
|
||||
))
|
||||
} */}
|
||||
|
||||
<Modal
|
||||
title="编辑学生"
|
||||
open={isModalOpen}
|
||||
onOk={() => handleOk(editStudentId, editStudentName)}
|
||||
onCancel={handleCancel}
|
||||
>
|
||||
<Input
|
||||
value={editStudentName}
|
||||
onChange={(e) => setEditStudentName(e.target.value)}
|
||||
placeholder="输入学生姓名"
|
||||
/>
|
||||
</Modal>
|
||||
|
||||
</div>
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
export default StudentTestPage;
|
|
@ -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 (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<Header />
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="flex justify-center items-center h-64">
|
||||
<div className="text-xl text-gray-600">加载中...</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-100">
|
||||
<Header />
|
||||
<div className="container mx-auto px-4 py-8">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
|
||||
<div className="lg:col-span-1">
|
||||
<ScoreForm onSubmit={handleScoreSubmit} editingScore={editingScore} students={studentsData} courses={coursesData} />
|
||||
</div>
|
||||
<div className="lg:col-span-2">
|
||||
<StudentSearch onSearch={handleSearch} />
|
||||
<ScoreTable
|
||||
scores={scoresData || []}
|
||||
students={studentsData || []}
|
||||
courses={coursesData || []}
|
||||
currentPage={currentPage}
|
||||
totalPages={totalPages}
|
||||
onPageChange={handlePageChange}
|
||||
onEdit={handleEditScore}
|
||||
onDelete={handleDeleteScore}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default StudentPage;
|
Binary file not shown.
|
@ -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: <ErrorPage />,
|
||||
handle: {
|
||||
crumb() {
|
||||
return <Link to={"/"}>主页</Link>;
|
||||
},
|
||||
},
|
||||
element: <MainLayout />,
|
||||
children: [
|
||||
MainRoute,
|
||||
adminRoute,
|
||||
],
|
||||
},
|
||||
{
|
||||
path: "/login",
|
||||
element: <LoginPage></LoginPage>,
|
||||
},
|
||||
{
|
||||
path: "/",
|
||||
element: <StudentPage></StudentPage>,
|
||||
},
|
||||
{
|
||||
path: "/student-test",
|
||||
element: <StudentTestPage></StudentTestPage>,
|
||||
},
|
||||
{
|
||||
path: "/student",
|
||||
element: <StudentPage></StudentPage>,
|
||||
},
|
||||
{
|
||||
path: "/car",
|
||||
element: <CarPage></CarPage>,
|
||||
},
|
||||
{
|
||||
path: "/club",
|
||||
element: <ClubPage></ClubPage>,
|
||||
},
|
||||
{
|
||||
path: "/driver",
|
||||
element: <DriverPage></DriverPage>,
|
||||
},
|
||||
{
|
||||
path: "/game",
|
||||
element: <GamePage></GamePage>,
|
||||
},
|
||||
{
|
||||
path: "/sortie",
|
||||
element: <SortiePage></SortiePage>,
|
||||
},
|
||||
|
||||
],
|
||||
},
|
||||
|
||||
|
||||
];
|
||||
|
||||
export const router = createBrowserRouter(routes);
|
|
@ -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"
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -245,8 +245,8 @@ model PostAncestry {
|
|||
|
||||
// 复合索引优化
|
||||
// 索引建议
|
||||
|
||||
}
|
||||
|
||||
model Message {
|
||||
id String @id @default(cuid())
|
||||
url String?
|
||||
|
@ -323,14 +323,7 @@ model Resource {
|
|||
@@index([type])
|
||||
@@index([createdAt])
|
||||
@@map("resource")
|
||||
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
|
||||
=======
|
||||
|
||||
|
||||
>>>>>>> de6e632ec69dd408a6c4e85d5cda01a1aa8e8276
|
||||
|
||||
//商品表
|
||||
model Goods {
|
||||
|
@ -344,6 +337,7 @@ model Goods {
|
|||
createdAt DateTime @default(now()) @map("created_at") // 创建时间
|
||||
updatedAt DateTime @updatedAt @map("updated_at") // 更新时间
|
||||
deletedAt DateTime? @map("deleted_at") // 删除时间,可为空
|
||||
|
||||
@@index([name])
|
||||
@@map("goods")
|
||||
}
|
||||
|
@ -356,6 +350,7 @@ model Tag {
|
|||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
@@map("tag")
|
||||
}
|
||||
|
||||
|
@ -368,7 +363,117 @@ model Review {
|
|||
createdAt DateTime @default(now()) @map("created_at")
|
||||
updatedAt DateTime @updatedAt @map("updated_at")
|
||||
deletedAt DateTime? @map("deleted_at")
|
||||
|
||||
@@index([goodsId])
|
||||
@@index([rating])
|
||||
@@map("review")
|
||||
}
|
||||
|
||||
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])
|
||||
}
|
||||
|
|
|
@ -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 创建权限
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<script>
|
||||
window.env = {
|
||||
VITE_APP_SERVER_IP: "",
|
||||
VITE_APP_SERVER_PORT: "",
|
||||
VITE_APP_UPLOAD_PORT: "",
|
||||
VITE_APP_APP_NAME: "",
|
||||
VITE_APP_VERSION: "",
|
||||
};
|
||||
</script>
|
||||
<title>信箱</title>
|
||||
<script type="module" crossorigin src="/assets/index-swVR_5uH.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-CMaZkOCT.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script src="browserCheck.js"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue