|
|
|
import { defineStore } from 'pinia';
|
|
|
|
import ExercisesApi from '@/sheep/api/motion/exercises';
|
|
|
|
import SupersetsApi from '@/sheep/api/motion/supersets';
|
|
|
|
import TemplatesApi from '@/sheep/api/Template/Templates';
|
|
|
|
|
|
|
|
export const useTrainingStore = defineStore('training', {
|
|
|
|
state: () => ({
|
|
|
|
id: null, // 页面传过来的 id
|
|
|
|
type: null, // 页面传过来的 type
|
|
|
|
actionDetail: {}, // 接口返回的详情
|
|
|
|
loading: false,
|
|
|
|
unitRecords: {}, // 训练数据
|
|
|
|
trainingName: '',
|
|
|
|
// 训练时间
|
|
|
|
totalSeconds: 0,
|
|
|
|
isPause: true,
|
|
|
|
timerInterval: null,
|
|
|
|
showPicker: false,
|
|
|
|
defaultTimeIndex: [0, 0, 0],
|
|
|
|
trainingTimeText: '', //起始时间
|
|
|
|
min: false,
|
|
|
|
isSystem: 1, //官方模板为1
|
|
|
|
dailyTemplateId: null,
|
|
|
|
isTraining: false,
|
|
|
|
}),
|
|
|
|
|
|
|
|
actions: {
|
|
|
|
// 1. 根据 id 和 type 加载数据(动作/超级组/模板)
|
|
|
|
async loadTrainingDetail(id, type) {
|
|
|
|
console.log('进入pinia的模板数据加载函数');
|
|
|
|
|
|
|
|
if (!id || !type) return;
|
|
|
|
// 先保存 id 和 type 到 store
|
|
|
|
this.id = id;
|
|
|
|
this.type = type;
|
|
|
|
this.loading = true;
|
|
|
|
try {
|
|
|
|
let res;
|
|
|
|
if (type === 1) {
|
|
|
|
res = await ExercisesApi.getExerciseById(id);
|
|
|
|
} else if (type === 2) {
|
|
|
|
res = await SupersetsApi.getSupersetsInfo(id);
|
|
|
|
} else if (type === 3) {
|
|
|
|
if (this.isSystem === 1) {
|
|
|
|
res = await TemplatesApi.getTemplateDetail(id);
|
|
|
|
} else {
|
|
|
|
res = await TemplatesApi.getCustTemplateDetail(id);
|
|
|
|
}
|
|
|
|
console.log('pinia中打开模板数据:', res);
|
|
|
|
}
|
|
|
|
if (res?.data) {
|
|
|
|
this.actionDetail = res.data;
|
|
|
|
console.log('加载完模板数据之后进行赋值动态数据:++++++');
|
|
|
|
if (type === 3) {
|
|
|
|
this.initTemplateRecords();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (err) {
|
|
|
|
console.error('加载训练详情失败:', err);
|
|
|
|
} finally {
|
|
|
|
this.loading = false;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
loadDailyTemplateForEdit(fullDailyPlan) {
|
|
|
|
|
|
|
|
// 2. 🔥 核心:把【完整每日模板数据】直接赋值给 actionDetail
|
|
|
|
// 动作、顺序、超级组、组数、全部保留,不请求接口
|
|
|
|
this.actionDetail = { ...fullDailyPlan };
|
|
|
|
|
|
|
|
console.log('✅ 每日模板编辑:已赋值完整模板数据到 actionDetail', this.actionDetail);
|
|
|
|
|
|
|
|
// 3. 固定类型为模板
|
|
|
|
this.type = 3;
|
|
|
|
|
|
|
|
// 4. 保存ID
|
|
|
|
this.id = fullDailyPlan.templateId;
|
|
|
|
|
|
|
|
// 5. 初始化训练记录(重量、次数、完成状态)
|
|
|
|
this.unitRecords = {};
|
|
|
|
const units = this.actionDetail?.units || [];
|
|
|
|
console.log('每日模板的unit',units);
|
|
|
|
|
|
|
|
units.forEach((unit, unitIndex) => {
|
|
|
|
let records = {};
|
|
|
|
// 按顺序赋值,不依赖 exerciseId,动作可改、可换
|
|
|
|
unit.exercises?.forEach((ex, exIndex) => {
|
|
|
|
records[ex.exerciseId] = ex.sets || [];
|
|
|
|
});
|
|
|
|
|
|
|
|
this.unitRecords[unitIndex] = {
|
|
|
|
records: records,
|
|
|
|
userWeight: 70,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
console.log('✅ 每日模板加载完成:', this.actionDetail, this.unitRecords);
|
|
|
|
},
|
|
|
|
|
|
|
|
// 初始化模板自带的训练数据到 unitRecords
|
|
|
|
initTemplateRecords() {
|
|
|
|
console.log('✅=================== 我执行了!开始初始化模板数据');
|
|
|
|
console.log('EEEEEEEEEEEEEEEEEEEEEE');
|
|
|
|
// 只在空的时候初始化(避免重复覆盖)
|
|
|
|
if (Object.keys(this.unitRecords).length > 0) return;
|
|
|
|
if (this.dailyTemplateId) return;
|
|
|
|
|
|
|
|
const units = this.actionDetail?.units || [];
|
|
|
|
|
|
|
|
units.forEach((unit, unitIndex) => {
|
|
|
|
let records = {};
|
|
|
|
const exercises = unit.exercises || [];
|
|
|
|
|
|
|
|
exercises.forEach((ex) => {
|
|
|
|
const sets = ex.sets || [];
|
|
|
|
|
|
|
|
// 转换 sets → 页面标准格式
|
|
|
|
const converted = sets.map((set) => {
|
|
|
|
const totalSec = set.duration || 0;
|
|
|
|
const h = String(Math.floor(totalSec / 3600)).padStart(2, '0');
|
|
|
|
const m = String(Math.floor((totalSec % 3600) / 60)).padStart(2, '0');
|
|
|
|
const s = String(totalSec % 60).padStart(2, '0');
|
|
|
|
return {
|
|
|
|
weight: set.weight ?? '',
|
|
|
|
reps: set.reps ?? '',
|
|
|
|
duration: set.duration ?? '',
|
|
|
|
distance: set.distance ?? '',
|
|
|
|
restTime: set.restTime ?? '',
|
|
|
|
h,
|
|
|
|
m,
|
|
|
|
s,
|
|
|
|
quickTimeDisplay: (set.restTime || 60) + 's',
|
|
|
|
// isActive: false,
|
|
|
|
isActive: (set.isCompleted ?? 0) === 1,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
records[ex.exerciseId] = converted;
|
|
|
|
});
|
|
|
|
|
|
|
|
// 直接存入 Pinia
|
|
|
|
this.unitRecords[unitIndex] = {
|
|
|
|
records: records,
|
|
|
|
userWeight: 70,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
//新增:每日模板专用赋值(真实训练记录)
|
|
|
|
initDailyTemplateRecords() {
|
|
|
|
console.log('✅ 每日模板:直接赋值真实训练记录');
|
|
|
|
this.unitRecords = {};
|
|
|
|
|
|
|
|
const units = this.actionDetail?.units || [];
|
|
|
|
|
|
|
|
units.forEach((unit, unitIndex) => {
|
|
|
|
let records = {};
|
|
|
|
const exercises = unit.exercises || [];
|
|
|
|
|
|
|
|
// exercises.forEach((ex) => {
|
|
|
|
// records[ex.exerciseId] = ex.sets || [];
|
|
|
|
// });
|
|
|
|
exercises.forEach((ex) => {
|
|
|
|
const sets = ex.sets || [];
|
|
|
|
|
|
|
|
// 转换 sets → 页面标准格式
|
|
|
|
const converted = sets.map((set) => {
|
|
|
|
const totalSec = set.duration || 0;
|
|
|
|
const h = String(Math.floor(totalSec / 3600)).padStart(2, '0');
|
|
|
|
const m = String(Math.floor((totalSec % 3600) / 60)).padStart(2, '0');
|
|
|
|
const s = String(totalSec % 60).padStart(2, '0');
|
|
|
|
return {
|
|
|
|
weight: set.weight ?? '',
|
|
|
|
reps: set.reps ?? '',
|
|
|
|
duration: set.duration ?? '',
|
|
|
|
distance: set.distance ?? '',
|
|
|
|
restTime: set.restTime ?? '',
|
|
|
|
h,
|
|
|
|
m,
|
|
|
|
s,
|
|
|
|
quickTimeDisplay: (set.restTime || 60) + 's',
|
|
|
|
// isActive: false,
|
|
|
|
isActive: (set.isCompleted ?? 0) === 1,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
converted.forEach((item, idx) => {
|
|
|
|
console.log(`第${idx}组:`, item.h, item.m, item.s);
|
|
|
|
});
|
|
|
|
records[ex.exerciseId] = converted;
|
|
|
|
});
|
|
|
|
|
|
|
|
this.unitRecords[unitIndex] = {
|
|
|
|
records: records,
|
|
|
|
userWeight: 70,
|
|
|
|
};
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
// ======================
|
|
|
|
// ✅ 工具方法放到这里
|
|
|
|
// ======================
|
|
|
|
convertUnitToActionDetail(unit) {
|
|
|
|
//转为动作
|
|
|
|
const convertExercises = (list) => {
|
|
|
|
return (list || []).map((item) => ({
|
|
|
|
id: item.exerciseId,
|
|
|
|
name: item.exerciseName,
|
|
|
|
// urlImage: item.exerciseCover || item.url3dAnimation,
|
|
|
|
urlImage: item.url3dAnimation || item.urlImage,
|
|
|
|
exerciseType: item.exerciseType,
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
|
|
|
if (unit.unitType === 2) {
|
|
|
|
return {
|
|
|
|
id: unit.supersetId || unit.unitId,
|
|
|
|
name: unit.unitName,
|
|
|
|
exercises: convertExercises(unit.exercises),
|
|
|
|
};
|
|
|
|
}
|
|
|
|
if (unit.unitType === 1) {
|
|
|
|
const act = (unit.exercises || [])[0] || {};
|
|
|
|
return {
|
|
|
|
id: act.exerciseId,
|
|
|
|
name: act.exerciseName,
|
|
|
|
urlImage: act.exerciseCover || act.url3dAnimation,
|
|
|
|
exerciseType: act.exerciseType,
|
|
|
|
categoryDescription: act.categoryDescription,
|
|
|
|
equipmentDescription: act.equipmentDescription,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return {};
|
|
|
|
},
|
|
|
|
|
|
|
|
// 替换动作时转换id 和 type 方便替换 新的数据详情 (actionDetail)
|
|
|
|
replaceAction(newId, newType, newDetail) {
|
|
|
|
this.id = newId;
|
|
|
|
this.type = newType;
|
|
|
|
this.actionDetail = { ...newDetail };
|
|
|
|
this.unitRecords = {};
|
|
|
|
},
|
|
|
|
|
|
|
|
// 把当前 动作/超级组 转为模板结构(type=3)
|
|
|
|
convertToTemplate() {
|
|
|
|
// 已经是模板,不用转
|
|
|
|
if (this.type === 3) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
const units = [];
|
|
|
|
|
|
|
|
// 1. 动作 type=1 → 转 unitType=1
|
|
|
|
if (this.type === 1) {
|
|
|
|
units.push({
|
|
|
|
unitType: 1,
|
|
|
|
unitId: this.actionDetail.id,
|
|
|
|
unitName: this.actionDetail.name,
|
|
|
|
exercises: [
|
|
|
|
{
|
|
|
|
exerciseId: this.actionDetail.id,
|
|
|
|
exerciseName: this.actionDetail.name,
|
|
|
|
exerciseType: this.actionDetail.exerciseType,
|
|
|
|
urlImage: this.actionDetail.urlImage || this.actionDetail.url3dAnimation,
|
|
|
|
categoryDescription: this.actionDetail.categoryDescription,
|
|
|
|
equipmentDescription: this.actionDetail.equipmentDescription,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// 2. 超级组 type=2 → 转 unitType=2
|
|
|
|
else if (this.type === 2) {
|
|
|
|
units.push({
|
|
|
|
unitType: 2,
|
|
|
|
unitId: this.actionDetail.id,
|
|
|
|
supersetId: this.actionDetail.id,
|
|
|
|
unitName: this.actionDetail.name,
|
|
|
|
exercises: this.actionDetail.exercises.map((item) => ({
|
|
|
|
exerciseId: item.id,
|
|
|
|
exerciseName: item.name,
|
|
|
|
exerciseType: item.exerciseType,
|
|
|
|
urlImage: item.url3dAnimation || item.urlImage,
|
|
|
|
})),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
// 切换为模板模式
|
|
|
|
this.type = 3;
|
|
|
|
this.actionDetail = {
|
|
|
|
units,
|
|
|
|
templateName: this.actionDetail.name || '训练',
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
// 训练数据的处理
|
|
|
|
// 保存一个动作的训练数据
|
|
|
|
saveUnitRecord(unitIndex, data) {
|
|
|
|
this.unitRecords[unitIndex] = {
|
|
|
|
records: data.records || {},
|
|
|
|
userWeight: data.userWeight ?? 70,
|
|
|
|
};
|
|
|
|
},
|
|
|
|
|
|
|
|
//获取一个动作的训练数据
|
|
|
|
getUnitRecord(unitIndex) {
|
|
|
|
return (
|
|
|
|
this.unitRecords[unitIndex] || {
|
|
|
|
records: {},
|
|
|
|
userWeight: 70,
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
//删除一个动作的所有数据
|
|
|
|
deleteUnitRecord(unitIndex) {
|
|
|
|
delete this.unitRecords[unitIndex];
|
|
|
|
},
|
|
|
|
|
|
|
|
// 单独更新某个动作的体重
|
|
|
|
updateUnitUserWeight(unitIndex, userWeight) {
|
|
|
|
// 如果这个 unit 还没有数据,先创建一个空结构
|
|
|
|
if (!this.unitRecords[unitIndex]) {
|
|
|
|
this.unitRecords[unitIndex] = {
|
|
|
|
records: {},
|
|
|
|
userWeight: 70,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// 直接修改体重
|
|
|
|
this.unitRecords[unitIndex].userWeight = userWeight;
|
|
|
|
},
|
|
|
|
|
|
|
|
// 设置训练名称
|
|
|
|
setTrainingName(name) {
|
|
|
|
this.trainingName = name;
|
|
|
|
},
|
|
|
|
|
|
|
|
// 获取训练名称
|
|
|
|
getTrainingName() {
|
|
|
|
return this.trainingName || '训练';
|
|
|
|
},
|
|
|
|
|
|
|
|
// ======================
|
|
|
|
// 👇 新增 全局计时 方法
|
|
|
|
// ======================
|
|
|
|
toggleTimer() {
|
|
|
|
this.isPause = !this.isPause;
|
|
|
|
if (!this.isPause) {
|
|
|
|
// 开启定时器
|
|
|
|
this.timerInterval = setInterval(() => {
|
|
|
|
this.totalSeconds++;
|
|
|
|
}, 1000);
|
|
|
|
} else {
|
|
|
|
// 暂停定时器
|
|
|
|
clearInterval(this.timerInterval);
|
|
|
|
this.timerInterval = null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
resetTimer() {
|
|
|
|
this.totalSeconds = 0;
|
|
|
|
},
|
|
|
|
setTotalSeconds(seconds) {
|
|
|
|
this.totalSeconds = seconds;
|
|
|
|
},
|
|
|
|
openTimePicker() {
|
|
|
|
const h = Math.floor(this.totalSeconds / 3600);
|
|
|
|
const m = Math.floor((this.totalSeconds % 3600) / 60);
|
|
|
|
const s = this.totalSeconds % 60;
|
|
|
|
this.defaultTimeIndex = [h, m, s];
|
|
|
|
this.showPicker = true;
|
|
|
|
},
|
|
|
|
closeTimePicker() {
|
|
|
|
this.showPicker = false;
|
|
|
|
},
|
|
|
|
// 清空计时(在clearStore时调用)
|
|
|
|
clearTimer() {
|
|
|
|
if (this.timerInterval) {
|
|
|
|
clearInterval(this.timerInterval);
|
|
|
|
this.timerInterval = null;
|
|
|
|
}
|
|
|
|
this.totalSeconds = 0;
|
|
|
|
this.isPause = true;
|
|
|
|
this.showPicker = false;
|
|
|
|
},
|
|
|
|
|
|
|
|
setTrainingTimeText(val) {
|
|
|
|
this.trainingTimeText = val;
|
|
|
|
},
|
|
|
|
// 清空
|
|
|
|
clearTrainingStore() {
|
|
|
|
this.clearTimer();
|
|
|
|
this.id = null;
|
|
|
|
this.type = null;
|
|
|
|
this.actionDetail = {};
|
|
|
|
this.loading = false;
|
|
|
|
this.unitRecords = {};
|
|
|
|
this.trainingTimeText = '';
|
|
|
|
this.min = false;
|
|
|
|
this.isSystem = 1;
|
|
|
|
},
|
|
|
|
},
|
|
|
|
|
|
|
|
persist: true, // 持久化
|
|
|
|
}); |
...
|
...
|
|