Răsfoiți Sursa

Merge branch 'xk'

xukang 3 luni în urmă
părinte
comite
bb9677af2b
30 a modificat fișierele cu 1091 adăugiri și 216 ștergeri
  1. 19 6
      TFPower-app/angular.json
  2. 8 0
      TFPower-app/package-lock.json
  3. 1 0
      TFPower-app/package.json
  4. 54 0
      TFPower-app/src/app/tab2/rili/rili.component.html
  5. 58 0
      TFPower-app/src/app/tab2/rili/rili.component.scss
  6. 22 0
      TFPower-app/src/app/tab2/rili/rili.component.spec.ts
  7. 234 0
      TFPower-app/src/app/tab2/rili/rili.component.ts
  8. 34 44
      TFPower-app/src/app/tab2/tab2.page.html
  9. 123 71
      TFPower-app/src/app/tab2/tab2.page.scss
  10. 144 75
      TFPower-app/src/app/tab2/tab2.page.ts
  11. 7 9
      TFPower-app/src/app/tab2/test-page/test-page.component.html
  12. 1 0
      TFPower-app/src/app/tab2/test-page/test-page.component.scss
  13. 22 0
      TFPower-app/src/app/tab2/tianqi/tianqi.component.html
  14. 112 0
      TFPower-app/src/app/tab2/tianqi/tianqi.component.scss
  15. 22 0
      TFPower-app/src/app/tab2/tianqi/tianqi.component.spec.ts
  16. 74 0
      TFPower-app/src/app/tab2/tianqi/tianqi.component.ts
  17. BIN
      TFPower-app/src/assets/durian/duoyun.png
  18. BIN
      TFPower-app/src/assets/durian/lei.png
  19. BIN
      TFPower-app/src/assets/durian/qing.png
  20. BIN
      TFPower-app/src/assets/durian/shachen.png
  21. BIN
      TFPower-app/src/assets/durian/wu.png
  22. BIN
      TFPower-app/src/assets/durian/xue.png
  23. BIN
      TFPower-app/src/assets/durian/yin.png
  24. BIN
      TFPower-app/src/assets/durian/yu.png
  25. BIN
      TFPower-app/src/assets/durian/yujiaxue.png
  26. BIN
      TFPower-app/src/assets/durian/zhenyu.png
  27. BIN
      TFPower-app/src/assets/images/power.png
  28. 10 10
      TFPower-app/src/lib/ncloud.ts
  29. 11 0
      TFPower-app/src/proxy.config.json
  30. 135 1
      TFPower-server/lib/ncloud.js

+ 19 - 6
TFPower-app/angular.json

@@ -31,7 +31,10 @@
                 "output": "assets"
               }
             ],
-            "styles": ["src/global.scss", "src/theme/variables.scss"],
+            "styles": [
+              "src/global.scss",
+              "src/theme/variables.scss"
+            ],
             "scripts": []
           },
           "configurations": {
@@ -44,8 +47,8 @@
                 },
                 {
                   "type": "anyComponentStyle",
-                  "maximumWarning": "2kb",
-                  "maximumError": "4kb"
+                  "maximumWarning": "6kb",
+                  "maximumError": "7kb"
                 }
               ],
               "fileReplacements": [
@@ -72,6 +75,10 @@
         },
         "serve": {
           "builder": "@angular-devkit/build-angular:dev-server",
+          "options": {
+            "browserTarget": "app:build",
+            "proxyConfig": "./src/proxy.config.json"
+          },
           "configurations": {
             "production": {
               "buildTarget": "app:build:production"
@@ -106,7 +113,10 @@
                 "output": "assets"
               }
             ],
-            "styles": ["src/global.scss", "src/theme/variables.scss"],
+            "styles": [
+              "src/global.scss",
+              "src/theme/variables.scss"
+            ],
             "scripts": []
           },
           "configurations": {
@@ -119,7 +129,10 @@
         "lint": {
           "builder": "@angular-eslint/builder:lint",
           "options": {
-            "lintFilePatterns": ["src/**/*.ts", "src/**/*.html"]
+            "lintFilePatterns": [
+              "src/**/*.ts",
+              "src/**/*.html"
+            ]
           }
         }
       }
@@ -145,4 +158,4 @@
       "setParserOptionsProject": true
     }
   }
-}
+}

+ 8 - 0
TFPower-app/package-lock.json

@@ -41,6 +41,7 @@
         "@capacitor/cli": "6.2.0",
         "@ionic/angular-toolkit": "^11.0.1",
         "@types/jasmine": "~5.1.0",
+        "@types/pinyin": "^2.10.2",
         "@typescript-eslint/eslint-plugin": "^6.0.0",
         "@typescript-eslint/parser": "^6.0.0",
         "eslint": "^8.57.0",
@@ -6032,6 +6033,13 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/pinyin": {
+      "version": "2.10.2",
+      "resolved": "https://registry.npmmirror.com/@types/pinyin/-/pinyin-2.10.2.tgz",
+      "integrity": "sha512-jLzlRkaLRLg+lgYPjOuP3HX2cozUkhXls5GTXopsKuKJ9lDGlIAb88OoIztH6TbNUsoJnl/7e/kjaumA5IKKJg==",
+      "dev": true,
+      "license": "MIT"
+    },
     "node_modules/@types/qs": {
       "version": "6.9.17",
       "resolved": "https://registry.npmmirror.com/@types/qs/-/qs-6.9.17.tgz",

+ 1 - 0
TFPower-app/package.json

@@ -46,6 +46,7 @@
     "@capacitor/cli": "6.2.0",
     "@ionic/angular-toolkit": "^11.0.1",
     "@types/jasmine": "~5.1.0",
+    "@types/pinyin": "^2.10.2",
     "@typescript-eslint/eslint-plugin": "^6.0.0",
     "@typescript-eslint/parser": "^6.0.0",
     "eslint": "^8.57.0",

+ 54 - 0
TFPower-app/src/app/tab2/rili/rili.component.html

@@ -0,0 +1,54 @@
+<!-- <ion-header>
+  <ion-toolbar>
+    <ion-buttons slot="start">
+      <ion-button (click)="preMonth()">上个月</ion-button>
+    </ion-buttons>
+    <ion-title (click)="openPicker()">{{ curYear }}年 {{ curMonth }}月 ></ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="nextMonth()">下个月</ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header> -->
+<div>
+  <div style="display: flex;margin: auto;justify-content: space-between;">
+    <ion-buttons slot="start">
+      <ion-button (click)="preMonth()">上个月</ion-button>
+    </ion-buttons>
+    <div (click)="openPicker()" style="display: flex;align-items: center;justify-content: center;font-weight: bolder;">
+      {{ curYear }}年 {{ curMonth }}月 ></div>
+    <ion-buttons slot="end">
+      <ion-button (click)="nextMonth()">下个月</ion-button>
+    </ion-buttons>
+  </div>
+  <div>
+    <div class="week-days">
+      <div *ngFor="let day of week" class="week-day">{{ day }}</div>
+    </div>
+
+    <div class="days">
+      <!-- 上个月 -->
+      <div *ngFor="let item of firstDays" (click)="topre(item)" class="lastday">
+        {{ item }}
+      </div>
+
+      <!-- 当前月 -->
+      <div *ngFor="let day of curDays" class="curday" [ngClass]="{'selected': selectedDate === day }"
+        (click)="selectDate(day)" style="display: flex; flex-direction: column; justify-content: end;">
+
+        <div class="img" style="height: 60%;">
+          <!-- 根据日期查找天气类型 -->
+          <!-- {{ getWeatherType(curYear, curMonth, day) }} -->
+          <img *ngIf="checkedDates.includes(formatDate(curYear, curMonth, day))" style="height: 100%;"
+            src="https://s1.aigei.com/src/img/gif/74/745acea3ee024c4ca589ed8323755a1c.gif?imageMogr2/auto-orient/thumbnail/!282x282r/gravity/Center/crop/282x282/quality/85/%7CimageView2/2/w/282&e=1735488000&token=P7S2Xpzfz11vAkASLTkfHN7Fw-oOZBecqeJaxypL:mSthK2X-rdE9weDjbe6VF46ck7s="
+            alt="">
+          <img *ngIf="!checkedDates.includes(formatDate(curYear, curMonth, day))" style="height: 100%;"
+            [src]="getWeatherType(curYear, curMonth, day)" alt="">
+        </div>
+        <div style="height: 40%;">{{ day }}</div>
+      </div>
+
+      <!-- 下个月 -->
+      <div *ngFor="let item of nextDaysArray" (click)="tolast(item)" class="lastday">{{ item }}</div>
+    </div>
+  </div>
+</div>

+ 58 - 0
TFPower-app/src/app/tab2/rili/rili.component.scss

@@ -0,0 +1,58 @@
+.week-days {
+  display: flex;
+  justify-content: space-between;
+  padding: 10px 0;
+}
+
+.week-day {
+  flex: 1;
+  text-align: center;
+  font-weight: bold;
+}
+
+.days {
+  display: flex;
+  flex-wrap: wrap;
+  padding: 10px;
+}
+
+.curday, .lastday {
+  font-size: 12px;
+  width: calc(100% / 7); /* 每行7个 */
+  height: 43px; /* 每个日期的高度 */
+  width: 14.28%;
+  display: flex;
+  flex-direction: column; /* 垂直排列 */
+  align-items: center;
+  justify-content: flex-end; /* 向下对齐 */
+  cursor: pointer;
+  position: relative; /* 使 dot 可以绝对定位 */
+  border-radius: 50%;
+}
+
+.dot {
+  width: 20px; /* 圆点的直径 */
+  height: 20px; /* 圆点的直径 */
+  background-color: #009b7d; /* 圆点的颜色 */
+  border-radius: 50%; /* 圆形 */
+  top: 0; /* 在顶部 */
+  transform: translateY(-50%); /* 向上移动圆点,使其居中 */
+}
+
+.curday {
+  // background-color: #f0f0f0; 
+}
+
+.lastday {
+  // background-color: #e0e0e0; 
+  color: #ccc;
+}
+
+.selected {
+  border: 1px solid  #009b7d;; /* 选中日期的背景色 */
+  color: #009b7d; /* 选中日期的字体颜色 */
+}
+
+.daka{
+  background-color: #a4e17e;
+}

+ 22 - 0
TFPower-app/src/app/tab2/rili/rili.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { RiliComponent } from './rili.component';
+
+describe('RiliComponent', () => {
+  let component: RiliComponent;
+  let fixture: ComponentFixture<RiliComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [RiliComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(RiliComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 234 - 0
TFPower-app/src/app/tab2/rili/rili.component.ts

@@ -0,0 +1,234 @@
+import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
+import { IonContent, IonHeader, IonTitle, IonToolbar, IonButton, IonLabel, IonItem, IonList, IonBackButton, IonButtons, IonIcon, IonItemDivider, IonAvatar, IonThumbnail, IonItemOptions, IonItemOption, IonItemSliding, IonInput, IonCheckbox, IonRadio, IonToggle, IonRadioGroup, IonSearchbar, IonSegment, IonSegmentButton, IonDatetime, IonFooter, IonCardContent, IonCardTitle, IonCardHeader, IonCard, IonCol, IonRow, IonGrid, IonChip, IonImg, PickerController, IonPicker } from '@ionic/angular/standalone';
+import { CommonModule } from '@angular/common';
+import { HttpClient } from '@angular/common/http';
+import { pinyin } from 'pinyin-pro';
+import { CloudQuery, CloudUser } from 'src/lib/ncloud';
+
+@Component({
+  selector: 'app-rili',
+  templateUrl: './rili.component.html',
+  styleUrls: ['./rili.component.scss'],
+  standalone: true,
+  imports: [IonContent, IonHeader, IonTitle, IonToolbar, IonButton, IonInput, IonItem, IonList, IonIcon, IonContent, IonHeader, IonTitle, IonToolbar, IonButton, IonLabel, IonLabel, IonList, IonItem, IonBackButton, IonButtons, IonIcon, IonItemDivider, IonAvatar, IonThumbnail, IonItemOptions, IonItemSliding, IonItemOption, IonItemOptions, IonInput, IonCheckbox, IonRadio, IonToggle, IonRadioGroup, IonSearchbar, IonSegment, IonSegmentButton, IonDatetime, IonFooter, IonCardContent, IonCardTitle, IonCardHeader, IonCard, IonCol, IonRow, IonGrid, IonChip, IonImg, CommonModule, IonPicker]
+})
+export class RiliComponent implements OnInit {
+  curYear: number;
+  curMonth: number;
+  week: string[] = ['一', '二', '三', '四', '五', '六', '日'];
+  firstDays: number[] = [];
+  curDays: number[] = [];
+  nextDaysArray: number[] = [];
+  selectedDate: number | null = null;
+
+  pickerColumns: any[] = [];
+  pickerButtons: any[] = [];
+  checkedDates: string[] = [
+
+  ];
+  selectTime: string = ""//选中元素的时间按照“2024-12-20”格式
+
+  async loadDates() {
+    let currentUser = new CloudUser();
+    const cloudQuery = new CloudQuery("fitUser");
+    cloudQuery.equalTo("user", currentUser.toPointer());
+    const userData = await cloudQuery.find();
+    console.log("userData::", userData[0].data.checkeddays);
+    this.checkedDates = userData[0].data.checkeddays
+
+  }
+
+  @Input() value2: string = ""; // 接收父组件传递的值
+  @Output() ionChange2 = new EventEmitter<string>(); // 输出事件
+  // async fetchWeatherData() {
+  //   try {
+  //     this.weatherData = await this.weatherService.getWeatherData(); // 提取 data 字段
+  //     console.log('天气数据:', this.weatherData); // 打印天气数据
+  //   } catch (error) {
+  //     console.error('获取天气数据失败:', error);
+  //     alert('无法获取天气数据,请检查网络连接或API状态。');
+  //   }
+  // }
+  weather: any = [
+  ];
+  fetchWeatherData() {
+    // 第一个请求,获取 adcode
+    const apiUrl = '/api/api/weather/city/101240101';
+    this.http.get(apiUrl)
+      .subscribe((response: any) => {
+        const adcode = response; // 获取 adcode 字段
+        if (adcode) {
+          this.weather = adcode.data.forecast
+          console.log(this.weather);
+          console.log("adcode", adcode);
+
+
+          // 使用 adcode 获取天气信息
+        } else {
+          console.error('adcode not found in response:', response);
+        }
+      }, error => {
+        console.error('Error fetching adcode:', error);
+      });
+  }
+
+  //转化成拼音
+  convertToPinyin(text: string): string {
+    const result = pinyin(text, {
+      toneType: 'none', // 不带声调
+      type: 'array', // 返回拼音数组
+    });
+    return result.join(''); // 将拼音数组转换为字符串
+  }
+  getWeatherType(year: number, month: number, day: number): string {
+    // 格式化日期为 "YYYY-MM-DD"
+    const dateStr = `${year}-${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}`;
+
+    // 查找对应的天气数据
+    const weatherData = this.weather.find((item: { ymd: string; }) => item.ymd === dateStr);
+
+    // 如果找到天气数据,返回类型,否则返回空字符串
+    return weatherData ? "assets/durian/" + this.convertToPinyin(weatherData.type) + ".png" : '';
+  }
+
+
+
+  constructor(private pickerCtrl: PickerController, private http: HttpClient) {
+    const today = new Date();
+    this.curYear = today.getFullYear();
+    this.curMonth = today.getMonth() + 1; // 当前月,0-11
+    this.selectedDate = today.getDate(); // 选中当天的日期
+    this.updateCalendar();
+    this.initializePicker();
+    this.loadDates()
+  }
+
+  ngOnInit() {
+    this.fetchWeatherData();
+  }
+  daka() {
+    //打卡,点击之后,被选中的日期元素背景色被标为绿色
+    if (this.checkedDates.includes(this.selectTime)) {
+      console.log("之前就有记录");
+      return
+
+    }
+    this.checkedDates.push(this.selectTime)
+    console.log(this.checkedDates);
+
+  }
+  initializePicker() {
+    this.pickerColumns = [
+      {
+        name: 'year',
+        options: this.createYearOptions(),
+      },
+      {
+        name: 'month',
+        options: this.createMonthOptions(),
+      },
+    ];
+
+    this.pickerButtons = [
+      {
+        text: '取消',
+        role: 'cancel',
+      },
+      {
+        text: '确定',
+        handler: (value: { year: { value: number; }; month: { value: number; }; }) => {
+          this.curYear = value.year.value;
+          this.curMonth = value.month.value;
+          this.updateCalendar();
+        },
+      },
+    ];
+  }
+
+  updateCalendar() {
+    const firstDay = this.getFirstDayOfMonth(this.curYear, this.curMonth);
+    const daysInMonth = this.getDaysInMonth(this.curYear, this.curMonth);
+
+    this.firstDays = this.createArray(firstDay === 0 ? 6 : firstDay - 1);
+    this.curDays = this.createArray(daysInMonth);
+    this.nextDaysArray = this.createArray(7 - this.getLastDayOfMonth(this.curYear, this.curMonth));
+  }
+
+  preMonth() {
+    this.curMonth--;
+    if (this.curMonth < 1) {
+      this.curYear--;
+      this.curMonth = 12;
+    }
+    this.updateCalendar();
+  }
+
+  nextMonth() {
+    this.curMonth++;
+    if (this.curMonth > 12) {
+      this.curYear++;
+      this.curMonth = 1;
+    }
+    this.updateCalendar();
+  }
+  formatDate(year: number, month: number, day: number): string {
+    return `${year}-${month < 10 ? '0' + month : month}-${day < 10 ? '0' + day : day}`;
+  }
+  selectDate(day: number) {
+    this.selectedDate = day; // 选中日期
+    this.selectTime = `${this.curYear}-${this.curMonth < 10 ? '0' + this.curMonth : this.curMonth}-${day < 10 ? '0' + day : day}`; // 格式化时间
+    console.log("组件运作", this.selectTime);
+    this.ionChange2.emit(this.selectTime);
+  }
+
+  tolast(item: number) {
+    this.nextMonth();
+    this.selectedDate = item; // 选中日期
+  }
+
+  topre(item: number) {
+    this.preMonth();
+    this.selectedDate = item; // 选中日期
+  }
+
+  async openPicker() {
+    const picker = await this.pickerCtrl.create({
+      columns: this.pickerColumns,
+      buttons: this.pickerButtons,
+    });
+    await picker.present();
+  }
+
+  createArray(n: number): number[] {
+    return Array.from({ length: n }, (_, index) => index + 1); // 从1开始
+  }
+
+  getDaysInMonth(year: number, month: number): number {
+    return new Date(year, month, 0).getDate();
+  }
+
+  getFirstDayOfMonth(year: number, month: number): number {
+    return new Date(year, month - 1, 1).getDay(); // 注意:月份是0-11
+  }
+
+  getLastDayOfMonth(year: number, month: number): number {
+    return new Date(year, month, 0).getDay(); // 获取当月最后一天是星期几
+  }
+
+  createYearOptions() {
+    const currentYear = new Date().getFullYear();
+    const years = [];
+    for (let i = currentYear - 10; i <= currentYear + 10; i++) {
+      years.push({ text: `${i}`, value: i });
+    }
+    return years;
+  }
+
+  createMonthOptions() {
+    const months = [];
+    for (let i = 1; i <= 12; i++) {
+      months.push({ text: `${i}`, value: i });
+    }
+    return months;
+  }
+}

+ 34 - 44
TFPower-app/src/app/tab2/tab2.page.html

@@ -49,22 +49,28 @@
             </div>
           </ion-card-content>
         </ion-card>
-
+        <app-tianqi></app-tianqi>
         <ion-card>
           <ion-card-header>
+            <div class="inline-container">
+              <ion-icon name="alert-circle-outline" style="margin-right: 2px;"></ion-icon>
+              <ion-label class="link" (click)="chakan()">点击查看动能细则</ion-label>
+            </div>
             <ion-card-title>打卡区域</ion-card-title>
             <div class="power-label">
               <strong>我的动能</strong>
-              <p class="stat-value">{{ user.get('power') }}</p>
+              <div class="power-content">
+                <img src="assets/images/power.png" alt="Power Icon">
+                <p class="stat-value">{{ user.get('power') }}</p>
+              </div>
             </div>
           </ion-card-header>
           <ion-card-content>
-            <ion-datetime [value]="realDate.toISOString()" (ionChange)="onDateChange($event)">
-            </ion-datetime>
+            <app-rili (ionChange2)="onDateChange2($event)"></app-rili>
             <div class="card-info">
               <ion-button [disabled]="getButtonState(realDate).isDisabled"
                 (click)="getButtonState(realDate).buttonText === '补签' ? handleMakeupClick() : markAttendance()"
-                class="check">
+                class="check pulse-button">
                 {{ getButtonState(realDate).buttonText }}
               </ion-button>
             </div>
@@ -95,33 +101,17 @@
     </ng-template>
 
 
-    <ion-card>
-      <!-- 未登录 -->
-      @if(!currentUser?.id){
-      <ion-card-header>
-        <ion-card-title>请登录</ion-card-title>
-        <ion-card-subtitle>暂无信息</ion-card-subtitle>
-      </ion-card-header>
-      }
-      <!-- 未登录 -->
-      @if(currentUser?.id){
-      <ion-card-header>
-        <ion-card-title>{{currentUser?.get("username")}} {{currentUser?.get("realname")}}</ion-card-title>
-        <ion-card-subtitle>性别:{{currentUser?.get("gender")||"-"}} 年龄:{{currentUser?.get("age")||"-"}}
-        </ion-card-subtitle>
-      </ion-card-header>
-      }
-      <ion-card-content>
-        @if(!currentUser?.id){
-        <ion-button expand="block" (click)="signup()">注册</ion-button>
-        <ion-button expand="block" (click)="login()">登录</ion-button>
-        }
-        @if(currentUser?.id){
-        <ion-button expand="block" (click)="editUser()">编辑资料</ion-button>
-        <ion-button expand="block" (click)="logout()" color="light">登出</ion-button>
-        }
-      </ion-card-content>
-    </ion-card>
+
+    <!-- 未登录 -->
+    <div *ngIf="!currentUser?.id" class="button-group">
+      <ion-label class="link" (click)="signup()">注册</ion-label>
+      <ion-label class="link" (click)="login()">登录</ion-label>
+    </div>
+
+    <!-- 已登录 -->
+    <div *ngIf="currentUser?.id" class="button-group">
+      <ion-label class="link" (click)="logout()">登出</ion-label>
+    </div>
 
   </div>
 
@@ -145,15 +135,15 @@
               <ion-col size="2.5" class="grid-header">项目3</ion-col>
               <ion-col size="2.5" class="grid-header">项目4</ion-col>
             </ion-row>
-
-            <!-- 绑定计划数据 -->
+          </ion-grid>
+          <!-- 绑定计划数据 -->
+          <ion-list>
             <ion-row *ngFor="let day of planList">
               <ion-item-sliding>
                 <ion-item>
                   <!-- 显示计划内容 -->
                   <ion-col size="1" class="plan-column">{{ day.get('date') }}</ion-col>
                   <ion-col size="1" class="plan-column">{{ day.get('trainingPart') }}</ion-col>
-                  <!-- 显示每个训练项目,确保即使为空也占位 -->
                   <ion-col size="2.5" *ngFor="let task of day.get('trainingItems'); let i = index" class="plan-column">
                     <div class="task-container">
                       <span class="task-item">{{ task.item || '' }}</span>
@@ -165,12 +155,14 @@
                 <ion-item-options side="end">
                   <ion-item-option class="edit-btn" color="primary" shape="round" (click)="editPlan(day)">编辑
                   </ion-item-option>
-                  <ion-item-option class="delete-btn" color="danger" (click)="deletePlan(day)">删除</ion-item-option>
+                  <ion-item-option class="delete-btn" color="danger" shape="round" (click)="deletePlan(day)">删除
+                  </ion-item-option>
                 </ion-item-options>
-
               </ion-item-sliding>
             </ion-row>
-          </ion-grid>
+          </ion-list>
+
+
         </div>
       </ion-card>
       <ion-card-subtitle>
@@ -185,7 +177,7 @@
   <div *ngIf="selectedTab === 'consultation'" class="consult">
     <ion-card-content>
       <div
-        style="width: 95%; margin: auto; height: 110px; display: flex; justify-content: space-between; background-color: #ffffff; border: 1px solid #e7e7db; border-radius: 20px; overflow: hidden;"
+        style="width: 95%; margin: auto; height: 100px; display: flex; justify-content: space-between; background-color: #ffffff; border: 1px solid #e7e7db; border-radius: 20px; overflow: hidden;"
         (click)="doPoemTask()">
         <!-- 左侧内容部分 -->
         <div
@@ -206,8 +198,7 @@
         </div>
         <!-- 右侧图片部分 -->
         <div>
-          <img src="../../assets/images/action5.png" style="height: 100px; display: flex; justify-content: flex-end;"
-            alt="">
+          <img src="assets/images/action5.png" style="height: 100px; display: flex; justify-content: flex-end;" alt="">
         </div>
       </div>
 
@@ -274,7 +265,7 @@
         </ion-card-content>
       </ion-card>
       <div
-        style="width: 95%; margin: auto; height: 110px; display: flex; justify-content: space-between; background-color: #ffffff; border: 1px solid #e7e7db; border-radius: 20px; overflow: hidden;"
+        style="width: 95%; margin: auto; height: 100px; display: flex; justify-content: space-between; background-color: #ffffff; border: 1px solid #e7e7db; border-radius: 20px; overflow: hidden;"
         (click)="doInqueryTask()">
 
         <!-- 左侧内容部分 -->
@@ -300,8 +291,7 @@
 
         <!-- 右侧图片部分 -->
         <div>
-          <img src="../../assets/images/battle1.png" style="height: 100px; display: flex; justify-content: flex-end;"
-            alt="">
+          <img src="assets/images/battle1.png" style="height: 100px; display: flex; justify-content: flex-end;" alt="">
         </div>
       </div>
       <ion-card *ngIf="healthTaskVisible">

+ 123 - 71
TFPower-app/src/app/tab2/tab2.page.scss

@@ -35,14 +35,13 @@ ion-segment-button.md::part(indicator-background) {
 }
 
 ion-content {
-  padding-top: 56px; /* 给内容区域加上与header相等的顶部间距 */
+  padding-top: 56px; 
 }
 ion-content {
   background-color: #f9f9f9;
   padding-top: 56px;
 }
 // plan部分css
-/* 给表格增加阴影和圆角 */
 //表头
 .grid-header {
   font-weight: bold;
@@ -50,11 +49,13 @@ ion-content {
   color: rgb(7, 6, 6);
   text-align: center;
    display: flex;
+   
     justify-content: center;
     align-items: center;
 }
 .plan-table .table {
-  --ion-grid-column-padding: 0px;  /* 去除额外的内边距,避免错位 */
+  --ion-grid-column-padding: 0px; 
+  font-size: 20px;
 }
 
 .ion-item {
@@ -77,11 +78,9 @@ ion-content {
 /* 表格行的底部间距 */
 .plan-table ion-col {
   margin-bottom: 1px;
- 
-    display: flex;
-    justify-content: center;
-    align-items: center;
-  
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
 /* 表格的列 */
@@ -89,14 +88,29 @@ ion-content {
   background-color: #ffffff;
   padding: 12px;
   text-align: center;
-   display: flex;
-    justify-content: center;
-    align-items: center;
+  display: flex;
+  justify-content: center;
+  align-items: center;
 }
 
 /* 表格的标题 */
 
+.edit-btn {
+  font-size: 14px;
+  padding: 5px 10px;
+  background-color: #009b7d;
+  color: white;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
+}
 
+/* 删除按钮添加阴影和背景渐变 */
+.delete-btn {
+  font-size: 14px;
+  padding: 5px 10px;
+  background: linear-gradient(to right, #ff6347, #f44336); /* 渐变背景色 */
+  color: white;
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+}
 .section-title {
   font-size: 20px;
   font-weight: bold;
@@ -130,61 +144,40 @@ ion-content {
   }
 }
 /* 问诊部分的设计 */
-/* 确保图片等大 */
-.consult .ion-text-center ion-img {
-  width: 100%; /* 设置宽度为 100% */
-  height: 200px; /* 设置固定高度,确保两张图片等高 */
-  object-fit: cover; /* 保持图片比例,同时裁剪图片以适应区域 */
-}
-.consult ion-item {
-  margin-bottom: 12px; /* 增加医生卡片之间的间距 */
-  padding: 12px; /* 调整卡片内边距 */
-  padding-right: 4px;
-  border-radius: 8px; /* 圆角设计 */
-  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); /* 添加卡片阴影 */
-  background-color: #ffffff; /* 卡片背景色 */
-}
-.consult .thumbnail {
-  width: 160px;  /* 可以根据需要设置尺寸 */
-  height: 160px;  /* 高度和宽度一致使图片为正方形 */
-  margin-bottom: 10px;  /* 图片和按钮之间的间距 */
-}
-.consult ion-thumbnail {
-  width: 60px; /* 缩略图宽度 */
-  height: 60px; /* 缩略图高度 */
-}
 .consult .lan {
   --background: #009b7d;
   --background-hover: #9ce0be;
   --background-activated: #88f4be;
   --background-focused: #88f4be;
-
   --color: #ffffff;
-
   --border-radius: 10px;
   --border-color: #28a169;
   --border-style: solid;
   --border-width: 1px;
-
   --box-shadow: 0 2px 6px 0 rgb(0, 0, 0, 0.25);
-
   --ripple-color: deeppink;
   --padding-left: 10px;
   --padding-right: 10px;
   --padding-top: 3px;
   --padding-bottom: 3px;
+}
 
-  /* 例如给 .lan 这个类应用背景色 */
-  // background-color: var(--background);
-  // color: var(--color);
-  // border-radius: var(--border-radius);
-  // box-shadow: var(--box-shadow);
-  // padding: var(--padding-top) var(--padding-right) var(--padding-bottom) var(--padding-left);
-  // border: var(--border-width) var(--border-style) var(--border-color);
+.consult ion-item {
+  margin-bottom: 12px; 
+  padding: 12px; 
+  padding-right: 4px;
+  border-radius: 8px; 
+  box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); 
+  background-color: #ffffff; 
+}
+
+.consult ion-thumbnail {
+  width: 17%;
+  height:  17%; 
 }
 
 .consult .coach-info {
-  margin-left: 12px; /* 缩略图与文字的间距 */
+  margin-left: 12px;
   display: flex;
   flex-direction: column;
   justify-content: center;
@@ -204,7 +197,7 @@ ion-content {
 .consult .coach-info p {
   margin: 2px 0;
   font-size: 12px;
-  color: #666; /* 字体颜色 */
+  color: #666; 
 }
 .consult ion-button {
   --background: rgb(244, 245, 244);
@@ -214,7 +207,7 @@ ion-content {
   --border-style: solid;
   --border-width: 1px;
   --box-shadow: 0 2px 6px 0 rgb(0, 0, 0, 0.25);
-  --ripple-color: deeppink;
+  --ripple-color: rgb(206, 178, 193);
   --padding-top: 10px;
   --padding-bottom: 10px;
 }
@@ -231,20 +224,20 @@ ion-content {
  
 }
 .plan-column {
-  transition: background-color 0.3s ease, box-shadow 0.3s ease;  /* 平滑过渡效果 */
+  transition: background-color 0.3s ease, box-shadow 0.3s ease;
 }
 
 .task-container {
-  display: flex; /* 设置为 flexbox */
-  flex-direction: column; /* 让子元素垂直排列 */
-  align-items: center; /* 水平居中 */
-  justify-content: center; /* 垂直居中 */
-  text-align: center; /* 确保文本在水平上居中 */
+  display: flex; 
+  flex-direction: column; 
+  align-items: center; 
+  justify-content: center; 
+  text-align: center; 
 }
 
 .task-item {
   font-weight: bold;
-  margin-bottom: 5px; /* 为了分隔上下两个span */
+  margin-bottom: 5px;
 }
 /* 打卡部分的设计 */
 .checkin p {
@@ -265,7 +258,7 @@ ion-content {
 }
 
 .checkin .avatar-container {
-  margin-right:5px; /* 头像和信息之间的间距 */
+  margin-right:5px; 
 }
 
 .checkin .avatar {
@@ -275,7 +268,7 @@ ion-content {
 }
 
 .checkin .user-info {
-  flex: 1; /* 使信息部分占满剩余空间 */
+  flex: 1; 
 }
 
 
@@ -287,14 +280,18 @@ ion-content {
 /* 卡片内信息区域 */
 .card-info {
   display: flex;
-  flex-direction: column; /* 垂直排列,按钮在上,统计信息在下 */
-  align-items: center;  /* 横向居中 */
-  justify-content: center; /* 纵向居中 */
-  gap: 20px;  /* 设置按钮和统计信息的间隔 */
+  flex-direction: column;
+  align-items: center;
+  justify-content: center; 
+  gap: 20px;
+}
+.inline-container {
+  display: flex;
 }
 
+
 /* 圆形打卡按钮 */
-.checkin ion-button {
+.checkin .check {
 
   --background: #009b7d;
   --color: white;
@@ -303,19 +300,42 @@ ion-content {
   --border-width: 1px;
   --box-shadow: 0 2px 6px 0 rgb(0, 0, 0, 0.25);
   --ripple-color: deeppink;
-   width: 100px; 
+  width: 100px; 
   height: 100px; 
   font-size: 18px;
   --border-radius: 50%; 
   display: flex;
   align-items: center;
   justify-content: center;
-   --padding-start: 16px;
+  --padding-start: 16px;
   --padding-end: 16px;
   --padding-top: 12px;
   --padding-bottom: 12px;
 }
+//按钮脉冲效果
+.pulse-button {
+  animation: pulse-animation 1.5s infinite;
+  border-radius: 50%; 
+}
 
+@keyframes pulse-animation {
+0% {
+    transform: scale(1);
+    opacity: 1;
+    box-shadow: 0 1px 2px rgba(0, 147, 223, 0.4), 0 1px 1px rgba(0, 147, 223, 0.1) inset;
+  }
+  50% {
+    transform: scale(1.1);
+    opacity: 0.7;
+    box-shadow: 0 1px 10px rgba(0, 147, 223, 0.6), 0 1px 5px rgba(0, 147, 223, 0.2) inset;
+  }
+  100% {
+    transform: scale(1);
+    opacity: 1;
+    border: 1px solid rgba(59, 235, 235, 0.7);
+    box-shadow: 0 1px 30px #0093df, 0 1px 20px #0093df inset;
+  }
+}
 
 .circle-btn:disabled {
   background-color: #dcdcdc;
@@ -336,25 +356,57 @@ ion-content {
 }
 
 /* 统计值 */
-ion-card .power-label {
+.power-label {
   position: absolute;
   top: 5px;
   right: 30px;
-  font-size: 14px !important; /* 使用 !important 强制应用样式 */
+  font-size: 14px !important;
+  display: flex;
+  flex-direction: column;
+  align-items: flex-end;
+}
+
+.power-label strong {
+  font-weight: bold;
+}
+
+.power-content {
+  display: flex;
+  align-items: center; /* 确保图片和文本垂直居中对齐 */
+  gap: 4px; /* 控制图标和数字之间的间距 */
+}
+
+.power-content img {
+  height: 20px; /* 调整图片大小 */
+  width: 20px; /* 调整图片大小 */
 }
 
 .checkin .stat-value {
-  font-size: 24px !important;  /* 使用 !important 强制应用样式 */
+  margin: 0;
+  font-size: 24px !important; 
   color: #000000;
 }
+
+
 ion-card {
-    border-radius: 15px; /* 圆角边框 */
-    background-color: #f9f9f9; /* 卡片内部背景颜色 */
-    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); /* 添加阴影效果 */
-    border: 1px solid #e0e0e0; /* 边框颜色 */
+    border-radius: 15px; 
+    background-color: #f9f9f9; 
+    box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); 
+    border: 1px solid #e0e0e0; 
   }
 
+.button-group {
+  display: flex;
+  gap: 5px; /* 按钮之间的间距 */
+  justify-content: left; /* 水平居中 */
+}
 
+.button-group ion-button {
+  width: 5%; /* 按钮宽度调整为小巧 */
+  height: 5%;
+  --padding-start: 10px;
+  --padding-end: 10px;
+}
 
 
 

+ 144 - 75
TFPower-app/src/app/tab2/tab2.page.ts

@@ -19,6 +19,9 @@ 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';
+import { TianqiComponent } from './tianqi/tianqi.component';
+import { RiliComponent } from './rili/rili.component';
+
 @Component({
   selector: 'app-tab2',
   templateUrl: 'tab2.page.html',
@@ -57,6 +60,8 @@ import { TestPageComponent } from './test-page/test-page.component';
     IonItemOption,
     IonItemSliding,
     IonItemOptions,
+    TianqiComponent,
+    RiliComponent
   ]
 })
 export class Tab2Page implements OnInit {
@@ -76,7 +81,6 @@ export class Tab2Page implements OnInit {
     this.loadPlanList()
     this.loadCoachList()
     this.loadPlanUser()
-
   }
   async loadPlanList() {
     let currentUser = new CloudUser();
@@ -84,7 +88,7 @@ export class Tab2Page implements OnInit {
     if (currentUser) {
       cloudQuery.equalTo("user", currentUser.toPointer());
       this.planList = await cloudQuery.find();
-      console.log(this.planList)
+      console.log("计划为:", this.planList)
       //排序算法
       this.planList.sort((a, b) => {
         const srcIdA = a.get("srcId").match(/\d+/);
@@ -121,7 +125,10 @@ export class Tab2Page implements OnInit {
     });
     toast.present();
   }
-
+  onDateChange2(event: any) {
+    console.log(event);
+    this.realDate = new Date(event);
+  }
   // 计算 BMI
   calculateBMI(height: number, weight: number): number {
     const heightInMeters = height / 100;
@@ -138,7 +145,7 @@ export class Tab2Page implements OnInit {
     } else if (bmi >= 25 && bmi < 29.9) {
       return '您的BMI稍微偏高,可以增加运动,控制饮食!(≧◡≦)';
     } else {
-      return '您的BMI较高,建议积极锻炼,控制体重!(。•́︿•̀。)';
+      return '您的BMI太高了,建议积极锻炼,控制体重!(。•́︿•̀。)';
     }
   }
 
@@ -154,22 +161,18 @@ export class Tab2Page implements OnInit {
     // 使用 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
+        this.power = user.get("power") || 0;
       } else {
         console.warn('No user data found');
       }
@@ -195,6 +198,44 @@ export class Tab2Page implements OnInit {
     }
     this.consecutiveDays = count;
   }
+  //查看动能细则
+  async chakan() {
+    // 动能细则的内容
+    const powerDetails = `
+   
+      普通打卡: 每天获得 5 动能。
+      连续打卡第七天: 额外获得 10 动能。
+      连续打卡第十五天: 额外获得 20 动能。
+      连续打卡第三十天: 额外获得 30 动能。
+      补签:补签之后,连续打卡天数会清零。
+      动能后续能够在商城兑换联名奖品哦!
+  `;
+
+    // 创建 alert 对话框
+    const alert = await this.alertController.create({
+      header: '动能细则',
+      message: powerDetails,  // 显示动能细则内容
+      buttons: [
+        {
+          text: '取消',
+          role: 'cancel',
+          cssClass: 'secondary',
+          handler: () => {
+            console.log('操作被取消');
+          }
+        },
+        {
+          text: '确认',
+          handler: () => {
+            console.log('动能细则已确认');
+          }
+        }
+      ]
+    });
+
+    // 显示对话框
+    await alert.present();
+  }
 
   // 格式化日期
   formatDate(date: Date): string {
@@ -235,29 +276,21 @@ export class Tab2Page implements OnInit {
   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") || [];
@@ -265,22 +298,33 @@ export class Tab2Page implements OnInit {
       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 奖励
-
+      let totalReward = this.dailyReward;
+      totalReward += this.getConsecutiveReward(this.consecutiveDays);
+      user.set({ "power": user.get("power") + totalReward });
       await user.save();
       this.loadPlanUser();
-      this.showToast('打卡成功,获得了 ' + totalReward + ' Power');
+      this.showToast('打卡成功,获得了 ' + totalReward + ' 动能');
     }
   }
-
-  // 补签操作
+  //补签操作
   async handleMakeupSignIn(user: CloudUser): Promise<string> {
     if (user.get("power") >= this.powerForMakup) {
+      const currentDate = this.realDate;
+      const formattedDate = this.formatDate(currentDate);
+      // 将补签日期添加到已打卡的日期集合
+      if (!this.checkInHistory.has(formattedDate)) {
+        this.checkInHistory.add(formattedDate);
+        this.days = this.checkInHistory.size;
+        this.calculateConsecutiveDays();
+      }
       user.set({ "power": user.get("power") - this.powerForMakup });
+      let checkedDays = user.get("checkeddays") || [];
+      if (!checkedDays.includes(formattedDate)) {
+        checkedDays.push(formattedDate); // 添加补签日期
+        user.set({ "checkeddays": checkedDays });
+      }
+      user.set({ "days": this.days });
+      user.set({ "sucdays": 1 });
       await user.save();
       return '补签成功!';
     } else {
@@ -288,9 +332,9 @@ export class Tab2Page implements OnInit {
     }
   }
 
-  // 补签点击事件
+  // 补签点击提示事件
   async handleMakeupClick() {
-    const confirmed = window.confirm('补签将消耗 ' + this.powerForMakup + ' 动能,确定补签吗?');
+    const confirmed = window.confirm('补签将消耗 ' + this.powerForMakup + ' 动能,且连续打卡天数清零!确定补签吗?');
     if (confirmed) {
       let currentUser = new CloudUser();
       const cloudQuery = new CloudQuery("fitUser");
@@ -333,6 +377,10 @@ export class Tab2Page implements OnInit {
     if (user?.id) {
       this.currentUser = user
     }
+    window.location.reload();  // 登录后刷新页面
+    // this.loadPlanList()
+    // this.loadCoachList()
+    // this.loadPlanUser()
   }
   async signup() {
     // 弹出注册窗口
@@ -340,11 +388,15 @@ export class Tab2Page implements OnInit {
     if (user?.id) {
       this.currentUser = user
     }
-  }
-  logout() {
-    this.currentUser?.logout();
 
   }
+  async logout() {
+    await this.currentUser?.logout();
+    window.location.reload();  // 登录后刷新页面
+    // this.loadPlanList()
+    // this.loadCoachList()
+    // this.loadPlanUser()
+  }
 
   editUser() {
     openUserEditModal(this.modalCtrl)
@@ -372,26 +424,27 @@ export class Tab2Page implements OnInit {
       });
     });
   }
-  regeneratePlan() {
-    console.log('重新生成计划:');
-
-    // 创建一个弹出框
-    this.modalCtrl.create({
-      component: TestPageComponent,
-      componentProps: {}
-    }).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;
+  async regeneratePlan() {
+    if (await this.checkout()) {
+      console.log('重新生成计划:');
+      // 创建一个弹出框
+      this.modalCtrl.create({
+        component: TestPageComponent,
+        componentProps: {}
+      }).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()
+          this.loadPlanList()
+        });
       });
-    });
+    }
   }
   async deletePlan(day: any) {
     const alert = await this.alertController.create({
@@ -424,6 +477,7 @@ export class Tab2Page implements OnInit {
 
     await alert.present();
   }
+
   //任务链
   actionTaskList: AgentTaskStep[] = []
   healthTaskList: AgentTaskStep[] = []
@@ -437,29 +491,47 @@ 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.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 });
-    // 定义任务集
-    let InquireServiceTaskList = [
-      task1, task2, task3
-    ]
-    // 传递给显示组件
-    this.healthTaskList = InquireServiceTaskList
-    // 开始执行任务
-    startTask(InquireServiceTaskList)
+  async doPoemTask() {
+    if (await this.checkout()) {
+      await this.checkout()
+      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.actionTaskList = PoemTaskList
+      startTask(PoemTaskList)
+    }
+  }
+  async doInqueryTask() {
+
+    if (await this.checkout()) {
+      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 });
+      // 定义任务集
+      let InquireServiceTaskList = [
+        task1, task2, task3
+      ]
+      // 传递给显示组件
+      this.healthTaskList = InquireServiceTaskList
+      // 开始执行任务
+      startTask(InquireServiceTaskList)
+    }
   }
   // 聊天页面
+  async checkout() {
+    let currentUser = new CloudUser();
+    if (!currentUser?.id) {
+      console.log("用户未登录,请登录后重试");
+      let user = await openUserLoginModal(this.modalCtrl);
+      if (!user?.id) {
+        return false;  // 用户未登录且登录失败,返回 false
+      }
+      currentUser = user;
+    }
+    return true; // 用户已登录,返回 true
+  }
   async openInquiry(coach: CloudObject) {
     let currentUser = new CloudUser();
     let userPrompt = ``
@@ -492,9 +564,7 @@ export class Tab2Page implements OnInit {
     let ACL: any = {
       "*": { read: false, write: false }
     }
-    if (currentUser?.id) {
-      ACL[currentUser?.id] = { read: true, write: true }
-    }
+
     consult.set({
       title: `交流记录${dateStr}-${coach?.get("name")}`,
       coach: coach.toPointer(),
@@ -518,7 +588,6 @@ export class Tab2Page implements OnInit {
 # 对话环节
 0.导诊(根据用户基本情况,引导选择合适的训练计划) 
 1.预设的问询方式(根据学员自述情况进行引导)
-
 - 打招呼,以学员自述为主
 - 当信息充足时,确认学员的目标与需求,并进入下一个环节 
 2.拓展的问询细节 
@@ -530,7 +599,7 @@ export class Tab2Page implements OnInit {
 - 完成训练计划时,请在消息结尾附带: [交流完成]
 # 开始话语
 当您准备好了,可以以一个健身教练的身份,向来访的学员打招呼。
-你好!欢迎来到健身房,我是${coach?.get("name")}教练,${coach?.get("desc")}。今天你想要专注锻炼哪个部位呢?或者有什么具体的健身目标吗?`);
+你好!欢迎来到WiseFitness健身房,我是${coach?.get("name")}教练,在${coach?.get("specialize")}方面颇有造诣。今天你想询问关于这个方面的哪些问题呢?`);
       },
       onMessage: (chat: FmodeChat, message: FmodeChatMessage) => {
         console.log("onMessage", message)

+ 7 - 9
TFPower-app/src/app/tab2/test-page/test-page.component.html

@@ -86,15 +86,14 @@
     </div>
 
     <!-- 生成计划按钮 -->
-    <ion-button expand="full" shape="round" (click)="generatePlan()" [disabled]="isGenerating"
-      class="generate-plan-btn">
+    <ion-button expand="full" shape="round" (click)="generatePlan()" [disabled]="isRevert" class="generate-plan-btn">
       <ion-icon slot="start" style="margin-right: 5px;" name="fitness"></ion-icon>
       生成健身计划
     </ion-button>
 
     <!-- 显示健身计划结果 -->
     <div *ngIf="generatedPlan" class="result-container">
-      <ion-progress-bar *ngIf="isRevert" type="indeterminate" color="secondary"></ion-progress-bar>
+      <ion-progress-bar *ngIf="isRevert" type="indeterminate" color="success"></ion-progress-bar>
       <h3>您的健身计划:</h3>
       <ion-card>
         <ion-card-header>
@@ -110,16 +109,15 @@
               <ion-row>
                 <!-- 左侧按钮 -->
                 <ion-col size="6">
-                  <ion-button *ngIf="planReady" expand="full" (click)="confirmPlan()" shape="round" class="confirm-btn">
-                    <ion-icon name="checkmark-circle-outline"></ion-icon>确认计划
+                  <ion-button *ngIf="planReady" expand="full" color="light" shape="round" (click)="discardPlan()"
+                    class="discard-btn">
+                    <ion-icon name="close-circle-outline"></ion-icon>放弃计划
                   </ion-button>
                 </ion-col>
-
                 <!-- 右侧按钮 -->
                 <ion-col size="6">
-                  <ion-button *ngIf="planReady" expand="full" color="light" shape="round" (click)="discardPlan()"
-                    class="discard-btn">
-                    <ion-icon name="close-circle-outline"></ion-icon>放弃计划
+                  <ion-button *ngIf="planReady" expand="full" (click)="confirmPlan()" shape="round" class="confirm-btn">
+                    <ion-icon name="checkmark-circle-outline"></ion-icon>确认计划
                   </ion-button>
                 </ion-col>
               </ion-row>

+ 1 - 0
TFPower-app/src/app/tab2/test-page/test-page.component.scss

@@ -17,6 +17,7 @@ h3
   border-radius: 8px;
   box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
 }
+
 ion-grid {
   margin-bottom: 8px;
 }

+ 22 - 0
TFPower-app/src/app/tab2/tianqi/tianqi.component.html

@@ -0,0 +1,22 @@
+<div class="box1" style="position: relative;width: 95%;margin: auto;">
+  <div class="box2">
+    <div>
+      <span id="crtp" class="span1" style="display: block;">{{weather?.data?.forecast[0].type}}</span>
+      <span id="interval_tp" style="display: block;" class="span2">{{juti}}</span>
+      <span id="interval_tp" style="display: block;" class="span2">湿度:{{weather?.data?.shidu}}</span>
+      <span id="interval_tp" style="display: block;" class="span2">空气质量:{{weather?.data?.quality}}</span>
+    </div>
+    <span class="span3">
+      <i class="layui-icon layui-icon-location" style="color:#65B0FF">{{weather?.cityInfo?.city}}</i><span
+        id="coordinate"></span>
+    </span>
+  </div>
+  <div class="box3">
+    <span id="crdate" class="span4">{{time}}</span>
+    <div class="box4">
+      <img [src]="image">
+    </div>
+    <span id="weekday" class="span5"
+      style="display: flex;text-align: right;">{{weather?.data?.forecast[0].notice}}</span>
+  </div>
+</div>

+ 112 - 0
TFPower-app/src/app/tab2/tianqi/tianqi.component.scss

@@ -0,0 +1,112 @@
+body {
+    background-color: aqua;
+}
+ 
+img {
+    max-width: 72px;
+    height: auto;
+}
+ 
+* {
+    box-sizing: border-box;
+    border-width: 0;
+    border-style: solid;
+    border-color: #e5e7eb;
+}
+
+.box1 {
+    margin: auto;
+    margin-top: 20px;
+    box-shadow: rgba(50, 50, 93, 0.25) 0px 2px 5px -1px, rgba(0, 0, 0, 0.3) 0px 1px 3px -1px;
+    display: flex;
+    position: relative;
+    width: 95%;
+    margin-right: 1.5rem;
+    background-color: white;
+    border-radius: 15px;
+    justify-content: space-between;
+    padding: 1.5rem;
+}
+ 
+.box2 {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-start;
+    justify-content: space-between;
+}
+ 
+.box3 {
+    display: flex;
+    flex-direction: column;
+    align-items: flex-end;
+}
+ 
+.box4 {
+    position: relative;
+    width: 72px;
+    margin-bottom: 0.75rem;
+}
+ 
+.span1 {
+    display: block;
+    font-size: 1.875rem;
+    line-height: 2.25rem;
+    margin-bottom: 0.25rem;
+    font-weight: 600;
+}
+ 
+.span2 {
+    --tw-text-opacity: 1;
+    color: rgb(102 102 102 / var(--tw-text-opacity));
+    font-size: 0.875rem;
+    line-height: 1.25rem;
+}
+ 
+.span3 {
+    display: flex;
+    align-items: center;
+}
+ 
+.span4 {
+    --tw-text-opacity: 1;
+    color: rgb(102 102 102 / var(--tw-text-opacity));
+    font-size: 0.875rem;
+    line-height: 1.25rem;
+    margin-bottom: 10px;
+}
+ 
+.span4 {
+    --tw-text-opacity: 1;
+    color: rgb(102 102 102 / var(--tw-text-opacity));
+    font-size: 0.875rem;
+    line-height: 1.25rem;
+}
+
+ion-button {
+    --background: #000;
+    --background-hover: #9ce0be;
+    --background-activated: #88f4be;
+    --background-focused: #88f4be;
+  
+    --color: #ecc422;
+  
+    --border-radius: 15px;
+    --border-color: #000;
+    --border-style: solid;
+    --border-width: 1px;
+  
+    --box-shadow: 0 2px 6px 0 rgb(0, 0, 0, 0.25);
+  
+    --ripple-color: deeppink;
+  
+    --padding-top: 10px;
+    --padding-bottom: 10px;
+  }
+
+
+
+
+
+
+
+

+ 22 - 0
TFPower-app/src/app/tab2/tianqi/tianqi.component.spec.ts

@@ -0,0 +1,22 @@
+import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
+
+import { TianqiComponent } from './tianqi.component';
+
+describe('TianqiComponent', () => {
+  let component: TianqiComponent;
+  let fixture: ComponentFixture<TianqiComponent>;
+
+  beforeEach(waitForAsync(() => {
+    TestBed.configureTestingModule({
+      imports: [TianqiComponent],
+    }).compileComponents();
+
+    fixture = TestBed.createComponent(TianqiComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  }));
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});

+ 74 - 0
TFPower-app/src/app/tab2/tianqi/tianqi.component.ts

@@ -0,0 +1,74 @@
+import { CommonModule } from '@angular/common';
+import { HttpClient } from '@angular/common/http';
+import { Component, OnInit } from '@angular/core';
+import { pinyin } from 'pinyin-pro';
+import { addIcons } from 'ionicons';
+import { checkmark } from 'ionicons/icons';
+addIcons({ checkmark })
+@Component({
+  selector: 'app-tianqi',
+  templateUrl: './tianqi.component.html',
+  styleUrls: ['./tianqi.component.scss'],
+  standalone: true,
+  imports: [CommonModule]
+})
+export class TianqiComponent implements OnInit {
+  time: string = "";
+  yulu: string = "";
+  juti: string = "";
+  loading: HTMLIonLoadingElement | null = null;
+  weather: any = [
+  ];
+  async fetchWeatherData() {
+    // 第一个请求,获取 adcode
+    const apiUrl = '/api/api/weather/city/101240101';
+    try {
+      const response: any = await this.http.get(apiUrl).toPromise(); // 使用 toPromise()
+      const adcode = response; // 获取 adcode 字段
+      if (adcode) {
+        this.weather = adcode; // 赋值给 weather
+        console.log(this.weather);
+      } else {
+        console.error('adcode not found in response:', response);
+      }
+    } catch (error) {
+      console.error('Error fetching adcode:', error);
+    }
+  }
+  image: string = ""
+  constructor(private http: HttpClient) {
+    this.fetchWeatherData().then(() => {
+      const dateMap = ["周天", "周一", "周二", "周三", "周四", "周五", "周六"];
+      const weekdayMap = ["零", "一", "两", "三", "四", "五"];
+      const date = new Date();
+      this.time = date.getMonth() + 1 + "-" + date.getDate() + " " + dateMap[date.getDay()];
+      if (date.getDay() === 6 || date.getDay() === 0) {
+        this.yulu = "好好享受周末吧~";
+      } else {
+        this.yulu = "再坚持" + weekdayMap[(6 - date.getDay())] + "天就到周末啦~";
+      }
+      console.log("weather.cityInfo:::", this.weather); // 这里打印的 weather 应该不再是空的
+      const weather_log = this.weather.data.forecast[0].type;
+      const daytemp = this.weather.data.forecast[0].high;
+      const nighttemp = this.weather.data.forecast[0].low;
+
+      console.log("nighttemp", nighttemp);
+
+      this.juti = nighttemp + daytemp
+      console.log(this.juti);
+      this.image = "assets/durian/" + this.convertToPinyin(this.weather?.data?.forecast[0].type) + ".png"
+    });
+  }
+  convertToPinyin(text: string): string {
+    const result = pinyin(text, {
+      toneType: 'none', // 不带声调
+      type: 'array', // 返回拼音数组
+    });
+    return result.join(''); // 将拼音数组转换为字符串
+  }
+  ngOnInit() {
+  }
+  public gptre: string = "";
+  public progress = 0;
+  public Complete = false
+}

BIN
TFPower-app/src/assets/durian/duoyun.png


BIN
TFPower-app/src/assets/durian/lei.png


BIN
TFPower-app/src/assets/durian/qing.png


BIN
TFPower-app/src/assets/durian/shachen.png


BIN
TFPower-app/src/assets/durian/wu.png


BIN
TFPower-app/src/assets/durian/xue.png


BIN
TFPower-app/src/assets/durian/yin.png


BIN
TFPower-app/src/assets/durian/yu.png


BIN
TFPower-app/src/assets/durian/yujiaxue.png


BIN
TFPower-app/src/assets/durian/zhenyu.png


BIN
TFPower-app/src/assets/images/power.png


+ 10 - 10
TFPower-app/src/lib/ncloud.ts

@@ -29,7 +29,7 @@ export class CloudObject {
 
     async save() {
         let method = "POST";
-        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}`;
+        let url = `https://dev.fmode.cn/parse/classes/${this.className}`;
 
         // 更新
         if (this.id) {
@@ -61,7 +61,7 @@ export class CloudObject {
 
     async destroy() {
         if (!this.id) return;
-        const response = await fetch(`http://dev.fmode.cn:1337/parse/classes/${this.className}/${this.id}`, {
+        const response = await fetch(`https://dev.fmode.cn/parse/classes/${this.className}/${this.id}`, {
             headers: {
                 "x-parse-application-id": "dev"
             },
@@ -120,7 +120,7 @@ export class CloudQuery {
     }
 
     async get(id: string) {
-        const url = `http://dev.fmode.cn:1337/parse/classes/${this.className}/${id}?`;
+        const url = `https://dev.fmode.cn/parse/classes/${this.className}/${id}?`;
 
         const response = await fetch(url, {
             headers: {
@@ -138,7 +138,7 @@ export class CloudQuery {
     }
 
     async find() {
-        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+        let url = `https://dev.fmode.cn/parse/classes/${this.className}?`;
 
         let queryStr = ``
         Object.keys(this.queryParams).forEach(key => {
@@ -174,7 +174,7 @@ export class CloudQuery {
     }
 
     async first() {
-        let url = `http://dev.fmode.cn:1337/parse/classes/${this.className}?`;
+        let url = `https://dev.fmode.cn/parse/classes/${this.className}?`;
 
         if (Object.keys(this.queryParams["where"]).length) {
             const whereStr = JSON.stringify(this.queryParams["where"]);
@@ -234,7 +234,7 @@ export class CloudUser extends CloudObject {
             return null;
         }
         return this;
-        // const response = await fetch(`http://dev.fmode.cn:1337/parse/users/me`, {
+        // const response = await fetch(`https://dev.fmode.cn/parse/users/me`, {
         //     headers: {
         //         "x-parse-application-id": "dev",
         //         "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
@@ -288,7 +288,7 @@ export class CloudUser extends CloudObject {
 
     /** 登录 */
     async login(username: string, password: string): Promise<CloudUser | null> {
-        const response = await fetch(`http://dev.fmode.cn:1337/parse/login`, {
+        const response = await fetch(`https://dev.fmode.cn/parse/login`, {
             headers: {
                 "x-parse-application-id": "dev",
                 "Content-Type": "application/json"
@@ -320,7 +320,7 @@ export class CloudUser extends CloudObject {
             return;
         }
 
-        const response = await fetch(`http://dev.fmode.cn:1337/parse/logout`, {
+        const response = await fetch(`https://dev.fmode.cn/parse/logout`, {
             headers: {
                 "x-parse-application-id": "dev",
                 "x-parse-session-token": this.sessionToken
@@ -350,7 +350,7 @@ export class CloudUser extends CloudObject {
             ...additionalData // 合并额外的用户数据
         };
 
-        const response = await fetch(`http://dev.fmode.cn:1337/parse/users`, {
+        const response = await fetch(`https://dev.fmode.cn/parse/users`, {
             headers: {
                 "x-parse-application-id": "dev",
                 "Content-Type": "application/json"
@@ -377,7 +377,7 @@ export class CloudUser extends CloudObject {
 
     override async save() {
         let method = "POST";
-        let url = `http://dev.fmode.cn:1337/parse/users`;
+        let url = `https://dev.fmode.cn/parse/users`;
 
         // 更新用户信息
         if (this.id) {

+ 11 - 0
TFPower-app/src/proxy.config.json

@@ -0,0 +1,11 @@
+{
+    "/api": {
+        "target": "http://t.weather.sojson.com/",
+        "secure": false,
+        "logLevel": "debug",
+        "changeOrigin": true,
+        "pathRewrite": {
+            "^/api": ""
+        }
+    }
+}

+ 135 - 1
TFPower-server/lib/ncloud.js

@@ -1,5 +1,6 @@
 
 
+<<<<<<< HEAD
 // class CloudObject{
 //     id
 //     className
@@ -182,10 +183,27 @@ class CloudObject{
             if(["objectId","id","createdAt","updateAt","ACL"].indexOf(key)>-1)
             {
                 return 
+=======
+class CloudObject {
+    id
+    className
+    data = {}
+    constructor(className) {
+        this.className = className
+    }
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id }
+    }
+    set(json) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt", "ACL"].indexOf(key) > -1) {
+                return
+>>>>>>> xk
             }
             this.data[key]=json[key]
         })
     }
+<<<<<<< HEAD
     // data返回数据给界面
     get(){
         return this.data[key] || null
@@ -223,17 +241,60 @@ class CloudObject{
     async destroy() {
         if(!this.id)return 
         let response=await fetch("http://dev.fmode.cn:1337/parse/classes/Post/"+this.id, {
+=======
+    get(key) {
+        return this.data[key] || null
+    }
+    async save() {
+        let method = "POST"
+        let url = "https://dev.fmode.cn/parse/classes/" + this.className
+        // 更新
+        if (this.id) {
+            url += "/" + this.id
+            method = "PUT"
+        }
+        let body = JSON.stringify(this.data)
+        let response = await fetch(url, {
             "headers": {
-              "x-parse-application-id": "dev"
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            "body": body,
+            "method": method,
+            "mode": "cors",
+            "credentials": "omit"
+        });
+        let result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error)
+        }
+        if (result?.objectId) { this.id = result?.objectId }
+        return this
+    }
+    async destory() {
+        if (!this.id) return
+        let response = await fetch("https://dev.fmode.cn/parse/classes/Doctor/" + this.id, {
+>>>>>>> xk
+            "headers": {
+                "x-parse-application-id": "dev"
             },
             "body": null,
             "method": "DELETE",
             "mode": "cors",
             "credentials": "omit"
+<<<<<<< HEAD
           });
           let json=await response?.json()
           if(json)this.id=null
           return true;
+=======
+        });
+        let result = await response?.json();
+        if (result) {
+            this.id = null
+        }
+        return true
+>>>>>>> xk
     }
 
 }
@@ -241,6 +302,7 @@ class CloudQuery
 {
     ClassName
 
+<<<<<<< HEAD
     constructor(ClassName)
     {
         this.ClassName=ClassName
@@ -280,6 +342,42 @@ class CloudQuery
             "headers": {
               "if-none-match": "W/\"ab-2gNtNZqRYX93fdbIaSj6z5h571k\"",
               "x-parse-application-id": "dev"
+=======
+class CloudQuery {
+    className
+    constructor(className) {
+        this.className = className
+    }
+
+    whereOptions = {}
+    greaterThan(key, value) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gt"] = value
+    }
+    greaterThanAndEqualTo(key, value) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$gte"] = value
+    }
+    lessThan(key, value) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lt"] = value
+    }
+    lessThanAndEqualTo(key, value) {
+        if (!this.whereOptions[key]) this.whereOptions[key] = {}
+        this.whereOptions[key]["$lte"] = value
+    }
+    equalTo(key, value) {
+        this.whereOptions[key] = value
+    }
+
+    async get(id) {
+        let url = "https://dev.fmode.cn/parse/classes/" + this.className + "/" + id + "?"
+
+        let response = await fetch(url, {
+            "headers": {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+>>>>>>> xk
             },
     
             "body": null,
@@ -291,6 +389,7 @@ class CloudQuery
         // console.log(json);
         return json || {}
     }
+<<<<<<< HEAD
  
     // 查询一个表中的所有数据,高级查询
     async find(){
@@ -301,12 +400,25 @@ class CloudQuery
         {
             let whereStr=JSON.stringify(this.whereOptions)
             url+=`where=${whereStr}`
+=======
+    async find() {
+        let url = "https://dev.fmode.cn/parse/classes/" + this.className + "?"
+
+        if (Object.keys(this.whereOptions)?.length) {
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+>>>>>>> xk
         }
 
         let response=await fetch(url, {
             "headers": {
+<<<<<<< HEAD
               "if-none-match": "W/\"ab-2gNtNZqRYX93fdbIaSj6z5h571k\"",
               "x-parse-application-id": "dev"
+=======
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+>>>>>>> xk
             },
 
             "body": null,
@@ -318,6 +430,7 @@ class CloudQuery
         // console.log(json);
         return json?.results || []
     }
+<<<<<<< HEAD
     async first(){
         let url="http://dev.fmode.cn:1337/parse/classes/"+this.ClassName+"?"
 
@@ -326,18 +439,32 @@ class CloudQuery
         {
             let whereStr=JSON.stringify(this.whereOptions)
             url+=`where=${whereStr}`
+=======
+    async first() {
+        let url = "https://dev.fmode.cn/parse/classes/" + this.className + "?"
+
+        if (Object.keys(this.whereOptions)?.length) {
+            let whereStr = JSON.stringify(this.whereOptions)
+            url += `where=${whereStr}`
+>>>>>>> xk
         }
 
         let response=await fetch(url, {
             "headers": {
+<<<<<<< HEAD
               "if-none-match": "W/\"ab-2gNtNZqRYX93fdbIaSj6z5h571k\"",
               "x-parse-application-id": "dev"
+=======
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+>>>>>>> xk
             },
 
             "body": null,
             "method": "GET",
             "mode": "cors",
             "credentials": "omit"
+<<<<<<< HEAD
           });
         let json=await response?.json();
         // console.log(json);
@@ -346,6 +473,13 @@ class CloudQuery
         {
             let existsObject=new CloudObject(this.ClassName)
 
+=======
+        });
+        let json = await response?.json();
+        let exists = json?.results?.[0] || null
+        if (exists) {
+            let existsObject = new CloudObject(this.className)
+>>>>>>> xk
             existsObject.set(exists)
             existsObject.id=exists.objectId
             existsObject.createdAt=exists.createdAt