This commit is contained in:
Li1304553726 2025-05-13 15:58:47 +08:00
parent d817b3e8fe
commit 42a0800e41
59 changed files with 2466 additions and 136 deletions

0
.vscode/extensions.json vendored Normal file → Executable file
View File

0
.vscode/settings.json vendored Normal file → Executable file
View File

View File

@ -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 }) => ({

View File

@ -0,0 +1,9 @@
import { Controller } from '@nestjs/common';
import { CarService } from './car.service';
@Controller('car')
export class CarController {
constructor(private readonly carService: CarService) {}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
import { Controller } from '@nestjs/common';
import { CourseService } from './course.service';
@Controller('course')
export class CourseController {
constructor(private readonly courseService: CourseService) {}
}

View File

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

View File

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

View File

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

View File

@ -0,0 +1,9 @@
import { Controller } from '@nestjs/common';
import { DriverService } from './driver.service';
@Controller('driver')
export class DriverController {
constructor(private readonly driverService: DriverService) {}
}

View File

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

View File

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

View File

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

37
apps/server/src/models/goods/goods.controller.ts Normal file → Executable file
View File

@ -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: [], // 返回搜索结果(示例中为空数组)
};
}
}

9
apps/server/src/models/goods/goods.module.ts Normal file → Executable file
View File

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

View File

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

19
apps/server/src/models/goods/goods.service.ts Normal file → Executable file
View File

@ -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的详细信息
};
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
apps/web/nginx.conf Normal file → Executable file
View File

0
apps/web/src/App.css Normal file → Executable file
View File

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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
apps/web/src/app/main/component/People.tsx Normal file → Executable file
View File

View File

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

View File

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

View File

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

View File

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

View File

@ -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
apps/web/src/app/main/layout/index.tsx Normal file → Executable file
View File

View File

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

View File

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

BIN
apps/web/src/assets/student.png Executable file

Binary file not shown.

View File

0
apps/web/src/components/common/input/InputList.tsx Normal file → Executable file
View File

View File

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

0
apps/web/src/routes/main-route.tsx Normal file → Executable file
View File

0
apps/web/src/utils/index.ts Normal file → Executable file
View File

View File

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

View File

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

View File

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

View File

@ -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
packages/config/src/index.ts Normal file → Executable file
View File

25
web-dist/index.html Executable file
View File

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