新增普拉提训练系统的数据库结构和数据导入功能
- 创建普拉提分类和动作数据的SQL导入脚本,支持垫上普拉提和器械普拉提的分类管理 - 实现数据库结构迁移脚本,添加新字段以支持普拉提类型和器械名称 - 更新数据库升级总结文档,详细说明数据库结构变更和数据导入步骤 - 创建训练会话相关表,支持每日训练实例功能 - 引入训练会话管理模块,整合训练计划与实际训练会话的关系
This commit is contained in:
@@ -1,22 +1,38 @@
|
||||
export interface ExerciseLibraryItem {
|
||||
key: string;
|
||||
name: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
category: string; // 中文分类名
|
||||
targetMuscleGroups: string;
|
||||
equipmentName?: string;
|
||||
beginnerReps?: number;
|
||||
beginnerSets?: number;
|
||||
breathingCycles?: number;
|
||||
holdDuration?: number;
|
||||
specialInstructions?: string;
|
||||
}
|
||||
|
||||
export interface ExerciseCategoryDto {
|
||||
key: string; // 英文 key
|
||||
name: string; // 中文名
|
||||
type: 'mat_pilates' | 'equipment_pilates';
|
||||
equipmentName?: string;
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
export interface ExerciseDto {
|
||||
key: string;
|
||||
name: string;
|
||||
description: string;
|
||||
description?: string;
|
||||
categoryKey: string;
|
||||
categoryName: string;
|
||||
targetMuscleGroups: string;
|
||||
equipmentName?: string;
|
||||
beginnerReps?: number;
|
||||
beginnerSets?: number;
|
||||
breathingCycles?: number;
|
||||
holdDuration?: number;
|
||||
specialInstructions?: string;
|
||||
sortOrder?: number;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,14 +2,14 @@ import { Injectable } from '@nestjs/common';
|
||||
import { InjectModel } from '@nestjs/sequelize';
|
||||
import { ExerciseCategory } from './models/exercise-category.model';
|
||||
import { Exercise } from './models/exercise.model';
|
||||
import { ExerciseConfigResponse, ExerciseLibraryItem } from './dto/exercise.dto';
|
||||
import { ExerciseConfigResponse } from './dto/exercise.dto';
|
||||
|
||||
@Injectable()
|
||||
export class ExercisesService {
|
||||
constructor(
|
||||
@InjectModel(ExerciseCategory) private readonly categoryModel: typeof ExerciseCategory,
|
||||
@InjectModel(Exercise) private readonly exerciseModel: typeof Exercise,
|
||||
) {}
|
||||
) { }
|
||||
|
||||
async getConfig(): Promise<ExerciseConfigResponse> {
|
||||
const [categories, exercises] = await Promise.all([
|
||||
@@ -18,65 +18,11 @@ export class ExercisesService {
|
||||
]);
|
||||
|
||||
return {
|
||||
categories: categories.map((c) => ({ key: c.key, name: c.name, sortOrder: c.sortOrder })),
|
||||
exercises: exercises.map((e) => ({
|
||||
key: e.key,
|
||||
name: e.name,
|
||||
description: e.description,
|
||||
categoryKey: e.categoryKey,
|
||||
categoryName: e.categoryName,
|
||||
sortOrder: e.sortOrder,
|
||||
})),
|
||||
categories: categories,
|
||||
exercises,
|
||||
};
|
||||
}
|
||||
|
||||
async seedFromLibrary(library: ExerciseLibraryItem[]): Promise<void> {
|
||||
const categoryNameToKey: Record<string, string> = {};
|
||||
const uniqueCategoryNames = Array.from(new Set(library.map((i) => i.category)));
|
||||
|
||||
uniqueCategoryNames.forEach((name) => {
|
||||
const key = this.slugifyCategory(name);
|
||||
categoryNameToKey[name] = key;
|
||||
});
|
||||
|
||||
await this.categoryModel.bulkCreate(
|
||||
uniqueCategoryNames.map((name, index) => ({
|
||||
key: categoryNameToKey[name],
|
||||
name,
|
||||
sortOrder: index,
|
||||
})),
|
||||
{ ignoreDuplicates: true },
|
||||
);
|
||||
|
||||
await this.exerciseModel.bulkCreate(
|
||||
library.map((item, index) => ({
|
||||
key: item.key,
|
||||
name: item.name,
|
||||
description: item.description,
|
||||
categoryKey: categoryNameToKey[item.category],
|
||||
categoryName: item.category,
|
||||
sortOrder: index,
|
||||
})),
|
||||
{ ignoreDuplicates: true },
|
||||
);
|
||||
}
|
||||
|
||||
private slugifyCategory(name: string): string {
|
||||
const mapping: Record<string, string> = {
|
||||
'核心与腹部': 'core',
|
||||
'脊柱与后链': 'spine_posterior_chain',
|
||||
'侧链与髋': 'lateral_hip',
|
||||
'平衡与支撑': 'balance_support',
|
||||
'进阶控制': 'advanced_control',
|
||||
'柔韧与拉伸': 'mobility_stretch',
|
||||
};
|
||||
return mapping[name] || name
|
||||
.normalize('NFKD')
|
||||
.replace(/[\u0300-\u036f]/g, '')
|
||||
.replace(/[^a-zA-Z0-9]+/g, '_')
|
||||
.replace(/^_+|_+$/g, '')
|
||||
.toLowerCase();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -20,6 +20,20 @@ export class ExerciseCategory extends Model {
|
||||
})
|
||||
declare name: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.ENUM('mat_pilates', 'equipment_pilates'),
|
||||
allowNull: false,
|
||||
comment: '普拉提类型:垫上普拉提或器械普拉提',
|
||||
})
|
||||
declare type: 'mat_pilates' | 'equipment_pilates';
|
||||
|
||||
@Column({
|
||||
type: DataType.STRING,
|
||||
allowNull: true,
|
||||
comment: '器械名称(仅器械普拉提需要)',
|
||||
})
|
||||
declare equipmentName: string;
|
||||
|
||||
@Column({
|
||||
type: DataType.INTEGER,
|
||||
allowNull: false,
|
||||
|
||||
@@ -13,15 +13,36 @@ export class Exercise extends Model {
|
||||
})
|
||||
declare key: string;
|
||||
|
||||
@Column({ type: DataType.STRING, allowNull: false, comment: '名称(含中英文)' })
|
||||
@Column({ type: DataType.STRING, allowNull: false, comment: '动作名称' })
|
||||
declare name: string;
|
||||
|
||||
@Column({ type: DataType.STRING, allowNull: false, comment: '中文分类名(冗余,便于展示)' })
|
||||
declare categoryName: string;
|
||||
|
||||
@Column({ type: DataType.TEXT, allowNull: false, comment: '描述' })
|
||||
@Column({ type: DataType.TEXT, allowNull: true, comment: '动作描述' })
|
||||
declare description: string;
|
||||
|
||||
@Column({ type: DataType.TEXT, allowNull: false, comment: '主要锻炼肌肉群' })
|
||||
declare targetMuscleGroups: string;
|
||||
|
||||
@Column({ type: DataType.STRING, allowNull: true, comment: '器械名称(器械普拉提专用)' })
|
||||
declare equipmentName: string;
|
||||
|
||||
@Column({ type: DataType.INTEGER, allowNull: true, comment: '入门级别建议练习次数' })
|
||||
declare beginnerReps: number;
|
||||
|
||||
@Column({ type: DataType.INTEGER, allowNull: true, comment: '入门级别建议组数' })
|
||||
declare beginnerSets: number;
|
||||
|
||||
@Column({ type: DataType.INTEGER, allowNull: true, comment: '呼吸循环次数(替代普通次数)' })
|
||||
declare breathingCycles: number;
|
||||
|
||||
@Column({ type: DataType.INTEGER, allowNull: true, comment: '保持时间(秒)' })
|
||||
declare holdDuration: number;
|
||||
|
||||
@Column({ type: DataType.STRING, allowNull: true, comment: '特殊说明(如每侧、前后各等)' })
|
||||
declare specialInstructions: string;
|
||||
|
||||
@ForeignKey(() => ExerciseCategory)
|
||||
@Column({ type: DataType.STRING, allowNull: false, comment: '分类键' })
|
||||
declare categoryKey: string;
|
||||
|
||||
Reference in New Issue
Block a user