Pārlūkot izejas kodu

add game page cards

0225273 1 nedēļu atpakaļ
vecāks
revīzija
baae014d0c

+ 4 - 3
MindOCApp/src/app/game/game.module.ts

@@ -1,5 +1,5 @@
 import { IonicModule } from '@ionic/angular';
-import { NgModule } from '@angular/core';
+import { isStandalone, NgModule } from '@angular/core';
 import { CommonModule } from '@angular/common';
 import { FormsModule } from '@angular/forms';
 import { GamePage } from './game.page';
@@ -13,8 +13,9 @@ import { GamePageRoutingModule } from './game-routing.module';
     CommonModule,
     FormsModule,
     ExploreContainerComponentModule,
-    GamePageRoutingModule
+    GamePageRoutingModule,
   ],
-  declarations: [GamePage]
+  declarations: [GamePage],
+  
 })
 export class GamePageModule {}

+ 100 - 5
MindOCApp/src/app/game/game.page.html

@@ -1,5 +1,5 @@
-<ion-content  class="game-container">
-  <!-- 游戏模式切换 -->
+<!-- <ion-content  class="game-container">
+  游戏模式切换
   <div class="game-mode-switch">
     <ion-segment [(ngModel)]="currentGame" (ionChange)="switchGameMode()">
       <ion-segment-button value="bubble">
@@ -51,12 +51,12 @@
   </div>
 
 
-  <!-- 游戏画布容器 -->
+  游戏画布容器
   <div #gameCanvasContainer class="canvas-container">
     <canvas #gameCanvas></canvas>
   </div>
   
-  <!-- 游戏控制面板 -->
+  游戏控制面板
   <div class="game-panel">
     <div class="score-board">
       <ion-icon name="trophy"></ion-icon>
@@ -69,4 +69,99 @@
       ></ion-icon>
     </ion-button>
   </div>  
-</ion-content>
+</ion-content> -->
+<ion-header translucent>
+  <ion-toolbar>
+    <ion-title>解压游戏舱</ion-title>
+    <ion-buttons slot="end">
+      <ion-button (click)="openAchievements()">
+        <ion-icon name="trophy" slot="icon-only"></ion-icon>
+      </ion-button>
+    </ion-buttons>
+  </ion-toolbar>
+</ion-header>
+<ion-content [fullscreen]="true" class="game-container">
+  <!-- 情绪状态卡片
+  <div class="mood-card" *ngIf="currentMood">
+    <div class="mood-visual" [style.background]="getMoodGradient()">
+      <span class="mood-score">{{ currentMood.score | number:'1.1-1' }}</span>
+    </div>
+    <div class="mood-info">
+      <h3>当前情绪状态</h3>
+      <p>{{ currentMood.description }}</p>
+      <ion-chip *ngFor="let tag of currentMood.tags">
+        {{ tag }}
+      </ion-chip>
+    </div>
+  </div> -->
+  <!-- 游戏卡片列表 -->
+  <div class="game-cards">
+    <!-- 捏泡泡游戏卡片 -->
+    <div class="game-card" (click)="openGame('bubble')">
+      <div class="game-preview bubble-preview">
+        <img [src]="'assets/images/bubbles.jpg'">
+        <div class="bubble" *ngFor="let bubble of demoBubbles"></div>
+      </div>
+      <div class="game-info">
+        <h3>压力泡泡</h3>
+        <p>点击爆破彩色泡泡释放压力</p>
+        <div class="game-meta">
+          <span class="duration">2-5分钟</span>
+          <ion-badge *ngIf="unlockedAchievements.bubble" color="primary">
+            已解锁
+          </ion-badge>
+        </div>
+      </div>
+    </div>
+    <!-- 禅意沙画游戏卡片 -->
+    <div class="game-card"  (click)="openGame('sand')">
+      <div class="game-preview sand-preview">
+        <img [src]="'assets/images/sands.jpg'">
+        <div class="sand-trail"></div>
+      </div>
+      <div class="game-info">
+        <h3>禅意沙画</h3>
+        <p>指尖描绘宁静沙纹</p>
+        <div class="game-meta">
+          <span class="duration">3-8分钟</span>
+          <ion-badge *ngIf="!unlockedAchievements.sand" color="medium">
+            需完成3次泡泡游戏
+          </ion-badge>
+        </div>
+      </div>
+    </div>
+    <!-- 水晶雨滴游戏卡片 -->
+    <div class="game-card" (click)="openGame('raindrop')">
+      <div class="game-preview raindrop-preview">
+        <img [src]="'assets/images/raindrops.jpg'">
+      </div>
+      <div class="game-info">
+        <h3>水晶雨滴</h3>
+        <p>触碰下落的雨滴创造涟漪</p>
+        <div class="game-meta">
+          <span class="duration">3-7分钟</span>
+          <ion-badge *ngIf="unlockedAchievements.raindrop" color="primary">
+            已解锁
+          </ion-badge>
+        </div>
+      </div>
+    </div>
+    <!-- 星空涂鸦游戏卡片 -->
+    <div class="game-card" (click)="openGame('constellation')">
+      <div class="game-preview constellation-preview">
+        <img [src]="'assets/images/stars.jpg'">
+      </div>
+      <div class="game-info">
+        <h3>星空涂鸦</h3>
+        <p>连接星星创造专属星座</p>
+        <div class="game-meta">
+          <span class="duration">5-10分钟</span>
+          <ion-badge *ngIf="!unlockedAchievements.constellation" color="medium">
+            需完成5次游戏
+          </ion-badge>
+        </div>
+      </div>
+    </div>
+    <!-- 更多游戏卡片... -->
+  </div>
+</ion-content>

+ 222 - 134
MindOCApp/src/app/game/game.page.scss

@@ -1,166 +1,254 @@
 /* game.page.scss */
-/* card*/
-.card {
-  position: relative;
-  width: 190px;
-  height: 254px;
-  background-color: #000;
-  display: flex;
-  flex-direction: column;
-  justify-content: end;
-  padding: 12px;
-  gap: 12px;
-  border-radius: 8px;
-  cursor: pointer;
-}
-
-.card::before {
-  content: '';
-  position: absolute;
-  inset: 0;
-  left: -5px;
-  margin: auto;
-  width: 200px;
-  height: 264px;
-  border-radius: 10px;
-  background: linear-gradient(-45deg, #e81cff 0%, #40c9ff 100% );
-  z-index: -10;
-  pointer-events: none;
-  transition: all 0.6s cubic-bezier(0.175, 0.885, 0.32, 1.275);
-}
-
-.card::after {
-  content: "";
-  z-index: -1;
-  position: absolute;
-  inset: 0;
-  background: linear-gradient(-45deg, #fc00ff 0%, #00dbde 100% );
-  transform: translate3d(0, 0, 0) scale(0.95);
-  filter: blur(20px);
-}
-
-.heading {
-  font-size: 20px;
-  text-transform: capitalize;
-  font-weight: 700;
-}
-
-.card p:not(.heading) {
-  font-size: 14px;
-}
-
-.card p:last-child {
-  color: #e81cff;
-  font-weight: 600;
+.game-container {
+  --background: linear-gradient(180deg, #f8f9fa 0%, #e9ecef 100%);
+  padding-top: 12px;
 }
 
-.card:hover::after {
-  filter: blur(30px);
+/* 情绪状态卡片 */
+.mood-card {
+  display: flex;
+  background: white;
+  border-radius: 16px;
+  margin: 0 16px 20px;
+  box-shadow: 0 4px 12px rgba(192, 108, 132, 0.1);
+  overflow: hidden;
+  
+  .mood-visual {
+    width: 30%;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    background: linear-gradient(135deg, #a8edea 0%, #fed6e3 100%);
+    
+    .mood-score {
+      font-size: 24px;
+      font-weight: bold;
+      color: white;
+      text-shadow: 0 2px 4px rgba(0,0,0,0.1);
+    }
+  }
+  
+  .mood-info {
+    width: 70%;
+    padding: 12px;
+    
+    h3 {
+      margin: 0 0 8px;
+      color: #495057;
+      font-size: 16px;
+    }
+    
+    p {
+      margin: 0 0 8px;
+      color: #6c757d;
+      font-size: 14px;
+    }
+    
+    ion-chip {
+      height: 24px;
+      font-size: 12px;
+      margin: 4px 4px 0 0;
+    }
+  }
 }
 
-.card:hover::before {
-  transform: rotate(-90deg) scaleX(1.34) scaleY(0.77);
+/* 游戏卡片列表 */
+.game-cards {
+  padding: 0 16px;
 }
 
-
-.game-container {
-    --game-primary: #FF9A9E;
-    --game-secondary: #A8EDEA;
-    --game-accent: #6C5B7B;
+.game-card {
+  background: white;
+  border-radius: 12px;
+  margin-bottom: 16px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.08);
+  transition: transform 0.3s ease, box-shadow 0.3s ease;
   
-    height: 100vh;
+  &:active {
+    transform: scale(0.98);
+  }
+  
+  .game-preview {
+    height: 120px;
+    position: relative;
     overflow: hidden;
+  }
   
-    .game-mode-switch {
+  /* 捏泡泡预览效果 */
+  .bubble-preview {
+    background: linear-gradient(45deg, #f6f7f9 0%, #e9ecef 100%);
+    
+    .bubble {
       position: absolute;
-      top: 16px;
-      left: 50%;
-      transform: translateX(-50%);
-      z-index: 10;
-      width: 80%;
-      max-width: 400px;
+      border-radius: 50%;
+      background: rgba(192, 108, 132, 0.6);
+      animation: float 4s infinite ease-in-out;
+      
+      @for $i from 1 through 8 {
+        &:nth-child(#{$i}) {
+          width: 20px + random(30);
+          height: 20px + random(30);
+          left: random(80) + '%';
+          top: random(80) + '%';
+          animation-delay: $i * 0.5s;
+          background: rgba(192 - $i*10, 108 + $i*5, 132 - $i*3, 0.6);
+        }
+      }
+    }
+  }
   
-      ion-segment {
-        --background: #ff9a9e;
-        backdrop-filter: blur(10px);
-        height: 40px;
+  /* 沙画预览效果 */
+  .sand-preview {
+    background: #f8f9fa;
+    
+    .sand-trail {
+      position: absolute;
+      width: 80%;
+      height: 3px;
+      background: #d4b483;
+      top: 50%;
+      left: 10%;
+      border-radius: 3px;
+      
+      &::before, &::after {
+        content: '';
+        position: absolute;
+        width: 60%;
+        height: 3px;
+        background: inherit;
+        border-radius: inherit;
+      }
+      
+      &::before {
+        top: -20px;
+        left: 20%;
+        transform: rotate(20deg);
       }
       
+      &::after {
+        top: 20px;
+        left: 10%;
+        transform: rotate(-15deg);
+      }
     }
+  }
   
-    .canvas-container {
-      width: 100%;
-      height: calc(100% - 60px);
-      touch-action: none;
+  /* 雨滴预览效果 */
+  .raindrop-preview {
+    background: linear-gradient(180deg, #e0f7fa 0%, #b2ebf2 100%);
+    position: relative;
+    
+    .raindrop {
+      position: absolute;
+      width: 6px;
+      height: 12px;
+      background: linear-gradient(180deg, rgba(255,255,255,0.8) 0%, rgba(178,235,242,0.6) 100%);
+      border-radius: 50% 50% 60% 60%;
+      animation: fall 3s linear infinite;
+      
+      @for $i from 1 through 6 {
+        &:nth-child(#{$i}) {
+          left: 15% + $i * 12%;
+          top: -20px;
+          animation-delay: $i * 0.3s;
+        }
+      }
     }
-  
-    .game-panel {
+    
+    .ripple {
       position: absolute;
-      bottom: 20px;
-      left: 20px;
-      right: 20px;
-      display: flex;
-      justify-content: space-between;
-      align-items: center;
-      padding: 12px 20px;
-      background: rgba(255,255,255,0.9);
-      border-radius: 30px;
-      backdrop-filter: blur(8px);
-  
-      .score-board {
-        display: flex;
-        align-items: center;
-        gap: 8px;
-        color: var(--game-accent);
-        font-weight: 500;
-  
-        ion-icon {
-          color: #FFD700;
-          font-size: 24px;
+      border: 1px solid rgba(255,255,255,0.6);
+      border-radius: 50%;
+      animation: spread 2s ease-out infinite;
+      
+      @for $i from 1 through 3 {
+        &:nth-child(#{$i + 6}) {
+          width: 10px * $i;
+          height: 10px * $i;
+          left: 40%;
+          top: 70%;
+          animation-delay: $i * 0.5s;
         }
       }
     }
+  }
   
-    .achievement-toast {
-      position: fixed;
-      top: -60px;
-      left: 50%;
-      transform: translateX(-50%);
-      background: rgba(0,0,0,0.8);
-      color: white;
-      padding: 12px 24px;
-      border-radius: 30px;
+  @keyframes fall {
+    0% { transform: translateY(-20px) scale(0.8); opacity: 0; }
+    20% { opacity: 1; }
+    80% { opacity: 0.8; }
+    100% { transform: translateY(120px) scale(0.9); opacity: 0; }
+  }
+  
+  @keyframes spread {
+    0% { transform: scale(0.5); opacity: 1; }
+    100% { transform: scale(3); opacity: 0; }
+  }
+  .game-info {
+    padding: 12px;
+    position: relative;
+    overflow: hidden;
+    background: linear-gradient(
+        to top, 
+        rgba(255, 255, 255, 0) 0%, 
+        rgba(255, 255, 255, 0.85) 50%,
+        rgba(255, 255, 255, 0.9) 100%
+    );
+
+    h3 {
+      margin: 0 0 4px;
+      color: #343a40;
+      font-size: 18px;
+    }
+    
+    p {
+      margin: 0 0 8px;
+      color: #6c757d;
+      font-size: 14px;
+    }
+    
+    .game-meta {
       display: flex;
+      justify-content: space-between;
       align-items: center;
-      gap: 8px;
-      transition: top 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);
-  
-      &.show {
-        top: 20px;
-      }
-  
-      ion-icon {
-        color: #FFD700;
+      
+      .duration {
+        font-size: 12px;
+        //color: #adb5bd;
       }
     }
   }
+}
+
+/* 浮动推荐 */
+.floating-recommendation {
+  position: fixed;
+  bottom: 80px;
+  left: 16px;
+  right: 16px;
+  background: white;
+  border-radius: 12px;
+  padding: 12px 16px;
+  box-shadow: 0 4px 16px rgba(0,0,0,0.1);
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 10;
   
-  /* 泡泡爆破动画 */
-  @keyframes pop {
-    0% { transform: scale(1); opacity: 1; }
-    100% { transform: scale(1.5); opacity: 0; }
+  p {
+    margin: 0;
+    color: #495057;
+    font-size: 14px;
+    
+    strong {
+      color: #c06c84;
+    }
   }
-
-ion-icon[name^="volume"] {
-transition: transform 0.3s ease;
-
-&[name="volume-mute"] {
-    animation: shake 0.5s ease;
-}
 }
 
-@keyframes shake {
-0%, 100% { transform: translateX(0); }
-25% { transform: translateX(-3px); }
-75% { transform: translateX(3px); }
+/* 动画 */
+@keyframes float {
+  0%, 100% { transform: translateY(0) scale(1); }
+  50% { transform: translateY(-10px) scale(1.05); }
 }

+ 59 - 151
MindOCApp/src/app/game/game.page.ts

@@ -1,175 +1,83 @@
 // game.page.ts
-import { Component, ViewChild, ElementRef, AfterViewInit, HostListener } from '@angular/core';
+import { Component, ViewChild, ElementRef, AfterViewInit, HostListener , OnInit} from '@angular/core';
 import * as PIXI from 'pixi.js';
 import { Howl, Howler } from 'howler';
 import { Haptics, ImpactStyle } from '@capacitor/haptics';
-
+import { ModalController } from '@ionic/angular';
 @Component({
   selector: 'app-game',
   templateUrl: 'game.page.html',
   styleUrls: ['game.page.scss'],
   standalone: false,
 })
-export class GamePage implements AfterViewInit {
-
-  @ViewChild('gameCanvas') canvasRef!: ElementRef;
-  @ViewChild('gameCanvasContainer') containerRef!: ElementRef;
-  
-  // 游戏状态
-  currentGame: 'bubble' | 'sand' = 'bubble';
-  score = 0;
-  isMuted = false;
-  showAchievement = false;
-  achievementText = '';
-  achievementIcon = '';
-
-  // PIXI实例
-  private app!: PIXI.Application;
-  private bubbles: PIXI.Sprite[] = [];
-  private sandTexture!: PIXI.RenderTexture;
-
-  ngAfterViewInit() {
-    this.initPixi();
-    this.loadAssets();
-  }
-
-  private initPixi(){
+export class GamePage implements OnInit {
 
-  }
+  currentMood = {
+    score: 0.6,
+    description: "轻度焦虑",
+    tags: ["工作压力", "时间紧迫"]
+  };
 
-  private loadAssets(){
-    PIXI.Assets.load([
-      { alias: 'bubble', src: 'assets/images/bubble.png' },
-      { alias: 'brush', src: 'assets/images/brush.png' }
-    ]).then(() => {
-      //this.initBubbleGame();
-      //this.initSandGame();
-      this.switchGameMode();
-    });
+  demoBubbles = Array(8).fill(0);
+  unlockedAchievements = {
+    bubble: true,
+    sand: false,
+    raindrop:false,
+    constellation:false
+  };
+  showRecommendation = true;
+  recommendedGame = "禅意沙画";
+  constructor(private modalCtrl: ModalController) {}
+  ngOnInit() {
+    // 从服务获取最新情绪数据和游戏解锁状态
   }
-
-  /** 切换游戏模式 */
-  switchGameMode() {
-    this.app.stage.removeChildren();
-    if (this.currentGame === 'bubble') {
-      this.app.stage.addChild(...this.bubbles);
+  getMoodGradient() {
+    const score = this.currentMood.score;
+    if (score > 0.7) {
+      return 'linear-gradient(135deg, #a8edea 0%, #fed6e3 100%)';
+    } else if (score > 0.4) {
+      return 'linear-gradient(135deg, #f6f7f9 0%, #e9ecef 100%)';
     } else {
-      this.initSandDrawing();
+      return 'linear-gradient(135deg, #ff9a9e 0%, #fad0c4 100%)';
     }
   }
 
-  /** 沙画绘制逻辑 */
-  private initSandDrawing() {
-    const brush = PIXI.Sprite.from('brush');
-    brush.anchor.set(0.5);
-    brush.visible = false;
-
-    const sandGraphics = new PIXI.Graphics();
-    let drawing = false;
-    let lastPoint: PIXI.Point | null = null;
-
-    // 触摸/鼠标事件
-    this.app.stage.eventMode = 'static';
-    this.app.stage.hitArea = this.app.screen;
-    this.app.stage
-      .on('pointerdown', (e: PIXI.FederatedPointerEvent) => {
-        drawing = true;
-        lastPoint = e.global.clone();
-        brush.position.copyFrom(e.global);
-        brush.visible = true;
-      })
-      .on('pointermove', (e: PIXI.FederatedPointerEvent) => {
-        if (!drawing) return;
-        
-        // 绘制沙粒轨迹
-        //sandGraphics.lineStyle({ width: 8, color: 0xf4d03f, alpha: 0.7 });
-        if (lastPoint) {
-          sandGraphics.moveTo(lastPoint.x, lastPoint.y);
-          sandGraphics.lineTo(e.global.x, e.global.y);
+  async openGame(gameType: string) {
+    let component;
+    switch(gameType) {
+      case 'bubble':
+        //component = BubbleGamePage;
+        break;
+      case 'sand':
+        if (!this.unlockedAchievements.sand) {
+          // 显示解锁条件提示
+          return;
         }
-        lastPoint = e.global.clone();
-
-        // 更新笔刷位置
-        brush.position.copyFrom(e.global);
-        this.playSound('draw');
-      })
-      .on('pointerup', () => {
-        drawing = false;
-        brush.visible = false;
-        lastPoint = null;
-      });
-
-    this.app.stage.addChild(sandGraphics, brush);
-  }
-
-  // 音效
-  private sounds = {
-    pop: new Howl({ src: ['assets/sfx/pop.mp3'] }),
-    draw: new Howl({ src: ['assets/sfx/sand-draw.mp3'] }),
-    unlock: new Howl({ src: ['assets/sfx/achievement.mp3'] })
-  };
-
-  /** 播放音效 */
-  private playSound(type: keyof typeof this.sounds) {
-    if (!this.isMuted) {
-      this.sounds[type].play();
-    }
-  }
-
-  /** 
-   * 切换声音状态 
-   * 功能说明:
-   * 1. 切换全局静音状态
-   * 2. 控制所有音效实例
-   * 3. 提供触觉反馈
-   * 4. 保存状态到本地存储
-   */
-   async toggleSound() {
-    try {
-      // 切换静音状态
-      this.isMuted = !this.isMuted;
-
-      // 触觉反馈(需要Capacitor支持)
-      await Haptics.impact({ style: ImpactStyle.Light });
-
-      // 控制全局音频
-      Howler.mute(this.isMuted);
-
-      // 同步控制单独音效实例(可选)
-      Object.values(this.sounds).forEach(sound => {
-        sound.mute(this.isMuted);
-      });
-
-      // 保存状态到本地存储
-      localStorage.setItem('gameMuteState', JSON.stringify(this.isMuted));
-
-      // 显示状态提示
-      this.showSoundStateToast();
-    } catch (error) {
-      console.error('切换声音时出错:', error);
+        //component = SandGamePage;
+        break;
+      case 'raindrop':
+        if (!this.unlockedAchievements.raindrop) {
+          // 显示解锁条件提示
+          return;
+        }
+        //component = SandGamePage;
+        break;
+      case 'constellation':
+        if (!this.unlockedAchievements.constellation) {
+          // 显示解锁条件提示
+          return;
+        }
+        //component = SandGamePage;
+        break;
+      default:
+        return;
     }
   }
-
-  /** 显示声音状态提示 */
-  private showSoundStateToast() {
-    this.achievementText = this.isMuted ? '静音模式已开启' : '声音模式已开启';
-    this.achievementIcon = this.isMuted ? 'volume-mute' : 'volume-high';
-    this.showAchievement = true;
-    
-    setTimeout(() => {
-      this.showAchievement = false;
-    }, 1500);
+  openAchievements() {
+    // 打开成就页面
   }
 
-  /** 初始化时读取静音状态 */
-  private initSoundState() {
-    const savedState = localStorage.getItem('gameMuteState');
-    if (savedState) {
-      this.isMuted = JSON.parse(savedState);
-      Howler.mute(this.isMuted);
-    }
+  dismissRecommendation() {
+    this.showRecommendation = false;
   }
-
-  constructor() {}
-
 }

BIN
MindOCApp/src/assets/images/bubbles.jpg


BIN
MindOCApp/src/assets/images/raindrops.jpg


BIN
MindOCApp/src/assets/images/sands.jpg


BIN
MindOCApp/src/assets/images/stars.jpg


+ 6 - 1
PageLayout.md

@@ -102,4 +102,9 @@
 
 ---
 
-此设计通过**柔和的渐变色**、**有节制的动效**和**直观的交互逻辑**,构建了一个兼具专业性与亲和力的数字疗愈空间。每个视觉元素均服务于核心功能——帮助用户在5分钟内完成一次有效的情绪调节。
+此设计通过**柔和的渐变色**、**有节制的动效**和**直观的交互逻辑**,构建了一个兼具专业性与亲和力的数字疗愈空间。每个视觉元素均服务于核心功能——帮助用户在5分钟内完成一次有效的情绪调节。
+```mermaid
+graph LR
+  感知层 -->|语音/生物信号| 分析层 -->|情绪图谱| 干预层
+  干预层 -->|游戏引擎| 感知层
+```