Add all files
This commit is contained in:
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/nest-cli",
|
||||
"collection": "@nestjs/schematics",
|
||||
"sourceRoot": "src",
|
||||
"compilerOptions": {
|
||||
"deleteOutDir": true,
|
||||
"exclude": ["node_modules", "dist", ".git", "../dist", "../src"]
|
||||
},
|
||||
"webpack": true
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
{
|
||||
"name": "server",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"description": "NestJS server application",
|
||||
"scripts": {
|
||||
"build": "nest build",
|
||||
"dev": "nest start --watch",
|
||||
"start": "nest start",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "^3.958.0",
|
||||
"@aws-sdk/lib-storage": "^3.958.0",
|
||||
"@nestjs/common": "^10.4.15",
|
||||
"@nestjs/core": "^10.4.15",
|
||||
"@nestjs/platform-express": "^10.4.15",
|
||||
"@supabase/supabase-js": "2.95.3",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.15.1",
|
||||
"coze-coding-dev-sdk": "^0.7.16",
|
||||
"dotenv": "^17.2.3",
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"drizzle-orm": "^0.45.1",
|
||||
"drizzle-zod": "^0.8.3",
|
||||
"express": "5.2.1",
|
||||
"pg": "^8.16.3",
|
||||
"rxjs": "^7.8.1",
|
||||
"zod": "^4.3.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nestjs/cli": "^10.4.9",
|
||||
"@nestjs/schematics": "^10.2.3",
|
||||
"@types/express": "5.0.6",
|
||||
"@types/node": "^22.10.2",
|
||||
"drizzle-kit": "^0.31.8",
|
||||
"typescript": "^5.7.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import { Controller, Get } from '@nestjs/common';
|
||||
import { AppService } from '@/app.service';
|
||||
|
||||
@Controller()
|
||||
export class AppController {
|
||||
constructor(private readonly appService: AppService) {}
|
||||
|
||||
@Get('hello')
|
||||
getHello(): { status: string; data: string } {
|
||||
return {
|
||||
status: 'success',
|
||||
data: this.appService.getHello()
|
||||
};
|
||||
}
|
||||
|
||||
@Get('health')
|
||||
getHealth(): { status: string; data: string } {
|
||||
return {
|
||||
status: 'success',
|
||||
data: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
import { Module } from '@nestjs/common'
|
||||
import { AuthController, StudentController, RecordController } from '@/controllers'
|
||||
import { AuthService, StudentService, RecordService } from '@/services'
|
||||
|
||||
@Module({
|
||||
controllers: [AuthController, StudentController, RecordController],
|
||||
providers: [
|
||||
AuthService,
|
||||
RecordService,
|
||||
{
|
||||
provide: StudentService,
|
||||
useFactory: (recordService: RecordService) => {
|
||||
const studentService = new StudentService()
|
||||
studentService.setGetRecordsFunc(() => recordService.getAllRecords())
|
||||
return studentService
|
||||
},
|
||||
inject: [RecordService]
|
||||
}
|
||||
],
|
||||
})
|
||||
export class AppModule {}
|
||||
@@ -0,0 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export class AppService {
|
||||
getHello(): string {
|
||||
return 'Hello, welcome to coze coding mini-program server!';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Controller, Post, Body } from '@nestjs/common'
|
||||
import { AuthService } from '@/services/auth.service'
|
||||
import { LoginDto } from '@/dto/auth.dto'
|
||||
|
||||
@Controller('auth')
|
||||
export class AuthController {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
@Post('login')
|
||||
async login(@Body() dto: LoginDto) {
|
||||
try {
|
||||
const userInfo = await this.authService.login(dto.code)
|
||||
return {
|
||||
code: 200,
|
||||
msg: '登录成功',
|
||||
data: userInfo
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: error.message || '登录失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './auth.controller'
|
||||
export * from './student.controller'
|
||||
export * from './record.controller'
|
||||
@@ -0,0 +1,80 @@
|
||||
import { Controller, Get, Post, Put, Body, Query, Delete, Param } from '@nestjs/common'
|
||||
import { RecordService } from '@/services/record.service'
|
||||
import { CreateRecordDto, QueryRecordDto } from '@/dto/record.dto'
|
||||
|
||||
@Controller('records')
|
||||
export class RecordController {
|
||||
constructor(private readonly recordService: RecordService) {}
|
||||
|
||||
@Get()
|
||||
async findAll(@Query() query: QueryRecordDto) {
|
||||
try {
|
||||
const result = await this.recordService.findAll(query)
|
||||
return {
|
||||
code: 200,
|
||||
msg: '获取成功',
|
||||
data: result
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: error.message || '获取失败',
|
||||
data: { records: [], statistics: {} }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() dto: CreateRecordDto) {
|
||||
try {
|
||||
const record = await this.recordService.create(dto)
|
||||
return {
|
||||
code: 200,
|
||||
msg: '添加成功',
|
||||
data: record
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 400,
|
||||
msg: error.message || '添加失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async update(@Param('id') id: string, @Body() dto: CreateRecordDto, @Query('openid') openid: string) {
|
||||
try {
|
||||
const record = await this.recordService.update(parseInt(id), dto, openid)
|
||||
return {
|
||||
code: record ? 200 : 400,
|
||||
msg: record ? '更新成功' : '更新失败',
|
||||
data: record
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: error.message || '更新失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(@Param('id') id: string, @Query('openid') openid: string) {
|
||||
try {
|
||||
const success = await this.recordService.delete(parseInt(id), openid)
|
||||
return {
|
||||
code: success ? 200 : 400,
|
||||
msg: success ? '删除成功' : '删除失败',
|
||||
data: null
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: error.message || '删除失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
import { Controller, Get, Post, Put, Delete, Body, Query, Param } from '@nestjs/common'
|
||||
import { StudentService } from '@/services/student.service'
|
||||
import { CreateStudentDto, UpdateStudentDto, QueryStudentDto } from '@/dto/student.dto'
|
||||
|
||||
@Controller('students')
|
||||
export class StudentController {
|
||||
constructor(private readonly studentService: StudentService) {}
|
||||
|
||||
@Get()
|
||||
async findAll(@Query() query: QueryStudentDto) {
|
||||
try {
|
||||
const students = await this.studentService.findAll(query)
|
||||
return {
|
||||
code: 200,
|
||||
msg: '获取成功',
|
||||
data: students
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: error.message || '获取失败',
|
||||
data: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Post()
|
||||
async create(@Body() dto: CreateStudentDto) {
|
||||
try {
|
||||
const student = await this.studentService.create(dto)
|
||||
return {
|
||||
code: 200,
|
||||
msg: '添加成功',
|
||||
data: student
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 400,
|
||||
msg: error.message || '添加失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Put(':id')
|
||||
async update(@Param('id') id: string, @Body() dto: UpdateStudentDto) {
|
||||
try {
|
||||
const student = await this.studentService.update(parseInt(id), dto)
|
||||
if (!student) {
|
||||
return {
|
||||
code: 404,
|
||||
msg: '学员不存在',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
return {
|
||||
code: 200,
|
||||
msg: '更新成功',
|
||||
data: student
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: error.message || '更新失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Delete(':id')
|
||||
async delete(@Param('id') id: string, @Query('openid') openid: string) {
|
||||
try {
|
||||
const success = await this.studentService.delete(parseInt(id), openid)
|
||||
return {
|
||||
code: success ? 200 : 400,
|
||||
msg: success ? '删除成功' : '删除失败',
|
||||
data: null
|
||||
}
|
||||
} catch (error) {
|
||||
return {
|
||||
code: 500,
|
||||
msg: error.message || '删除失败',
|
||||
data: null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { IsString, IsNotEmpty } from 'class-validator'
|
||||
|
||||
export class LoginDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
code: string
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './auth.dto'
|
||||
export * from './student.dto'
|
||||
export * from './record.dto'
|
||||
@@ -0,0 +1,45 @@
|
||||
import { IsString, IsNotEmpty, IsNumber, IsOptional, Min } from 'class-validator'
|
||||
|
||||
export class CreateRecordDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
date: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string
|
||||
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
duration: number
|
||||
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
fee: number
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
openid: string
|
||||
}
|
||||
|
||||
export class QueryRecordDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
openid?: string
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
name?: string
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
weekday?: number
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
month?: string
|
||||
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
year?: number
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
import { IsString, IsNotEmpty, IsOptional, IsNumber, Min, Max } from 'class-validator'
|
||||
|
||||
export class CreateStudentDto {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
studentName: string
|
||||
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
openid: string
|
||||
|
||||
// 0: 正在上课, 1: 已结束上课,默认0
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
status?: number
|
||||
|
||||
// 总课时
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
totalHours?: number
|
||||
|
||||
// 备注
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export class UpdateStudentDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
studentName?: string
|
||||
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(1)
|
||||
@IsOptional()
|
||||
status?: number
|
||||
|
||||
// 总课时
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
totalHours?: number
|
||||
|
||||
// 备注
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
notes?: string
|
||||
}
|
||||
|
||||
export class QueryStudentDto {
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
openid?: string
|
||||
|
||||
// 是否只返回正在上课的学员,0: 返回所有, 1: 只返回正在上课
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
activeOnly?: number
|
||||
|
||||
// 是否只返回已结束的学员
|
||||
@IsNumber()
|
||||
@IsOptional()
|
||||
graduatedOnly?: number
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
export * from './user.entity'
|
||||
export * from './student.entity'
|
||||
export * from './record.entity'
|
||||
@@ -0,0 +1,9 @@
|
||||
export interface ClassRecord {
|
||||
id: number
|
||||
date: string
|
||||
name: string
|
||||
duration: number
|
||||
fee: number
|
||||
createTime: string
|
||||
openid: string
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
export interface Student {
|
||||
id: number
|
||||
studentName: string
|
||||
addTime: string
|
||||
openid: string
|
||||
// 0: 正在上课, 1: 已结束上课
|
||||
status: number
|
||||
// 新增字段
|
||||
totalHours: number // 总课时
|
||||
notes: string // 备注
|
||||
usedHours?: number // 已用课时(查询时计算,不存储)
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
export interface UserInfo {
|
||||
openid: string
|
||||
nickName: string
|
||||
avatarUrl: string
|
||||
createTime: string
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import {
|
||||
Injectable,
|
||||
NestInterceptor,
|
||||
ExecutionContext,
|
||||
CallHandler,
|
||||
} from '@nestjs/common';
|
||||
import { Observable } from 'rxjs';
|
||||
import { map } from 'rxjs/operators';
|
||||
|
||||
@Injectable()
|
||||
export class HttpStatusInterceptor implements NestInterceptor {
|
||||
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
|
||||
const request = context.switchToHttp().getRequest();
|
||||
const response = context.switchToHttp().getResponse();
|
||||
|
||||
// 如果是 POST 请求且状态码为 201,改为 200
|
||||
if (request.method === 'POST' && response.statusCode === 201) {
|
||||
response.status(200);
|
||||
}
|
||||
|
||||
return next.handle();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from '@/app.module';
|
||||
import * as express from 'express';
|
||||
import { HttpStatusInterceptor } from '@/interceptors/http-status.interceptor';
|
||||
|
||||
function parsePort(): number {
|
||||
const args = process.argv.slice(2);
|
||||
const portIndex = args.indexOf('-p');
|
||||
if (portIndex !== -1 && args[portIndex + 1]) {
|
||||
const port = parseInt(args[portIndex + 1], 10);
|
||||
if (!isNaN(port) && port > 0 && port < 65536) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
return 3000;
|
||||
}
|
||||
|
||||
async function bootstrap() {
|
||||
const app = await NestFactory.create(AppModule);
|
||||
|
||||
app.enableCors({
|
||||
origin: true,
|
||||
credentials: true,
|
||||
});
|
||||
app.setGlobalPrefix('api');
|
||||
app.use(express.json({ limit: '50mb' }));
|
||||
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
||||
|
||||
// 全局拦截器:统一将 POST 请求的 201 状态码改为 200
|
||||
app.useGlobalInterceptors(new HttpStatusInterceptor());
|
||||
// 1. 开启优雅关闭 Hooks (关键!)
|
||||
app.enableShutdownHooks();
|
||||
|
||||
// 2. 解析端口
|
||||
const port = parsePort();
|
||||
try {
|
||||
await app.listen(port);
|
||||
console.log(`Server running on http://localhost:${port}`);
|
||||
} catch (err) {
|
||||
if (err.code === 'EADDRINUSE') {
|
||||
console.error(`❌ 端口 \({port} 被占用! 请运行 'npx kill-port \){port}' 然后重试。`);
|
||||
process.exit(1);
|
||||
} else {
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
console.log(`Application is running on: http://localhost:3000`);
|
||||
}
|
||||
bootstrap();
|
||||
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { UserInfo } from '@/entities'
|
||||
|
||||
@Injectable()
|
||||
export class AuthService {
|
||||
private users: Map<string, UserInfo> = new Map()
|
||||
|
||||
async login(code: string): Promise<UserInfo> {
|
||||
// 模拟微信登录:通过 code 生成 openid
|
||||
const openid = `user_${code.substring(0, 8)}_${Date.now()}`
|
||||
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
||||
|
||||
const userInfo: UserInfo = {
|
||||
openid,
|
||||
nickName: `用户${openid.substring(5, 9)}`,
|
||||
avatarUrl: '',
|
||||
createTime: now
|
||||
}
|
||||
|
||||
this.users.set(openid, userInfo)
|
||||
console.log('用户登录:', userInfo)
|
||||
return userInfo
|
||||
}
|
||||
|
||||
async getUserInfo(openid: string): Promise<UserInfo | undefined> {
|
||||
return this.users.get(openid)
|
||||
}
|
||||
|
||||
async updateUserInfo(openid: string, nickName: string, avatarUrl: string): Promise<UserInfo | undefined> {
|
||||
const user = this.users.get(openid)
|
||||
if (user) {
|
||||
user.nickName = nickName
|
||||
user.avatarUrl = avatarUrl
|
||||
return user
|
||||
}
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
export * from './auth.service'
|
||||
export * from './student.service'
|
||||
export * from './record.service'
|
||||
export { RecordQueryResult, RecordStatistics } from './record.service'
|
||||
@@ -0,0 +1,137 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { ClassRecord } from '@/entities'
|
||||
import { CreateRecordDto, QueryRecordDto } from '@/dto'
|
||||
|
||||
export interface RecordStatistics {
|
||||
totalDuration: number
|
||||
totalFee: number
|
||||
recordCount: number
|
||||
studentDuration: number
|
||||
studentFee: number
|
||||
}
|
||||
|
||||
export interface RecordQueryResult {
|
||||
records: ClassRecord[]
|
||||
statistics: RecordStatistics
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class RecordService {
|
||||
private records: ClassRecord[] = []
|
||||
private idCounter = 1
|
||||
|
||||
async create(dto: CreateRecordDto): Promise<ClassRecord> {
|
||||
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
||||
const record: ClassRecord = {
|
||||
id: this.idCounter++,
|
||||
date: dto.date,
|
||||
name: dto.name,
|
||||
duration: dto.duration,
|
||||
fee: dto.fee,
|
||||
createTime: now,
|
||||
openid: dto.openid
|
||||
}
|
||||
|
||||
this.records.push(record)
|
||||
console.log('添加记录:', record)
|
||||
return record
|
||||
}
|
||||
|
||||
async findAll(query: QueryRecordDto): Promise<RecordQueryResult> {
|
||||
let filtered = this.records
|
||||
|
||||
// 按用户筛选
|
||||
if (query.openid) {
|
||||
filtered = filtered.filter((r) => r.openid === query.openid)
|
||||
}
|
||||
|
||||
// 按学员姓名完全匹配筛选
|
||||
if (query.name) {
|
||||
filtered = filtered.filter((r) => r.name === query.name)
|
||||
}
|
||||
|
||||
// 按月份筛选(需要同时匹配年份和月份)
|
||||
if (query.month) {
|
||||
filtered = filtered.filter((r) => {
|
||||
const date = new Date(r.date)
|
||||
// month 参数格式为 "YYYY-MM"
|
||||
const [year, month] = query.month!.split('-').map(Number)
|
||||
return date.getFullYear() === year && date.getMonth() + 1 === month
|
||||
})
|
||||
}
|
||||
|
||||
// 按年份筛选
|
||||
if (query.year) {
|
||||
filtered = filtered.filter((r) => {
|
||||
const date = new Date(r.date)
|
||||
return date.getFullYear() === query.year
|
||||
})
|
||||
}
|
||||
|
||||
// 按日期降序排列
|
||||
filtered.sort((a, b) => new Date(b.date).getTime() - new Date(a.date).getTime())
|
||||
|
||||
// 计算统计数据
|
||||
const statistics = this.calculateStatistics(filtered, query.name)
|
||||
|
||||
return {
|
||||
records: filtered,
|
||||
statistics
|
||||
}
|
||||
}
|
||||
|
||||
private calculateStatistics(records: ClassRecord[], studentName?: string): RecordStatistics {
|
||||
const totalDuration = records.reduce((sum, r) => sum + r.duration, 0)
|
||||
const totalFee = records.reduce((sum, r) => sum + r.fee, 0)
|
||||
const recordCount = records.length
|
||||
|
||||
// 个人统计(如果按姓名筛选)
|
||||
let studentDuration = 0
|
||||
let studentFee = 0
|
||||
if (studentName) {
|
||||
const studentRecords = records.filter((r) => r.name === studentName)
|
||||
studentDuration = studentRecords.reduce((sum, r) => sum + r.duration, 0)
|
||||
studentFee = studentRecords.reduce((sum, r) => sum + r.fee, 0)
|
||||
}
|
||||
|
||||
return {
|
||||
totalDuration,
|
||||
totalFee,
|
||||
recordCount,
|
||||
studentDuration,
|
||||
studentFee
|
||||
}
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<ClassRecord | undefined> {
|
||||
return this.records.find((r) => r.id === id)
|
||||
}
|
||||
|
||||
async delete(id: number, openid: string): Promise<boolean> {
|
||||
const index = this.records.findIndex((r) => r.id === id && r.openid === openid)
|
||||
if (index !== -1) {
|
||||
this.records.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
async update(id: number, dto: Partial<ClassRecord>, openid: string): Promise<ClassRecord | null> {
|
||||
const record = this.records.find((r) => r.id === id && r.openid === openid)
|
||||
if (!record) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (dto.date !== undefined) record.date = dto.date
|
||||
if (dto.name !== undefined) record.name = dto.name
|
||||
if (dto.duration !== undefined) record.duration = dto.duration
|
||||
if (dto.fee !== undefined) record.fee = dto.fee
|
||||
|
||||
return record
|
||||
}
|
||||
|
||||
// 获取所有记录(供其他服务使用)
|
||||
getAllRecords(): ClassRecord[] {
|
||||
return this.records
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
import { Injectable } from '@nestjs/common'
|
||||
import { Student } from '@/entities'
|
||||
import { CreateStudentDto, UpdateStudentDto, QueryStudentDto } from '@/dto'
|
||||
|
||||
// 课时记录接口
|
||||
interface ClassRecord {
|
||||
id: number
|
||||
date: string
|
||||
name: string
|
||||
duration: number
|
||||
fee: number
|
||||
createTime: string
|
||||
openid: string
|
||||
}
|
||||
|
||||
// 获取课时记录的函数类型
|
||||
type GetRecordsFunc = () => ClassRecord[]
|
||||
|
||||
@Injectable()
|
||||
export class StudentService {
|
||||
private students: Student[] = []
|
||||
private idCounter = 1
|
||||
private getRecordsFunc: GetRecordsFunc | null = null
|
||||
|
||||
// 设置获取课时记录的函数
|
||||
setGetRecordsFunc(func: GetRecordsFunc) {
|
||||
this.getRecordsFunc = func
|
||||
}
|
||||
|
||||
async create(dto: CreateStudentDto): Promise<Student> {
|
||||
// 检查是否已存在同名学员
|
||||
const exists = this.students.find(
|
||||
(s) => s.openid === dto.openid && s.studentName === dto.studentName
|
||||
)
|
||||
|
||||
if (exists) {
|
||||
throw new Error('该学员已存在')
|
||||
}
|
||||
|
||||
const now = new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })
|
||||
const student: Student = {
|
||||
id: this.idCounter++,
|
||||
studentName: dto.studentName,
|
||||
addTime: now,
|
||||
openid: dto.openid,
|
||||
// 默认 status 为 0 (正在上课)
|
||||
status: dto.status ?? 0,
|
||||
// 新增字段
|
||||
totalHours: dto.totalHours ?? 0,
|
||||
notes: dto.notes ?? ''
|
||||
}
|
||||
|
||||
this.students.push(student)
|
||||
console.log('添加学员:', student)
|
||||
return student
|
||||
}
|
||||
|
||||
async findAll(dto: QueryStudentDto): Promise<any[]> {
|
||||
let result = this.students
|
||||
|
||||
// 按 openid 筛选
|
||||
if (dto.openid) {
|
||||
result = result.filter((s) => s.openid === dto.openid)
|
||||
}
|
||||
|
||||
// 只返回正在上课的学员
|
||||
if (dto.activeOnly === 1) {
|
||||
result = result.filter((s) => s.status === 0)
|
||||
}
|
||||
|
||||
// 只返回已结束的学员
|
||||
if (dto.graduatedOnly === 1) {
|
||||
result = result.filter((s) => s.status === 1)
|
||||
}
|
||||
|
||||
// 计算每个学员的已用课时
|
||||
const records = this.getRecordsFunc ? this.getRecordsFunc() : []
|
||||
return result.map(student => {
|
||||
const usedHours = records
|
||||
.filter(r => r.name === student.studentName && r.openid === student.openid)
|
||||
.reduce((sum, r) => sum + r.duration, 0)
|
||||
|
||||
return {
|
||||
...student,
|
||||
usedHours: Math.round(usedHours * 10) / 10 // 保留1位小数
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async findOne(id: number): Promise<Student | undefined> {
|
||||
return this.students.find((s) => s.id === id)
|
||||
}
|
||||
|
||||
async update(id: number, dto: UpdateStudentDto): Promise<Student | null> {
|
||||
const student = this.students.find((s) => s.id === id)
|
||||
if (!student) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (dto.studentName !== undefined) {
|
||||
student.studentName = dto.studentName
|
||||
}
|
||||
if (dto.status !== undefined) {
|
||||
student.status = dto.status
|
||||
}
|
||||
if (dto.totalHours !== undefined) {
|
||||
student.totalHours = dto.totalHours
|
||||
}
|
||||
if (dto.notes !== undefined) {
|
||||
student.notes = dto.notes
|
||||
}
|
||||
|
||||
return student
|
||||
}
|
||||
|
||||
async delete(id: number, openid: string): Promise<boolean> {
|
||||
const index = this.students.findIndex((s) => s.id === id && s.openid === openid)
|
||||
if (index !== -1) {
|
||||
this.students.splice(index, 1)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"declaration": true,
|
||||
"removeComments": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"target": "ES2021",
|
||||
"sourceMap": true,
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"incremental": true,
|
||||
"skipLibCheck": true,
|
||||
"strictNullChecks": true,
|
||||
"noImplicitAny": false,
|
||||
"strictBindCallApply": false,
|
||||
"forceConsistentCasingInFileNames": false,
|
||||
"noFallthroughCasesInSwitch": false,
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user