|
@@ -11,13 +11,14 @@ import { addIcons } from 'ionicons';
|
|
|
import {
|
|
import {
|
|
|
notificationsOutline, calendarNumber, checkmarkCircle, ellipseOutline,
|
|
notificationsOutline, calendarNumber, checkmarkCircle, ellipseOutline,
|
|
|
timeOutline, barbell, body, walk, add, refresh, fitness,
|
|
timeOutline, barbell, body, walk, add, refresh, fitness,
|
|
|
- flame, bicycle, trophy, ellipsisHorizontal, footsteps, footstepsOutline, chatbubbleEllipses } from 'ionicons/icons';
|
|
|
|
|
|
|
+ flame, bicycle, trophy, ellipsisHorizontal, footsteps, footstepsOutline, chatbubbleEllipses, barChartOutline, calendarClearOutline, star, checkmarkCircleOutline } from 'ionicons/icons';
|
|
|
|
|
|
|
|
// 引用fmode-ng智能体组件
|
|
// 引用fmode-ng智能体组件
|
|
|
import { ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
|
|
import { ChatPanelOptions, FmodeChat, FmodeChatMessage, openChatPanelModal } from 'fmode-ng';
|
|
|
import Parse from "parse";
|
|
import Parse from "parse";
|
|
|
import { CloudObject, CloudQuery, CloudUser } from 'src/lib/ncloud';
|
|
import { CloudObject, CloudQuery, CloudUser } from 'src/lib/ncloud';
|
|
|
|
|
|
|
|
|
|
+
|
|
|
// 导入计划创建模态框组件
|
|
// 导入计划创建模态框组件
|
|
|
import { PlanCreationModalComponent } from './plan-creation-modal.component';
|
|
import { PlanCreationModalComponent } from './plan-creation-modal.component';
|
|
|
|
|
|
|
@@ -42,27 +43,27 @@ export class Tab2Page {
|
|
|
let options: ChatPanelOptions = {
|
|
let options: ChatPanelOptions = {
|
|
|
roleId: "2DXJkRsjXK", // 预设,无需更改
|
|
roleId: "2DXJkRsjXK", // 预设,无需更改
|
|
|
onChatInit: (chat: FmodeChat) => {
|
|
onChatInit: (chat: FmodeChat) => {
|
|
|
- // 更新为跑步教练信息
|
|
|
|
|
|
|
+
|
|
|
chat.role.set("name", "陈驰");
|
|
chat.role.set("name", "陈驰");
|
|
|
chat.role.set("title", "跑步教练");
|
|
chat.role.set("title", "跑步教练");
|
|
|
chat.role.set("desc", "前马拉松运动员,10年跑步训练经验");
|
|
chat.role.set("desc", "前马拉松运动员,10年跑步训练经验");
|
|
|
chat.role.set("tags", ['长跑', '间歇训练', '跑姿矫正']);
|
|
chat.role.set("tags", ['长跑', '间歇训练', '跑姿矫正']);
|
|
|
chat.role.set("avatar", "/assets/avatars/runner-coach.jpg");
|
|
chat.role.set("avatar", "/assets/avatars/runner-coach.jpg");
|
|
|
|
|
|
|
|
- // 更新提示词为跑步专项
|
|
|
|
|
|
|
+
|
|
|
chat.role.set("prompt", `
|
|
chat.role.set("prompt", `
|
|
|
# 角色设定
|
|
# 角色设定
|
|
|
您是专业跑步教练陈驰,35岁,前国家马拉松队员。专注于跑步技术、训练计划和伤病预防。
|
|
您是专业跑步教练陈驰,35岁,前国家马拉松队员。专注于跑步技术、训练计划和伤病预防。
|
|
|
`);
|
|
`);
|
|
|
|
|
|
|
|
- // 更新对话分类为跑步主题
|
|
|
|
|
|
|
+
|
|
|
const promptCates = [
|
|
const promptCates = [
|
|
|
{ img: "/assets/icon/distance.png", name: "长跑训练" },
|
|
{ img: "/assets/icon/distance.png", name: "长跑训练" },
|
|
|
{ img: "/assets/icon/speed.png", name: "速度提升" },
|
|
{ img: "/assets/icon/speed.png", name: "速度提升" },
|
|
|
{ img: "/assets/icon/injury.png", name: "伤痛预防" }
|
|
{ img: "/assets/icon/injury.png", name: "伤痛预防" }
|
|
|
];
|
|
];
|
|
|
|
|
|
|
|
- // 更新对话提示内容
|
|
|
|
|
|
|
+
|
|
|
const promptList = [
|
|
const promptList = [
|
|
|
{
|
|
{
|
|
|
cate: "长跑训练",
|
|
cate: "长跑训练",
|
|
@@ -148,7 +149,7 @@ export class Tab2Page {
|
|
|
constructor(
|
|
constructor(
|
|
|
private modalCtrl: ModalController,
|
|
private modalCtrl: ModalController,
|
|
|
) {
|
|
) {
|
|
|
- addIcons({notificationsOutline,calendarNumber,refresh,footsteps,add,chatbubbleEllipses,checkmarkCircle,ellipseOutline,timeOutline,barbell,body,walk,fitness,flame,bicycle,trophy,ellipsisHorizontal,footstepsOutline});
|
|
|
|
|
|
|
+ addIcons({notificationsOutline,calendarNumber,refresh,barChartOutline,timeOutline,footsteps,calendarClearOutline,add,star,checkmarkCircleOutline,chatbubbleEllipses,checkmarkCircle,ellipseOutline,barbell,body,walk,fitness,flame,bicycle,trophy,ellipsisHorizontal,footstepsOutline});
|
|
|
|
|
|
|
|
// 初始化周数据
|
|
// 初始化周数据
|
|
|
this.weekDays = this.generateWeekDays();
|
|
this.weekDays = this.generateWeekDays();
|
|
@@ -238,29 +239,36 @@ export class Tab2Page {
|
|
|
|
|
|
|
|
async loadTrainingPlans() {
|
|
async loadTrainingPlans() {
|
|
|
if (!this.currentUser?.id) return;
|
|
if (!this.currentUser?.id) return;
|
|
|
-
|
|
|
|
|
- const query = new CloudQuery('TrainingPlan');
|
|
|
|
|
- query.equalTo('user', this.currentUser.toPointer());
|
|
|
|
|
-
|
|
|
|
|
- const plans = await query.find();
|
|
|
|
|
-
|
|
|
|
|
- this.myPlans = plans.map(plan => ({
|
|
|
|
|
- id: plan.id,
|
|
|
|
|
- name: plan.get('planName') || '未命名计划',
|
|
|
|
|
- progress: this.calculatePlanProgress(plan),
|
|
|
|
|
- nextTime: this.getNextTrainingTime(plan),
|
|
|
|
|
- remaining: this.getRemainingDays(plan),
|
|
|
|
|
- icon: this.getPlanIcon(plan.get('fitnessGoals')),
|
|
|
|
|
- description: plan.get('description'),
|
|
|
|
|
- durationInWeeks: plan.get('durationInWeeks'),
|
|
|
|
|
- difficultyLevel: plan.get('difficultyLevel')
|
|
|
|
|
- }));
|
|
|
|
|
-
|
|
|
|
|
- // 加载每个计划的任务
|
|
|
|
|
- for (const plan of plans) {
|
|
|
|
|
- if (typeof plan.id === "string") {
|
|
|
|
|
- await this.loadPlanTasks(plan.id);
|
|
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const query = new CloudQuery('TrainingPlan');
|
|
|
|
|
+ query.equalTo('user', this.currentUser.toPointer());
|
|
|
|
|
+
|
|
|
|
|
+ // 确保只加载属于当前用户的计划
|
|
|
|
|
+ const plans = await query.find();
|
|
|
|
|
+
|
|
|
|
|
+ this.myPlans = plans.map(plan => ({
|
|
|
|
|
+ id: plan.id,
|
|
|
|
|
+ name: plan.get('planName') || '未命名计划',
|
|
|
|
|
+ progress: this.calculatePlanProgress(plan),
|
|
|
|
|
+ nextTime: this.getNextTrainingTime(plan),
|
|
|
|
|
+ remaining: this.getRemainingDays(plan),
|
|
|
|
|
+ icon: this.getPlanIcon(plan.get('fitnessGoals')),
|
|
|
|
|
+ description: plan.get('description'),
|
|
|
|
|
+ durationInWeeks: plan.get('durationInWeeks'),
|
|
|
|
|
+ difficultyLevel: plan.get('difficultyLevel'),
|
|
|
|
|
+ startDate: plan.get('startDate') // 添加起始日期
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ // 加载每个计划的任务
|
|
|
|
|
+ for (const plan of plans) {
|
|
|
|
|
+ if (typeof plan.id === "string") {
|
|
|
|
|
+ await this.loadPlanTasks(plan.id);
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('加载训练计划失败:', error);
|
|
|
|
|
+ this.showToast('加载训练计划失败,请重试');
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -294,8 +302,16 @@ export class Tab2Page {
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
getRemainingDays(plan: CloudObject): number {
|
|
getRemainingDays(plan: CloudObject): number {
|
|
|
- // 模拟实现 - 实际应根据计划duration和开始日期计算
|
|
|
|
|
- return Math.floor(Math.random() * 30) + 1;
|
|
|
|
|
|
|
+ const startDate = plan.get('startDate');
|
|
|
|
|
+ if (!startDate) return 0;
|
|
|
|
|
+
|
|
|
|
|
+ const endDate = new Date(startDate);
|
|
|
|
|
+ const weeks = plan.get('durationInWeeks') || 4;
|
|
|
|
|
+ endDate.setDate(endDate.getDate() + weeks * 7);
|
|
|
|
|
+
|
|
|
|
|
+ const today = new Date();
|
|
|
|
|
+ const diffInTime = endDate.getTime() - today.getTime();
|
|
|
|
|
+ return Math.ceil(diffInTime / (1000 * 3600 * 24));
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
getPlanIcon(goals: string[] = []): string {
|
|
getPlanIcon(goals: string[] = []): string {
|
|
@@ -338,40 +354,71 @@ export class Tab2Page {
|
|
|
// 保存新计划
|
|
// 保存新计划
|
|
|
async saveNewPlan(planData: any) {
|
|
async saveNewPlan(planData: any) {
|
|
|
try {
|
|
try {
|
|
|
- // 确保当前用户存在
|
|
|
|
|
if (!this.currentUser || !this.currentUser.id) {
|
|
if (!this.currentUser || !this.currentUser.id) {
|
|
|
throw new Error('用户未登录,无法创建计划');
|
|
throw new Error('用户未登录,无法创建计划');
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- // 创建新的训练计划对象
|
|
|
|
|
- const TrainingPlan = Parse.Object.extend('TrainingPlan');
|
|
|
|
|
- const newPlan = new TrainingPlan();
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const newPlan = new CloudObject('TrainingPlan');
|
|
|
|
|
|
|
|
- // 设置计划属性
|
|
|
|
|
- newPlan.set('planName', planData.name);
|
|
|
|
|
- newPlan.set('description', planData.description);
|
|
|
|
|
- newPlan.set('durationInWeeks', parseInt(planData.duration));
|
|
|
|
|
- newPlan.set('difficultyLevel', planData.difficulty);
|
|
|
|
|
- newPlan.set('fitnessGoals', planData.goals);
|
|
|
|
|
|
|
+ // 确保日期格式正确
|
|
|
|
|
+ const startDate = planData.startDate;
|
|
|
|
|
+ const formattedDate = startDate ? startDate : new Date().toISOString();
|
|
|
|
|
|
|
|
- // 关联当前用户
|
|
|
|
|
- newPlan.set('user', this.currentUser.toPointer());
|
|
|
|
|
|
|
+ newPlan.set({
|
|
|
|
|
+ planName: planData.name,
|
|
|
|
|
+ description: planData.description,
|
|
|
|
|
+ durationInWeeks: parseInt(planData.duration),
|
|
|
|
|
+ difficultyLevel: planData.difficulty,
|
|
|
|
|
+ fitnessGoals: planData.goals,
|
|
|
|
|
+ user: this.currentUser.toPointer(),
|
|
|
|
|
+ startDate: formattedDate
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
- // 保存到后端
|
|
|
|
|
await newPlan.save();
|
|
await newPlan.save();
|
|
|
-
|
|
|
|
|
- // 显示成功消息
|
|
|
|
|
this.showToast('计划创建成功!');
|
|
this.showToast('计划创建成功!');
|
|
|
} catch (error: any) {
|
|
} catch (error: any) {
|
|
|
console.error('创建计划失败:', error);
|
|
console.error('创建计划失败:', error);
|
|
|
this.showToast(error.message || '创建计划失败,请重试');
|
|
this.showToast(error.message || '创建计划失败,请重试');
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
// 显示Toast消息
|
|
// 显示Toast消息
|
|
|
async showToast(message: string) {
|
|
async showToast(message: string) {
|
|
|
// 实际项目中应该使用Ionic的ToastController
|
|
// 实际项目中应该使用Ionic的ToastController
|
|
|
// 这里简化实现为alert
|
|
// 这里简化实现为alert
|
|
|
alert(message);
|
|
alert(message);
|
|
|
}
|
|
}
|
|
|
|
|
+ // 根据难度级别获取徽章颜色
|
|
|
|
|
+ getBadgeColor(difficulty: string): string {
|
|
|
|
|
+ return difficulty.toLowerCase() + '-badge';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取难度文本
|
|
|
|
|
+ getDifficultyText(difficulty: string): string {
|
|
|
|
|
+ const map: Record<string, string> = {
|
|
|
|
|
+ 'beginner': '初级',
|
|
|
|
|
+ 'intermediate': '中级',
|
|
|
|
|
+ 'advanced': '高级'
|
|
|
|
|
+ };
|
|
|
|
|
+ return map[difficulty.toLowerCase()] || difficulty;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 根据计划难度获取卡片颜色
|
|
|
|
|
+ getPlanColor(difficulty: string): string {
|
|
|
|
|
+ const map: Record<string, string> = {
|
|
|
|
|
+ 'beginner': 'linear-gradient(to right, #27ae60, #2ecc71)',
|
|
|
|
|
+ 'intermediate': 'linear-gradient(to right, #f39c12, #f1c40f)',
|
|
|
|
|
+ 'advanced': 'linear-gradient(to right, #e74c3c, #c0392b)'
|
|
|
|
|
+ };
|
|
|
|
|
+ return map[difficulty.toLowerCase()] || '#3a7bd5';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 获取任务标签颜色
|
|
|
|
|
+ getChipColor(difficulty: string): string {
|
|
|
|
|
+ const map: Record<string, string> = {
|
|
|
|
|
+ 'beginner': 'success',
|
|
|
|
|
+ 'intermediate': 'warning',
|
|
|
|
|
+ 'advanced': 'danger'
|
|
|
|
|
+ };
|
|
|
|
|
+ return map[difficulty.toLowerCase()] || 'primary';
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|