|
@@ -1,10 +1,10 @@
|
|
|
import { Component, OnInit, ChangeDetectorRef } from '@angular/core';
|
|
|
import { Router } from '@angular/router';
|
|
|
import { addIcons } from 'ionicons';
|
|
|
-import { checkmarkCircle, trash, calendar, helpCircle, create } from 'ionicons/icons';
|
|
|
+import { checkmarkCircle, sunny, infiniteOutline, alertCircleOutline, bicycleOutline, logoGitlab, trash, calendar, helpCircle, create } from 'ionicons/icons';
|
|
|
import { CommonModule } from '@angular/common';
|
|
|
import { FormsModule } from '@angular/forms';
|
|
|
-import { IonSelect, IonThumbnail, IonCardSubtitle, IonImg, IonCard, IonButtons, IonItem, IonList, IonHeader, IonIcon, IonToolbar, IonContent, IonSegment, IonSegmentButton, IonGrid, IonRow, IonCol, IonButton, IonLabel, IonBadge, IonInput, ModalController, IonCardTitle, IonCardContent, IonCardHeader, IonSelectOption } from '@ionic/angular/standalone';
|
|
|
+import { IonDatetime, IonSelect, IonItemOptions, IonThumbnail, IonCardSubtitle, IonImg, IonCard, IonButtons, IonItem, IonList, IonHeader, IonIcon, IonToolbar, IonContent, IonSegment, IonSegmentButton, IonGrid, IonRow, IonCol, IonButton, IonLabel, IonBadge, IonInput, ModalController, IonCardTitle, IonCardContent, IonCardHeader, IonSelectOption, IonItemSliding, IonItemOption, ToastController } from '@ionic/angular/standalone';
|
|
|
import { FmodeChatCompletion, ImagineWork, DalleOptions, ChatPanelOptions, FmodeChat, FmodeChatMessage, MarkdownPreviewModule, openChatPanelModal } from "fmode-ng";
|
|
|
import { AgentTaskStep } from './agent/agent.task';
|
|
|
import { TaskPoemPictureDesc } from './agent/tasks/poem/poem-desc';
|
|
@@ -18,6 +18,7 @@ import { EditPlanModalComponent } from './edit-plan-modal/edit-plan-modal.compon
|
|
|
import { AlertController } from '@ionic/angular';
|
|
|
import { openUserEditModal } from 'src/lib/user/modal-user-edit/modal-user-edit.component';
|
|
|
import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-login.component';
|
|
|
+import { TestPageComponent } from './test-page/test-page.component';
|
|
|
@Component({
|
|
|
selector: 'app-tab2',
|
|
|
templateUrl: 'tab2.page.html',
|
|
@@ -51,44 +52,282 @@ import { openUserLoginModal } from 'src/lib/user/modal-user-login/modal-user-log
|
|
|
IonButton,
|
|
|
IonLabel,
|
|
|
IonBadge,
|
|
|
- IonInput
|
|
|
+ IonInput,
|
|
|
+ IonDatetime,
|
|
|
+ IonItemOption,
|
|
|
+ IonItemSliding,
|
|
|
+ IonItemOptions,
|
|
|
]
|
|
|
})
|
|
|
export class Tab2Page implements OnInit {
|
|
|
+
|
|
|
selectedTab: string = 'checkin'; // 默认选中的tab
|
|
|
- planList: any[] = [
|
|
|
- ];
|
|
|
- coachList: any[] = [
|
|
|
- ];
|
|
|
+ planList: any[] = [];
|
|
|
+ coachList: any[] = [];
|
|
|
+ planUser: any[] = [];
|
|
|
currentUser: CloudUser | undefined
|
|
|
- constructor(private router: Router, private modalCtrl: ModalController, private cdr: ChangeDetectorRef, private alertController: AlertController) {
|
|
|
- addIcons({ checkmarkCircle, calendar, helpCircle, trash, create });
|
|
|
+ actionTaskVisible = false;
|
|
|
+ healthTaskVisible = false;
|
|
|
+ constructor(private toastController: ToastController, private router: Router, private modalCtrl: ModalController, private cdr: ChangeDetectorRef, private alertController: AlertController) {
|
|
|
+ addIcons({ alertCircleOutline, sunny, checkmarkCircle, calendar, helpCircle, trash, create, logoGitlab, bicycleOutline, infiniteOutline });
|
|
|
this.currentUser = new CloudUser();
|
|
|
}
|
|
|
+ ngOnInit() {
|
|
|
+ this.loadPlanList()
|
|
|
+ this.loadCoachList()
|
|
|
+ this.loadPlanUser()
|
|
|
+
|
|
|
+ }
|
|
|
async loadPlanList() {
|
|
|
let currentUser = new CloudUser();
|
|
|
const cloudQuery = new CloudQuery("fitPlan");
|
|
|
- cloudQuery.equalTo("user", currentUser.toPointer());
|
|
|
- this.planList = await cloudQuery.find();
|
|
|
- this.cdr.detectChanges();
|
|
|
+ if (currentUser) {
|
|
|
+ cloudQuery.equalTo("user", currentUser.toPointer());
|
|
|
+ this.planList = await cloudQuery.find();
|
|
|
+ console.log(this.planList)
|
|
|
+ //排序算法
|
|
|
+ this.planList.sort((a, b) => {
|
|
|
+ const srcIdA = a.get("srcId").match(/\d+/);
|
|
|
+ const srcIdB = b.get("srcId").match(/\d+/);
|
|
|
+ if (srcIdA && srcIdB) {
|
|
|
+ const numA = parseInt(srcIdA[0], 10);
|
|
|
+ const numB = parseInt(srcIdB[0], 10);
|
|
|
+ return numA - numB;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+ });
|
|
|
+ }
|
|
|
}
|
|
|
+
|
|
|
async loadCoachList() {
|
|
|
let query = new CloudQuery("Coach");
|
|
|
this.coachList = await query.find();
|
|
|
- this.cdr.detectChanges();
|
|
|
}
|
|
|
- ngOnInit() {
|
|
|
- this.loadPlanList()
|
|
|
- this.loadCoachList()
|
|
|
+
|
|
|
+ dailyReward = 5;
|
|
|
+ powerForMakup = 30;
|
|
|
+ power: number = 0;
|
|
|
+ realDate: Date = this.correctDate(new Date());
|
|
|
+ days: number = 0; // 总打卡天数
|
|
|
+ consecutiveDays: number = 0; // 连续打卡天数
|
|
|
+ checkInHistory: Set<string> = new Set(); // 已打卡日期集合
|
|
|
+
|
|
|
+ async showToast(message: string, color: string = 'success') {
|
|
|
+ const toast = await this.toastController.create({
|
|
|
+ message: message,
|
|
|
+ duration: 2000,
|
|
|
+ position: 'top',
|
|
|
+ color: color,
|
|
|
+ });
|
|
|
+ toast.present();
|
|
|
}
|
|
|
- ngOnChanges() {
|
|
|
- this.loadPlanList();
|
|
|
- this.loadCoachList()
|
|
|
+
|
|
|
+ // 计算 BMI
|
|
|
+ calculateBMI(height: number, weight: number): number {
|
|
|
+ const heightInMeters = height / 100;
|
|
|
+ const bmi = weight / (heightInMeters * heightInMeters);
|
|
|
+ return parseFloat(bmi.toFixed(2));
|
|
|
}
|
|
|
- ngAfterViewChecked() {
|
|
|
- this.loadPlanList();
|
|
|
- this.loadCoachList()
|
|
|
+
|
|
|
+ // 获取鼓励性话语
|
|
|
+ getEncouragement(bmi: number): string {
|
|
|
+ if (bmi < 18.5) {
|
|
|
+ return '您的BMI偏低,注意保持健康的饮食哦!(๑•́ ₃ •̀๑)';
|
|
|
+ } else if (bmi >= 18.5 && bmi < 24.9) {
|
|
|
+ return '您的BMI在正常范围,继续保持良好的生活方式!(。♥‿♥。)';
|
|
|
+ } else if (bmi >= 25 && bmi < 29.9) {
|
|
|
+ return '您的BMI稍微偏高,可以增加运动,控制饮食!(≧◡≦)';
|
|
|
+ } else {
|
|
|
+ return '您的BMI较高,建议积极锻炼,控制体重!(。•́︿•̀。)';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 加载用户数据
|
|
|
+ async loadPlanUser() {
|
|
|
+ let currentUser = new CloudUser();
|
|
|
+ const cloudQuery = new CloudQuery("fitUser");
|
|
|
+ // 确保 currentUser 和 cloudQuery 都有效
|
|
|
+ if (!currentUser || !cloudQuery) {
|
|
|
+ console.error('Invalid user or cloudQuery');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ // 使用 currentUser 的 pointer 查询用户相关数据
|
|
|
+ cloudQuery.equalTo("user", currentUser.toPointer());
|
|
|
+ try {
|
|
|
+ // 执行查询并获取数据
|
|
|
+ this.planUser = await cloudQuery.find();
|
|
|
+ // 确保查询结果存在且有效
|
|
|
+ if (this.planUser && this.planUser.length > 0) {
|
|
|
+ const user = this.planUser[0];
|
|
|
+ // 获取和处理已打卡日期
|
|
|
+ const checkedDays = user.get("checkeddays") || [];
|
|
|
+ if (Array.isArray(checkedDays)) {
|
|
|
+ checkedDays.forEach((date: string) => {
|
|
|
+ this.checkInHistory.add(date);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ // 安全地获取数据,使用默认值来防止 null 或 undefined 错误
|
|
|
+ this.days = user.get("days") || 0;
|
|
|
+ this.consecutiveDays = user.get("sucdays") || 0;
|
|
|
+ this.power = user.get("power") || 0; // 假设 power 默认是 0
|
|
|
+ } else {
|
|
|
+ console.warn('No user data found');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('Error loading user plan:', error);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ // 计算连续打卡天数
|
|
|
+ calculateConsecutiveDays() {
|
|
|
+ let currentDate = this.realDate;
|
|
|
+ let count = 0;
|
|
|
+ for (let i = 0; i < 30; i++) {
|
|
|
+ let date = new Date(currentDate);
|
|
|
+ date.setDate(date.getDate() - i);
|
|
|
+ let formattedDate = this.formatDate(date);
|
|
|
+ if (this.checkInHistory.has(formattedDate)) {
|
|
|
+ count++;
|
|
|
+ } else {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.consecutiveDays = count;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 格式化日期
|
|
|
+ formatDate(date: Date): string {
|
|
|
+ return date.toISOString().split('T')[0];
|
|
|
+ }
|
|
|
+
|
|
|
+ // 修正日期(解决时区问题)
|
|
|
+ correctDate(date: string | Date): Date {
|
|
|
+ if (date instanceof Date) {
|
|
|
+ return new Date(date.getTime() - date.getTimezoneOffset() * 60000);
|
|
|
+ } else if (typeof date === 'string') {
|
|
|
+ const parsedDate = new Date(date);
|
|
|
+ return new Date(parsedDate.getTime() - parsedDate.getTimezoneOffset() * 60000);
|
|
|
+ }
|
|
|
+ throw new Error("Invalid date format");
|
|
|
+ }
|
|
|
+
|
|
|
+ // 判断是否今天已打卡
|
|
|
+ isCheckedInToday(): boolean {
|
|
|
+ const currentDate = this.realDate;
|
|
|
+ const formattedDate = this.formatDate(currentDate);
|
|
|
+ return this.checkInHistory.has(formattedDate);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取连续打卡奖励
|
|
|
+ getConsecutiveReward(consecutiveDays: number): number {
|
|
|
+ if (consecutiveDays % 30 === 0) {
|
|
|
+ return 30; // 第30天,奖励30
|
|
|
+ } else if (consecutiveDays % 15 === 0) {
|
|
|
+ return 20; // 第15天,奖励20
|
|
|
+ } else if (consecutiveDays % 7 === 0) {
|
|
|
+ return 10; // 第7天,奖励10
|
|
|
+ }
|
|
|
+ return 0; // 不符合任何条件时不奖励
|
|
|
}
|
|
|
+
|
|
|
+ // 打卡操作
|
|
|
+ async markAttendance() {
|
|
|
+ const currentDate = this.realDate;
|
|
|
+ const formattedDate = this.formatDate(currentDate);
|
|
|
+
|
|
|
+ // 如果今天已经打卡
|
|
|
+ if (this.checkInHistory.has(formattedDate)) {
|
|
|
+ this.showToast('今天已经打卡过了', 'warning');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果是未来日期
|
|
|
+ if (currentDate > this.correctDate(new Date())) {
|
|
|
+ this.showToast('不能打卡未来的日期', 'danger');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 正常打卡
|
|
|
+ this.checkInHistory.add(formattedDate);
|
|
|
+ this.days = this.checkInHistory.size;
|
|
|
+ this.calculateConsecutiveDays();
|
|
|
+
|
|
|
+ let currentUser = new CloudUser();
|
|
|
+ const cloudQuery = new CloudQuery("fitUser");
|
|
|
+ cloudQuery.equalTo("user", currentUser.toPointer());
|
|
|
+ const userData = await cloudQuery.find();
|
|
|
+
|
|
|
+ if (userData.length > 0) {
|
|
|
+ const user = userData[0];
|
|
|
+ let checkedDays = user.get("checkeddays") || [];
|
|
|
+ checkedDays.push(formattedDate);
|
|
|
+ user.set({ "checkeddays": checkedDays });
|
|
|
+ user.set({ "days": this.days });
|
|
|
+ user.set({ "sucdays": this.consecutiveDays });
|
|
|
+
|
|
|
+ // 计算奖励
|
|
|
+ let totalReward = this.dailyReward; // 每天签到奖励
|
|
|
+ totalReward += this.getConsecutiveReward(this.consecutiveDays); // 连续签到奖励
|
|
|
+ user.set({ "power": user.get("power") + totalReward }); // 增加总 power 奖励
|
|
|
+
|
|
|
+ await user.save();
|
|
|
+ this.loadPlanUser();
|
|
|
+ this.showToast('打卡成功,获得了 ' + totalReward + ' Power');
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 补签操作
|
|
|
+ async handleMakeupSignIn(user: CloudUser): Promise<string> {
|
|
|
+ if (user.get("power") >= this.powerForMakup) {
|
|
|
+ user.set({ "power": user.get("power") - this.powerForMakup });
|
|
|
+ await user.save();
|
|
|
+ return '补签成功!';
|
|
|
+ } else {
|
|
|
+ return '补签失败,您的动能不足!';
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 补签点击事件
|
|
|
+ async handleMakeupClick() {
|
|
|
+ const confirmed = window.confirm('补签将消耗 ' + this.powerForMakup + ' 动能,确定补签吗?');
|
|
|
+ if (confirmed) {
|
|
|
+ let currentUser = new CloudUser();
|
|
|
+ const cloudQuery = new CloudQuery("fitUser");
|
|
|
+ cloudQuery.equalTo("user", currentUser.toPointer());
|
|
|
+ const userData = await cloudQuery.find();
|
|
|
+
|
|
|
+ if (userData.length > 0) {
|
|
|
+ const user = userData[0];
|
|
|
+ const resultMessage = await this.handleMakeupSignIn(user);
|
|
|
+ this.showToast(resultMessage, resultMessage.includes('成功') ? 'success' : 'danger');
|
|
|
+ this.loadPlanUser();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 日期变化时更新
|
|
|
+ onDateChange(event: any) {
|
|
|
+ this.realDate = new Date(this.correctDate(event.detail.value));
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取按钮状态(判断打卡、补签等)
|
|
|
+ getButtonState(date: Date): { isDisabled: boolean, buttonText: string } {
|
|
|
+ const formattedDate = this.formatDate(date);
|
|
|
+
|
|
|
+ if (formattedDate > this.formatDate(this.correctDate(new Date()))) {
|
|
|
+ // 未来日期
|
|
|
+ return { isDisabled: true, buttonText: '无法签到' };
|
|
|
+ } else if (this.checkInHistory.has(formattedDate)) {
|
|
|
+ // 已经打卡过
|
|
|
+ return { isDisabled: true, buttonText: '今天已经打卡' };
|
|
|
+ } else if (formattedDate < this.formatDate(this.correctDate(new Date()))) {
|
|
|
+ // 过去的日期
|
|
|
+ return { isDisabled: false, buttonText: '补签' };
|
|
|
+ }
|
|
|
+ return { isDisabled: false, buttonText: '打卡' };
|
|
|
+ }
|
|
|
+
|
|
|
async login() {
|
|
|
let user = await openUserLoginModal(this.modalCtrl);
|
|
|
if (user?.id) {
|
|
@@ -104,6 +343,7 @@ export class Tab2Page implements OnInit {
|
|
|
}
|
|
|
logout() {
|
|
|
this.currentUser?.logout();
|
|
|
+
|
|
|
}
|
|
|
|
|
|
editUser() {
|
|
@@ -120,7 +360,27 @@ export class Tab2Page implements OnInit {
|
|
|
componentProps: { plan: day }
|
|
|
}).then(modal => {
|
|
|
modal.present();
|
|
|
+ modal.onDidDismiss().then((result) => {
|
|
|
+ if (result.data) {
|
|
|
+ const updatedPlan = result.data;
|
|
|
+ const index = this.planList.findIndex(item => item.id === updatedPlan.id);
|
|
|
+ if (index !== -1) {
|
|
|
+ this.planList[index] = updatedPlan;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ this.loadPlanList()
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+ regeneratePlan() {
|
|
|
+ console.log('重新生成计划:');
|
|
|
|
|
|
+ // 创建一个弹出框
|
|
|
+ this.modalCtrl.create({
|
|
|
+ component: TestPageComponent,
|
|
|
+ componentProps: {}
|
|
|
+ }).then(modal => {
|
|
|
+ modal.present();
|
|
|
modal.onDidDismiss().then((result) => {
|
|
|
if (result.data) {
|
|
|
const updatedPlan = result.data;
|
|
@@ -129,6 +389,7 @@ export class Tab2Page implements OnInit {
|
|
|
this.planList[index] = updatedPlan;
|
|
|
}
|
|
|
}
|
|
|
+ this.loadPlanList()
|
|
|
});
|
|
|
});
|
|
|
}
|
|
@@ -163,9 +424,9 @@ export class Tab2Page implements OnInit {
|
|
|
|
|
|
await alert.present();
|
|
|
}
|
|
|
-
|
|
|
//任务链
|
|
|
- taskList: AgentTaskStep[] = []
|
|
|
+ actionTaskList: AgentTaskStep[] = []
|
|
|
+ healthTaskList: AgentTaskStep[] = []
|
|
|
//一个等待一秒的函数 每经过一秒
|
|
|
wait(duration: number = 1000) {
|
|
|
return new Promise(resolve => {
|
|
@@ -177,13 +438,15 @@ export class Tab2Page implements OnInit {
|
|
|
shareData: any = {}
|
|
|
// 任务:完成故事意境描述及图像绘制
|
|
|
doPoemTask() {
|
|
|
+ this.actionTaskVisible = true;
|
|
|
let task1 = TaskPoemPictureDesc({ shareData: this.shareData, modalCtrl: this.modalCtrl });
|
|
|
let task2 = TaskPoemPictureCreate({ shareData: this.shareData, modalCtrl: this.modalCtrl });
|
|
|
let PoemTaskList = [task1, task2]
|
|
|
- this.taskList = PoemTaskList
|
|
|
+ this.actionTaskList = PoemTaskList
|
|
|
startTask(PoemTaskList)
|
|
|
}
|
|
|
doInqueryTask() {
|
|
|
+ this.healthTaskVisible = true;
|
|
|
let task1 = TaskInqueryUserStory({ shareData: this.shareData, modalCtrl: this.modalCtrl });
|
|
|
let task2 = TaskInqueryDoctorQuestion({ shareData: this.shareData, modalCtrl: this.modalCtrl });
|
|
|
let task3 = TaskInqueryUserAnswer({ shareData: this.shareData, modalCtrl: this.modalCtrl });
|
|
@@ -192,7 +455,7 @@ export class Tab2Page implements OnInit {
|
|
|
task1, task2, task3
|
|
|
]
|
|
|
// 传递给显示组件
|
|
|
- this.taskList = InquireServiceTaskList
|
|
|
+ this.healthTaskList = InquireServiceTaskList
|
|
|
// 开始执行任务
|
|
|
startTask(InquireServiceTaskList)
|
|
|
}
|
|
@@ -232,7 +495,6 @@ export class Tab2Page implements OnInit {
|
|
|
if (currentUser?.id) {
|
|
|
ACL[currentUser?.id] = { read: true, write: true }
|
|
|
}
|
|
|
- localStorage.setItem("company", "E4KpGvTEto")
|
|
|
consult.set({
|
|
|
title: `交流记录${dateStr}-${coach?.get("name")}`,
|
|
|
coach: coach.toPointer(),
|
|
@@ -245,12 +507,13 @@ export class Tab2Page implements OnInit {
|
|
|
console.log("onChatInit");
|
|
|
console.log("预设角色", chat.role);
|
|
|
chat.role.set("name", coach?.get("name"));
|
|
|
- chat.role.set("title", "职业教练");
|
|
|
+ chat.role.set("title", "职业健身教练");
|
|
|
+ chat.role.set("desc", `一名${coach?.get("desc")},${coach?.get("name")},年龄${coach?.get("age")}岁`);
|
|
|
chat.role.set("tags", coach?.get("specialize"));
|
|
|
- chat.role.set("avatar", "../../assets/images/coach2.jpg")
|
|
|
+ chat.role.set("avatar", coach?.get("avater") || "../../assets/images/coach1.jpg")
|
|
|
chat.role.set("prompt", `
|
|
|
# 角色设定
|
|
|
-您是${coach?.get("name")},年龄${coach?.get("age")},特长为${coach?.get("specialize")},要完成一次教练与学员之间的锻炼部位交流。
|
|
|
+您是${coach?.get("name")},年龄${coach?.get("age")},特长为${coach?.get("specialize")},要完成一次教练与学员之间的健身交流。
|
|
|
|
|
|
# 对话环节
|
|
|
0.导诊(根据用户基本情况,引导选择合适的训练计划)
|
|
@@ -267,7 +530,7 @@ export class Tab2Page implements OnInit {
|
|
|
- 完成训练计划时,请在消息结尾附带: [交流完成]
|
|
|
# 开始话语
|
|
|
当您准备好了,可以以一个健身教练的身份,向来访的学员打招呼。
|
|
|
-你好!欢迎来到健身房,我是${coach?.get("name")}教练。今天你想要专注锻炼哪个部位呢?或者有什么具体的健身目标吗?`);
|
|
|
+你好!欢迎来到健身房,我是${coach?.get("name")}教练,${coach?.get("desc")}。今天你想要专注锻炼哪个部位呢?或者有什么具体的健身目标吗?`);
|
|
|
},
|
|
|
onMessage: (chat: FmodeChat, message: FmodeChatMessage) => {
|
|
|
console.log("onMessage", message)
|