feat(training-plans): 添加训练计划名称字段并实现软删除功能

- 在 DTO 和模型中新增 name 字段,支持可选的计划名称
- 实现分页查询功能,优化列表接口返回结构
- 将删除操作改为软删除,新增 deleted 字段控制
- 更新服务逻辑以支持新字段和分页参数
This commit is contained in:
richarjiang
2025-08-14 17:32:55 +08:00
parent 96a1190f74
commit 9ad65b19fd
4 changed files with 44 additions and 12 deletions

View File

@@ -7,6 +7,11 @@ export class CreateTrainingPlanDto {
@IsDateString() @IsDateString()
startDate: string; startDate: string;
@ApiProperty({ description: '计划名称' })
@IsString()
@IsOptional()
name: string;
@ApiProperty({ enum: ['daysOfWeek', 'sessionsPerWeek'] }) @ApiProperty({ enum: ['daysOfWeek', 'sessionsPerWeek'] })
@IsEnum(['daysOfWeek', 'sessionsPerWeek']) @IsEnum(['daysOfWeek', 'sessionsPerWeek'])
mode: PlanMode; mode: PlanMode;

View File

@@ -15,6 +15,10 @@ export class TrainingPlan extends Model {
@Column({ type: DataType.STRING, allowNull: false }) @Column({ type: DataType.STRING, allowNull: false })
declare userId: string; declare userId: string;
// 计划名称
@Column({ type: DataType.STRING, allowNull: true })
declare name: string;
@Column({ type: DataType.DATE, allowNull: false }) @Column({ type: DataType.DATE, allowNull: false })
declare createdAt: Date; declare createdAt: Date;
@@ -44,6 +48,9 @@ export class TrainingPlan extends Model {
@Column({ type: DataType.DATE, defaultValue: DataType.NOW }) @Column({ type: DataType.DATE, defaultValue: DataType.NOW })
declare updatedAt: Date; declare updatedAt: Date;
@Column({ type: DataType.BOOLEAN, defaultValue: false })
declare deleted: boolean;
} }

View File

@@ -1,4 +1,4 @@
import { Body, Controller, Delete, Get, Param, Post, UseGuards } from '@nestjs/common'; import { Body, Controller, Delete, Get, Param, Post, Query, UseGuards } from '@nestjs/common';
import { ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger'; import { ApiBody, ApiOperation, ApiParam, ApiTags } from '@nestjs/swagger';
import { TrainingPlansService } from './training-plans.service'; import { TrainingPlansService } from './training-plans.service';
import { CreateTrainingPlanDto } from './dto/training-plan.dto'; import { CreateTrainingPlanDto } from './dto/training-plan.dto';
@@ -28,8 +28,12 @@ export class TrainingPlansController {
@Get() @Get()
@ApiOperation({ summary: '训练计划列表' }) @ApiOperation({ summary: '训练计划列表' })
async list(@CurrentUser() user: AccessTokenPayload) { async list(
return this.service.list(user.sub); @CurrentUser() user: AccessTokenPayload,
@Query('page') page: number = 1,
@Query('limit') limit: number = 10,
) {
return this.service.list(user.sub, page, limit);
} }
@Get(':id') @Get(':id')

View File

@@ -19,6 +19,7 @@ export class TrainingPlansService {
const plan = await this.trainingPlanModel.create({ const plan = await this.trainingPlanModel.create({
id, id,
userId, userId,
name: dto.name ?? '',
createdAt, createdAt,
startDate: new Date(dto.startDate), startDate: new Date(dto.startDate),
mode: dto.mode, mode: dto.mode,
@@ -39,7 +40,10 @@ export class TrainingPlansService {
} }
async remove(userId: string, id: string) { async remove(userId: string, id: string) {
const count = await this.trainingPlanModel.destroy({ where: { id, userId } }); const [count] = await this.trainingPlanModel.update(
{ deleted: true },
{ where: { id, userId, deleted: false } }
);
if (count > 0) { if (count > 0) {
await this.activityLogsService.record({ await this.activityLogsService.record({
userId, userId,
@@ -52,14 +56,26 @@ export class TrainingPlansService {
return { success: count > 0 }; return { success: count > 0 };
} }
async list(userId: string) { async list(userId: string, page: number = 1, limit: number = 10) {
const rows = await this.trainingPlanModel.findAll({ where: { userId }, order: [['created_at', 'DESC']] }); const offset = (page - 1) * limit;
return rows.map(r => ({ const { rows, count } = await this.trainingPlanModel.findAndCountAll({
where: { userId, deleted: false },
order: [['created_at', 'DESC']],
limit,
offset,
});
return {
data: rows.map(r => ({
id: r.id, id: r.id,
createdAt: r.createdAt, createdAt: r.createdAt,
startDate: r.startDate, startDate: r.startDate,
goal: r.goal, goal: r.goal,
})); })),
total: count,
page,
limit,
};
} }
async detail(userId: string, id: string) { async detail(userId: string, id: string) {