Parcourir la source

Added AR camera activation with scanning animation

Implemented automatic body measurements calculation based on height/weight

Improved scene selection with visual feedback and custom icons

Added responsive design and custom notification system

Fixed TypeScript errors and Angular template bindings

Enhanced UI with animations and better visual hierarchy
“0235959” il y a 2 semaines
Parent
commit
655d53a5b1

+ 36 - 7
cloth-dit-web/src/modules/cloth/color/pages-fitting-room/pages-fitting-room/pages-fitting-room.html

@@ -8,12 +8,18 @@
 <main class="main-content">
     <div class="fitting-room-container">
         <!-- AR相机区域 -->
-        <div class="ar-camera" (click)="startAR()">
-            <div class="camera-content">
+        <div class="ar-camera" [class.active]="isARCameraActive" (click)="isARCameraActive ? stopAR() : startAR()">
+            <div class="camera-content" *ngIf="!isARCameraActive">
                 <div class="camera-icon">📷</div>
                 <div>点击开启AR试穿</div>
                 <div class="camera-desc">支持实时身材建模</div>
             </div>
+            <div class="ar-preview" *ngIf="isARCameraActive">
+                <div class="ar-overlay">
+                    <div class="scanning-animation"></div>
+                    <div class="ar-instruction">请将摄像头对准全身</div>
+                </div>
+            </div>
         </div>
 
         <!-- 身材设置 -->
@@ -34,17 +40,40 @@
                            [style.accentColor]="'#ff6b6b'">
                     <span>{{ weight }}kg</span>
                 </div>
+                <div class="setting-item">
+                    <label>胸围 (cm)</label>
+                    <div class="measurement-value">{{ bodyMeasurements.chest }}cm</div>
+                </div>
+                <div class="setting-item">
+                    <label>腰围 (cm)</label>
+                    <div class="measurement-value">{{ bodyMeasurements.waist }}cm</div>
+                </div>
+                <div class="setting-item">
+                    <label>臀围 (cm)</label>
+                    <div class="measurement-value">{{ bodyMeasurements.hips }}cm</div>
+                </div>
+                <div class="setting-item full-width">
+                    <label>体型预览</label>
+                    <div class="body-preview">
+                        <div class="body-shape" [style.height.px]="height/2"></div>
+                    </div>
+                </div>
             </div>
         </div>
 
         <!-- 场景选择 -->
         <div class="scene-selector">
-            <h4>试穿场景</h4>
+            <h4>试穿场景 <span *ngIf="selectedScene">- {{ sceneNames[selectedScene] }}</span></h4>
             <div class="scenes">
-                <div class="scene-item" (click)="selectScene('office')">🏢</div>
-                <div class="scene-item" (click)="selectScene('date')">💕</div>
-                <div class="scene-item" (click)="selectScene('party')">🎉</div>
-                <div class="scene-item" (click)="selectScene('casual')">☕</div>
+                <div class="scene-item" 
+                     *ngFor="let scene of sceneKeys"
+                     [style.--scene-bg-start]="sceneBackgrounds[scene]['--scene-bg-start']"
+                     [style.--scene-bg-end]="sceneBackgrounds[scene]['--scene-bg-end']"
+                     [class.active]="selectedScene === scene"
+                     (click)="selectScene(scene)">
+                    <div class="scene-icon">{{ getSceneIcon(scene) }}</div>
+                    <div class="scene-name">{{ sceneNames[scene] }}</div>
+                </div>
             </div>
         </div>
     </div>

+ 314 - 79
cloth-dit-web/src/modules/cloth/color/pages-fitting-room/pages-fitting-room/pages-fitting-room.scss

@@ -1,123 +1,358 @@
+@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@400;500;700&display=swap');
+
+:root {
+  --primary-color: #667eea;
+  --primary-dark: #764ba2;
+  --accent-color: #ff6b6b;
+  --text-color: #333;
+  --light-text: #777;
+  --bg-color: #f8f9fa;
+  --card-bg: white;
+}
+
+body {
+  font-family: 'Noto Sans SC', sans-serif;
+  color: var(--text-color);
+  background-color: var(--bg-color);
+  margin: 0;
+  padding: 0;
+}
+
+.header {
+  background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
+  color: white;
+  padding: 2rem 1rem;
+  text-align: center;
+  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
+  
+  h1 {
+    margin: 0;
+    font-size: 2.2rem;
+    font-weight: 700;
+  }
+  
+  p {
+    margin: 0.5rem 0 0;
+    font-size: 1rem;
+    opacity: 0.9;
+  }
+}
+
 .fitting-room-container {
-    margin: 20px;
+  max-width: 800px;
+  margin: 20px auto;
+  padding: 0 20px;
 }
 
 .ar-camera {
-    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
-    border-radius: 20px;
-    height: 400px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    color: white;
-    font-size: 18px;
-    margin-bottom: 20px;
-    position: relative;
-    overflow: hidden;
-    cursor: pointer;
+  background: linear-gradient(135deg, var(--primary-color), var(--primary-dark));
+  border-radius: 20px;
+  height: 400px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 18px;
+  margin-bottom: 20px;
+  position: relative;
+  overflow: hidden;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  
+  &:hover:not(.active) {
+    transform: translateY(-5px);
+    box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4);
+  }
+  
+  &::before {
+    content: '';
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="white" stroke-width="0.5" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
+  }
+  
+  &.active {
+    background: #222;
     
     &::before {
-        content: '';
-        position: absolute;
-        top: 0;
-        left: 0;
-        right: 0;
-        bottom: 0;
-        background: url('data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><defs><pattern id="grid" width="10" height="10" patternUnits="userSpaceOnUse"><path d="M 10 0 L 0 0 0 10" fill="none" stroke="white" stroke-width="0.5" opacity="0.1"/></pattern></defs><rect width="100" height="100" fill="url(%23grid)"/></svg>');
+      opacity: 0.5;
     }
+  }
 }
 
 .camera-content {
-    text-align: center;
-    z-index: 2;
-    position: relative;
+  text-align: center;
+  z-index: 2;
+  position: relative;
+  padding: 20px;
 }
 
 .camera-icon {
-    font-size: 80px;
-    margin-bottom: 20px;
-    animation: pulse 2s infinite;
+  font-size: 80px;
+  margin-bottom: 20px;
+  animation: pulse 2s infinite;
 }
 
 .camera-desc {
-    font-size: 14px;
-    opacity: 0.8;
-    margin-top: 8px;
+  font-size: 14px;
+  opacity: 0.8;
+  margin-top: 8px;
+}
+
+.ar-preview {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1;
+}
+
+.ar-overlay {
+  position: relative;
+  width: 100%;
+  height: 100%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.scanning-animation {
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  height: 3px;
+  background: var(--accent-color);
+  animation: scan 3s infinite;
+  box-shadow: 0 0 10px var(--accent-color);
+}
+
+.ar-instruction {
+  position: absolute;
+  bottom: 30px;
+  left: 0;
+  right: 0;
+  text-align: center;
+  font-size: 16px;
+  background: rgba(0,0,0,0.7);
+  padding: 10px;
+  border-radius: 20px;
+  margin: 0 40px;
+}
+
+@keyframes scan {
+  0% { top: 0; }
+  50% { top: 100%; }
+  100% { top: 0; }
 }
 
 @keyframes pulse {
-    0%, 100% { transform: scale(1); }
-    50% { transform: scale(1.1); }
+  0%, 100% { transform: scale(1); }
+  50% { transform: scale(1.1); }
 }
 
 .body-settings {
-    background: white;
-    border-radius: 15px;
-    padding: 20px;
-    margin-bottom: 20px;
-    box-shadow: 0 5px 20px rgba(0,0,0,0.1);
+  background: var(--card-bg);
+  border-radius: 15px;
+  padding: 20px;
+  margin-bottom: 20px;
+  box-shadow: 0 5px 20px rgba(0,0,0,0.1);
+  
+  h4 {
+    margin-bottom: 15px;
+    font-size: 16px;
+    color: var(--primary-dark);
+    display: flex;
+    align-items: center;
     
-    h4 {
-        margin-bottom: 15px;
-        font-size: 16px;
+    &::before {
+      content: '👤';
+      margin-right: 8px;
     }
+  }
 }
 
 .settings-grid {
-    display: grid;
-    grid-template-columns: repeat(2, 1fr);
-    gap: 15px;
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 15px;
+  
+  @media (max-width: 600px) {
+    grid-template-columns: 1fr;
+  }
 }
 
 .setting-item {
-    label {
-        display: block;
-        font-size: 14px;
-        color: #666;
-        margin-bottom: 5px;
-    }
-    
-    input[type="range"] {
-        width: 100%;
-    }
-    
-    span {
-        font-size: 12px;
-        color: #999;
-    }
+  label {
+    display: block;
+    font-size: 14px;
+    color: var(--light-text);
+    margin-bottom: 5px;
+    font-weight: 500;
+  }
+  
+  input[type="range"] {
+    width: 100%;
+    margin: 8px 0;
+  }
+  
+  span, .measurement-value {
+    font-size: 14px;
+    color: var(--text-color);
+    font-weight: 500;
+  }
+  
+  &.full-width {
+    grid-column: 1 / -1;
+  }
+}
+
+.body-preview {
+  height: 120px;
+  background: linear-gradient(to right, #f5f5f5, #e0e0e0);
+  border-radius: 10px;
+  position: relative;
+  overflow: hidden;
+  margin-top: 10px;
+}
+
+.body-shape {
+  position: absolute;
+  bottom: 0;
+  left: 50%;
+  transform: translateX(-50%);
+  width: 60px;
+  background: linear-gradient(to bottom, #ff9a9e, #fecfef);
+  border-radius: 30px 30px 0 0;
+  transition: height 0.3s ease;
 }
 
 .scene-selector {
-    background: white;
-    border-radius: 15px;
-    padding: 20px;
-    box-shadow: 0 5px 20px rgba(0,0,0,0.1);
+  background: var(--card-bg);
+  border-radius: 15px;
+  padding: 20px;
+  box-shadow: 0 5px 20px rgba(0,0,0,0.1);
+  
+  h4 {
+    margin-bottom: 15px;
+    font-size: 16px;
+    color: var(--primary-dark);
+    display: flex;
+    align-items: center;
     
-    h4 {
-        margin-bottom: 15px;
-        font-size: 16px;
+    &::before {
+      content: '🌍';
+      margin-right: 8px;
+    }
+    
+    span {
+      margin-left: 8px;
+      font-weight: normal;
+      color: var(--light-text);
     }
+  }
 }
 
 .scenes {
-    display: flex;
-    gap: 10px;
-    overflow-x: auto;
+  display: grid;
+  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+  gap: 12px;
 }
 
 .scene-item {
-    min-width: 80px;
-    height: 80px;
-    border-radius: 12px;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    font-size: 24px;
-    cursor: pointer;
-    transition: all 0.3s ease;
-    background: linear-gradient(45deg, var(--scene-bg-start, #ff9a9e), var(--scene-bg-end, #fecfef));
+  height: 120px;
+  border-radius: 12px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  position: relative;
+  overflow: hidden;
+  border: 2px solid transparent;
+  
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
+  }
+  
+  &.active {
+    border-color: var(--primary-color);
+    box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.3);
     
-    &:hover {
-        transform: scale(1.05);
+    &::after {
+      content: '✓';
+      position: absolute;
+      top: 5px;
+      right: 5px;
+      width: 20px;
+      height: 20px;
+      background: var(--primary-color);
+      color: white;
+      border-radius: 50%;
+      display: flex;
+      align-items: center;
+      justify-content: center;
+      font-size: 12px;
     }
+  }
+}
+
+.scene-icon {
+  font-size: 32px;
+  margin-bottom: 8px;
+}
+
+.scene-name {
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.custom-notification {
+  position: fixed;
+  bottom: 20px;
+  left: 50%;
+  transform: translateX(-50%) translateY(100px);
+  background: rgba(0,0,0,0.8);
+  color: white;
+  padding: 12px 20px;
+  border-radius: 8px;
+  z-index: 1000;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  transition: transform 0.3s ease;
+  
+  strong {
+    font-weight: 500;
+    margin-bottom: 4px;
+  }
+  
+  span {
+    font-size: 14px;
+    opacity: 0.9;
+  }
+  
+  &.show {
+    transform: translateX(-50%) translateY(0);
+  }
+}
+
+// 响应式调整
+@media (max-width: 480px) {
+  .ar-camera {
+    height: 300px;
+  }
+  
+  .scenes {
+    grid-template-columns: repeat(2, 1fr);
+  }
 }

+ 68 - 7
cloth-dit-web/src/modules/cloth/color/pages-fitting-room/pages-fitting-room/pages-fitting-room.ts

@@ -1,8 +1,9 @@
 import { Component } from '@angular/core';
+import { CommonModule } from '@angular/common';
 
 @Component({
   selector: 'app-pages-fitting-room',
-  imports:[],
+  imports: [CommonModule],
   templateUrl: './pages-fitting-room.html',
   styleUrls: ['./pages-fitting-room.scss']
 })
@@ -10,7 +11,14 @@ export class PagesFittingRoom {
   // 身材参数
   height = 170;
   weight = 60;
-  
+  selectedScene: keyof typeof this.sceneNames | null = null;
+  isARCameraActive = false;
+  bodyMeasurements = {
+    chest: 90,
+    waist: 70,
+    hips: 95
+  };
+
   // 场景映射
   sceneNames = {
     'office': '办公场景',
@@ -27,31 +35,84 @@ export class PagesFittingRoom {
     'casual': { '--scene-bg-start': '#ffeaa7', '--scene-bg-end': '#fab1a0' }
   };
 
+  // 获取场景图标
+  getSceneIcon(scene: keyof typeof this.sceneNames): string {
+    const icons = {
+      'office': '🏢',
+      'date': '💕',
+      'party': '🎉',
+      'casual': '☕'
+    };
+    return icons[scene];
+  }
+
+  // 获取场景键数组
+  get sceneKeys(): (keyof typeof this.sceneNames)[] {
+    return Object.keys(this.sceneNames) as (keyof typeof this.sceneNames)[];
+  }
+
   // 更新身高
   updateHeight(event: Event) {
     this.height = parseInt((event.target as HTMLInputElement).value);
+    this.updateBodyMeasurements();
   }
 
   // 更新体重
   updateWeight(event: Event) {
     this.weight = parseInt((event.target as HTMLInputElement).value);
+    this.updateBodyMeasurements();
+  }
+
+  // 根据身高体重自动计算其他身体参数
+  updateBodyMeasurements() {
+    const heightRatio = this.height / 170;
+    const weightRatio = this.weight / 60;
+    
+    this.bodyMeasurements = {
+      chest: Math.round(90 * heightRatio * (0.7 + 0.3 * weightRatio)),
+      waist: Math.round(70 * heightRatio * (0.6 + 0.4 * weightRatio)),
+      hips: Math.round(95 * heightRatio * (0.8 + 0.2 * weightRatio))
+    };
   }
 
   // 选择场景
   selectScene(scene: keyof typeof this.sceneNames) {
-    console.log(`已切换到${this.sceneNames[scene]}`);
+    this.selectedScene = scene;
     this.showNotification('场景切换', `已切换到${this.sceneNames[scene]}`);
   }
 
   // 启动AR试衣
   startAR() {
-    console.log('开启AR试穿');
-    this.showNotification('AR试穿', '正在启动相机...');
+    this.isARCameraActive = true;
+    setTimeout(() => {
+      this.showNotification('AR试穿', '相机已就绪,请将摄像头对准全身');
+    }, 1500);
+  }
+
+  // 关闭AR试衣
+  stopAR() {
+    this.isARCameraActive = false;
   }
 
   // 显示通知
   showNotification(title: string, message: string) {
-    // 在实际项目中,这里可以使用Angular Material的Snackbar或Toast服务
-    console.log(`[${title}] ${message}`);
+    const notification = document.createElement('div');
+    notification.className = 'custom-notification';
+    notification.innerHTML = `
+      <strong>${title}</strong>
+      <span>${message}</span>
+    `;
+    document.body.appendChild(notification);
+    
+    setTimeout(() => {
+      notification.classList.add('show');
+    }, 10);
+    
+    setTimeout(() => {
+      notification.classList.remove('show');
+      setTimeout(() => {
+        document.body.removeChild(notification);
+      }, 300);
+    }, 3000);
   }
 }