Răsfoiți Sursa

feat.reserve

0235615 6 zile în urmă
părinte
comite
0b09e7914b
30 a modificat fișierele cu 819 adăugiri și 9 ștergeri
  1. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/bottom-nav/bottom-nav.component.html
  2. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/bottom-nav/bottom-nav.component.scss
  3. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/bottom-nav/bottom-nav.component.spec.ts
  4. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/bottom-nav/bottom-nav.component.ts
  5. 21 0
      menu-web/src/modules/food/mobile/page-reserve/components/header/header.component.html
  6. 77 0
      menu-web/src/modules/food/mobile/page-reserve/components/header/header.component.scss
  7. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/header/header.component.spec.ts
  8. 25 0
      menu-web/src/modules/food/mobile/page-reserve/components/header/header.component.ts
  9. 22 0
      menu-web/src/modules/food/mobile/page-reserve/components/menu-card/menu-card.component.html
  10. 130 0
      menu-web/src/modules/food/mobile/page-reserve/components/menu-card/menu-card.component.scss
  11. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/menu-card/menu-card.component.spec.ts
  12. 35 0
      menu-web/src/modules/food/mobile/page-reserve/components/menu-card/menu-card.component.ts
  13. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/sorting-tabs/sorting-tabs.component.html
  14. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/sorting-tabs/sorting-tabs.component.scss
  15. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/sorting-tabs/sorting-tabs.component.spec.ts
  16. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/sorting-tabs/sorting-tabs.component.ts
  17. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/stats-section/stats-section.component.html
  18. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/stats-section/stats-section.component.scss
  19. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/stats-section/stats-section.component.spec.ts
  20. 0 0
      menu-web/src/modules/food/mobile/page-reserve/components/stats-section/stats-section.component.ts
  21. 10 0
      menu-web/src/modules/food/mobile/page-reserve/models/culture-info.model.ts
  22. 10 0
      menu-web/src/modules/food/mobile/page-reserve/models/menu-item.model.ts
  23. 72 1
      menu-web/src/modules/food/mobile/page-reserve/page-reserve.html
  24. 189 0
      menu-web/src/modules/food/mobile/page-reserve/page-reserve.scss
  25. 63 8
      menu-web/src/modules/food/mobile/page-reserve/page-reserve.ts
  26. 54 0
      menu-web/src/modules/food/mobile/page-reserve/services/culture.service.ts
  27. 55 0
      menu-web/src/modules/food/mobile/page-reserve/services/menu.service.ts
  28. 0 0
      menu-web/src/modules/food/mobile/page-reserve/shared/chart/chart.directive.spec.ts
  29. 34 0
      menu-web/src/modules/food/mobile/page-reserve/shared/chart/chart.directive.ts
  30. 22 0
      menu-web/src/styles.scss

+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/bottom-nav/bottom-nav.component.html


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/bottom-nav/bottom-nav.component.scss


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/bottom-nav/bottom-nav.component.spec.ts


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/bottom-nav/bottom-nav.component.ts


+ 21 - 0
menu-web/src/modules/food/mobile/page-reserve/components/header/header.component.html

@@ -0,0 +1,21 @@
+<header>
+  <div class="header-container">
+    <div class="logo">
+      <fa-icon [icon]="faCat"></fa-icon>
+      <span>点菜喵 · 发现</span>
+    </div>
+    <div class="user-actions">
+      <button class="icon-btn">
+        <fa-icon [icon]="faSearch"></fa-icon>
+      </button>
+      <button class="icon-btn">
+        <fa-icon [icon]="faBell"></fa-icon>
+      </button>
+    </div>
+  </div>
+
+  <div class="search-bar">
+    <fa-icon [icon]="faSearch"></fa-icon>
+    <input type="text" placeholder="搜索菜品、典故、文化..." (input)="onSearch($event)">
+  </div>
+</header>

+ 77 - 0
menu-web/src/modules/food/mobile/page-reserve/components/header/header.component.scss

@@ -0,0 +1,77 @@
+@import 'src/styles/variables';
+
+header {
+  background: $white;
+  padding: 15px 0 10px;
+  position: sticky;
+  top: 0;
+  z-index: 100;
+  box-shadow: $card-shadow;
+}
+
+.header-container {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 0 15px;
+}
+
+.logo {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 18px;
+  font-weight: 700;
+  color: $primary;
+  
+  fa-icon {
+    font-size: 22px;
+  }
+}
+
+.user-actions {
+  display: flex;
+  gap: 15px;
+}
+
+.icon-btn {
+  width: 36px;
+  height: 36px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: $light-gray;
+  color: $gray;
+  font-size: 16px;
+  border: none;
+  transition: $transition;
+  cursor: pointer;
+  
+  &:hover {
+    background: $primary;
+    color: $white;
+  }
+}
+
+.search-bar {
+  background: $light-gray;
+  border-radius: 25px;
+  padding: 10px 20px;
+  margin: 12px 15px 0;
+  display: flex;
+  align-items: center;
+  
+  fa-icon {
+    color: $gray;
+    margin-right: 10px;
+  }
+  
+  input {
+    border: none;
+    background: transparent;
+    outline: none;
+    width: 100%;
+    font-size: 14px;
+  }
+}

+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/header/header.component.spec.ts


+ 25 - 0
menu-web/src/modules/food/mobile/page-reserve/components/header/header.component.ts

@@ -0,0 +1,25 @@
+import { Component, EventEmitter, Output } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { faSearch, faBell, faCat } from '@fortawesome/free-solid-svg-icons';
+
+@Component({
+  selector: 'app-header',
+  standalone: true,
+  imports: [CommonModule, FontAwesomeModule],
+  templateUrl: './header.component.html',
+  styleUrls: ['./header.component.scss']
+})
+export class HeaderComponent {
+  @Output() search = new EventEmitter<string>();
+  
+  // Font Awesome 图标
+  faSearch = faSearch;
+  faBell = faBell;
+  faCat = faCat;
+  
+  onSearch(event: Event) {
+    const input = event.target as HTMLInputElement;
+    this.search.emit(input.value);
+  }
+}

+ 22 - 0
menu-web/src/modules/food/mobile/page-reserve/components/menu-card/menu-card.component.html

@@ -0,0 +1,22 @@
+<div class="menu-card">
+  <div class="menu-img" [style.background]="getGradient()">
+    <div class="menu-badge" *ngIf="menuItem.badge">{{ menuItem.badge }}</div>
+  </div>
+  <div class="menu-info">
+    <div class="menu-name">
+      <h3>{{ menuItem.name }}</h3>
+      <div class="menu-price">¥{{ menuItem.price }}</div>
+    </div>
+    <div class="menu-desc">{{ menuItem.description }}</div>
+    <div class="menu-stats">
+      <span><fa-icon [icon]="faStar"></fa-icon> {{ menuItem.rating }}</span>
+      <span><fa-icon [icon]="faClock"></fa-icon> {{ menuItem.time }}min</span>
+    </div>
+    <div class="menu-actions">
+      <button class="story-btn" (click)="viewCulture.emit(menuItem)">文化解读</button>
+      <button class="add-btn" (click)="onAddToCart()" [style.background]="isAdded ? '#4ecdc4' : ''">
+        <fa-icon [icon]="isAdded ? faCheck : faPlus"></fa-icon>
+      </button>
+    </div>
+  </div>
+</div>

+ 130 - 0
menu-web/src/modules/food/mobile/page-reserve/components/menu-card/menu-card.component.scss

@@ -0,0 +1,130 @@
+@import 'src/styles/variables';
+
+.menu-card {
+  background: $white;
+  border-radius: 15px;
+  overflow: hidden;
+  box-shadow: $card-shadow;
+  transition: $transition;
+  position: relative;
+  
+  &:hover {
+    transform: translateY(-5px);
+    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.12);
+    
+    .menu-img {
+      animation: pulse 1s ease-in-out;
+    }
+  }
+}
+
+.menu-img {
+  width: 100%;
+  height: 140px;
+  position: relative;
+}
+
+.menu-badge {
+  position: absolute;
+  top: 10px;
+  right: 10px;
+  background: $accent;
+  color: $white;
+  padding: 3px 10px;
+  border-radius: 20px;
+  font-size: 12px;
+  font-weight: 600;
+  z-index: 2;
+}
+
+.menu-info {
+  padding: 12px;
+}
+
+.menu-name {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 8px;
+  
+  h3 {
+    font-size: 16px;
+  }
+}
+
+.menu-price {
+  color: $primary;
+  font-weight: 600;
+}
+
+.menu-desc {
+  font-size: 12px;
+  color: $gray;
+  line-height: 1.5;
+  height: 36px;
+  overflow: hidden;
+  display: -webkit-box;
+  -webkit-line-clamp: 2;
+  -webkit-box-orient: vertical;
+}
+
+.menu-stats {
+  display: flex;
+  justify-content: space-between;
+  font-size: 11px;
+  color: $gray;
+  margin-top: 8px;
+  
+  fa-icon {
+    margin-right: 3px;
+  }
+}
+
+.menu-actions {
+  display: flex;
+  justify-content: space-between;
+  margin-top: 10px;
+}
+
+.story-btn {
+  width: 100%;
+  padding: 6px 0;
+  border-radius: 20px;
+  text-align: center;
+  font-size: 12px;
+  cursor: pointer;
+  transition: $transition;
+  background: rgba(78, 205, 196, 0.1);
+  color: $secondary;
+  border: none;
+  
+  &:hover {
+    background: rgba(78, 205, 196, 0.2);
+  }
+}
+
+.add-btn {
+  width: 32px;
+  height: 32px;
+  background: $primary;
+  color: $white;
+  border: none;
+  border-radius: 50%;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: $transition;
+  margin-left: 10px;
+  cursor: pointer;
+  
+  &:hover {
+    background: $primary-dark;
+    transform: scale(1.1);
+  }
+}
+
+@keyframes pulse {
+  0% { transform: scale(1); }
+  50% { transform: scale(1.05); }
+  100% { transform: scale(1); }
+}

+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/menu-card/menu-card.component.spec.ts


+ 35 - 0
menu-web/src/modules/food/mobile/page-reserve/components/menu-card/menu-card.component.ts

@@ -0,0 +1,35 @@
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
+import { faStar, faClock, faPlus, faCheck } from '@fortawesome/free-solid-svg-icons';
+import { MenuItem } from '../../models/menu-item.model';
+
+@Component({
+  selector: 'app-menu-card',
+  standalone: true,
+  imports: [CommonModule, FontAwesomeModule],
+  templateUrl: './menu-card.component.html',
+  styleUrls: ['./menu-card.component.scss']
+})
+export class MenuCardComponent {
+  @Input() menuItem!: MenuItem;
+  @Output() viewCulture = new EventEmitter<MenuItem>();
+  @Output() addToCart = new EventEmitter<MenuItem>();
+  
+  // Font Awesome 图标
+  faStar = faStar;
+  faClock = faClock;
+  faPlus = faPlus;
+  faCheck = faCheck;
+  
+  isAdded = false;
+  
+  onAddToCart() {
+    this.isAdded = true;
+    this.addToCart.emit(this.menuItem);
+    
+    setTimeout(() => {
+      this.isAdded = false;
+    }, 1500);
+  }
+}

+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/sorting-tabs/sorting-tabs.component.html


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/sorting-tabs/sorting-tabs.component.scss


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/sorting-tabs/sorting-tabs.component.spec.ts


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/sorting-tabs/sorting-tabs.component.ts


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/stats-section/stats-section.component.html


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/stats-section/stats-section.component.scss


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/stats-section/stats-section.component.spec.ts


+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/components/stats-section/stats-section.component.ts


+ 10 - 0
menu-web/src/modules/food/mobile/page-reserve/models/culture-info.model.ts

@@ -0,0 +1,10 @@
+export interface CultureInfo {
+  id: number;
+  name: string;
+  origin: string;
+  era: string;
+  history: string;
+  culture: string;
+  cooking: string;
+  health: string;
+}

+ 10 - 0
menu-web/src/modules/food/mobile/page-reserve/models/menu-item.model.ts

@@ -0,0 +1,10 @@
+export interface MenuItem {
+  id: number;
+  name: string;
+  price: number;
+  description: string;
+  rating: number;
+  time: number;
+  badge?: string;
+  colors: [string, string];
+}

+ 72 - 1
menu-web/src/modules/food/mobile/page-reserve/page-reserve.html

@@ -1 +1,72 @@
-<p>page-reserve works!</p>
+<div class="container">
+  <!-- Header -->
+  <app-header (search)="onSearch($event)"></app-header>
+
+  <!-- Sorting Tabs -->
+  <app-sorting-tabs 
+    [activeTab]="activeSort" 
+    (sortChange)="onSortChange($event)">
+  </app-sorting-tabs>
+
+  <!-- Stats Section -->
+  <div class="section">
+    <div class="section-title">
+      <h2>菜品健康指数</h2>
+      <a href="#" class="more">查看详情</a>
+    </div>
+    
+    <app-stats-section></app-stats-section>
+  </div>
+
+  <!-- Menu Grid -->
+  <div class="section">
+    <div class="section-title">
+      <h2>发现美味</h2>
+      <a href="#" class="more">全部菜品</a>
+    </div>
+    
+    <div class="menu-grid">
+      <app-menu-card 
+        *ngFor="let item of menuItems"
+        [menuItem]="item"
+        (viewCulture)="onViewCulture($event)"
+        (addToCart)="onAddToCart($event)">
+      </app-menu-card>
+    </div>
+  </div>
+</div>
+
+<!-- Culture Modal -->
+<div class="modal-overlay" [class.active]="showCultureModal" (click)="closeModal()">
+  <div class="culture-modal" (click)="$event.stopPropagation()">
+    <div class="modal-header">
+      <div class="modal-close" (click)="closeModal()">
+        <i class="fas fa-times"></i>
+      </div>
+      <h3 class="modal-title">{{ currentCulture?.name }}</h3>
+      <div class="modal-subtitle">{{ currentCulture?.origin }} · {{ currentCulture?.era }}</div>
+    </div>
+    <div class="modal-content">
+      <div class="modal-ar">
+        <div class="ar-animation"></div>
+        <div>AR文化展示</div>
+      </div>
+      <div class="modal-text">
+        <h4>历史典故</h4>
+        <p>{{ currentCulture?.history }}</p>
+        
+        <h4>文化内涵</h4>
+        <p>{{ currentCulture?.culture }}</p>
+        
+        <h4>烹饪技艺</h4>
+        <p>{{ currentCulture?.cooking }}</p>
+        
+        <h4>健康指数</h4>
+        <p>{{ currentCulture?.health }}</p>
+      </div>
+    </div>
+  </div>
+</div>
+
+<!-- Bottom Navigation -->
+<app-bottom-nav></app-bottom-nav>

+ 189 - 0
menu-web/src/modules/food/mobile/page-reserve/page-reserve.scss

@@ -0,0 +1,189 @@
+@import 'src/styles/variables';
+
+.container {
+  width: 100%;
+  max-width: 480px;
+  margin: 0 auto;
+  padding: 0 15px;
+  padding-bottom: 80px;
+}
+
+body {
+  background: linear-gradient(135deg, #fff5f5 0%, #fff0f0 100%);
+  color: var(--dark);
+  line-height: 1.6;
+}
+
+.section {
+  margin-top: 20px;
+}
+
+.section-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin: 0 15px 15px;
+  
+  h2 {
+    font-size: 18px;
+    color: $dark;
+    position: relative;
+    padding-left: 12px;
+    
+    &::before {
+      content: '';
+      position: absolute;
+      left: 0;
+      top: 50%;
+      transform: translateY(-50%);
+      width: 4px;
+      height: 16px;
+      background: $primary;
+      border-radius: 2px;
+    }
+  }
+  
+  .more {
+    color: $gray;
+    font-size: 13px;
+    text-decoration: none;
+  }
+}
+
+.menu-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 15px;
+  margin: 0 15px;
+  
+  @media (max-width: 480px) {
+    grid-template-columns: 1fr;
+  }
+}
+
+/* Modal Styles */
+.modal-overlay {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100%;
+  height: 100%;
+  background: rgba(0, 0, 0, 0.7);
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  z-index: 1000;
+  opacity: 0;
+  visibility: hidden;
+  transition: $transition;
+  
+  &.active {
+    opacity: 1;
+    visibility: visible;
+    
+    .culture-modal {
+      transform: translateY(0);
+      opacity: 1;
+    }
+  }
+}
+
+.culture-modal {
+  width: 90%;
+  max-width: 400px;
+  background: $white;
+  border-radius: 20px;
+  overflow: hidden;
+  transform: translateY(30px);
+  transition: $transition;
+  opacity: 0;
+}
+
+.modal-header {
+  background: linear-gradient(45deg, $primary, $primary-light);
+  padding: 20px;
+  color: $white;
+  text-align: center;
+  position: relative;
+}
+
+.modal-close {
+  position: absolute;
+  top: 15px;
+  right: 15px;
+  background: rgba(255, 255, 255, 0.2);
+  width: 30px;
+  height: 30px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  cursor: pointer;
+}
+
+.modal-title {
+  font-size: 20px;
+  margin-bottom: 5px;
+}
+
+.modal-subtitle {
+  font-size: 14px;
+  opacity: 0.9;
+}
+
+.modal-content {
+  padding: 20px;
+  max-height: 60vh;
+  overflow-y: auto;
+}
+
+.modal-ar {
+  width: 100%;
+  height: 200px;
+  background: linear-gradient(45deg, #a1c4fd, #c2e9fb);
+  border-radius: 12px;
+  margin-bottom: 20px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: $white;
+  font-size: 20px;
+  font-weight: bold;
+  position: relative;
+  overflow: hidden;
+}
+
+.ar-animation {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  background: url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="10" fill="%23ff6b6b" opacity="0.7"><animate attributeName="r" from="10" to="40" dur="2s" repeatCount="indefinite"/><animate attributeName="opacity" from="0.7" to="0" dur="2s" repeatCount="indefinite"/></circle></svg>');
+  background-size: contain;
+  background-position: center;
+  background-repeat: no-repeat;
+  animation: pulse 2s infinite;
+}
+
+.modal-text {
+  font-size: 14px;
+  line-height: 1.8;
+  color: $gray;
+  
+  h4 {
+    color: $primary;
+    margin: 15px 0 8px;
+  }
+}
+
+/* Animations */
+@keyframes float {
+  0% { transform: translateY(0); }
+  50% { transform: translateY(-10px); }
+  100% { transform: translateY(0); }
+}
+
+@keyframes pulse {
+  0% { transform: scale(1); }
+  50% { transform: scale(1.05); }
+  100% { transform: scale(1); }
+}

+ 63 - 8
menu-web/src/modules/food/mobile/page-reserve/page-reserve.ts

@@ -1,11 +1,66 @@
-import { Component } from '@angular/core';
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { HeaderComponent } from '../../components/header/header.component';
+import { SortingTabsComponent } from '../../components/sorting-tabs/sorting-tabs.component';
+import { StatsSectionComponent } from '../../components/stats-section/stats-section.component';
+import { MenuCardComponent } from '../../components/menu-card/menu-card.component';
+import { BottomNavComponent } from '../../components/bottom-nav/bottom-nav.component';
+import { MenuService } from '../../services/menu.service';
+import { CultureService } from '../../services/culture.service';
+import { MenuItem } from '../../models/menu-item.model';
+import { CultureInfo } from '../../models/culture-info.model';
+import { ChartDirective } from '../../shared/chart/chart.directive';
 
 @Component({
-  selector: 'app-page-reserve',
-  imports: [],
-  templateUrl: './page-reserve.html',
-  styleUrl: './page-reserve.scss'
+  selector: 'app-discover',
+  standalone: true,
+  imports: [
+    CommonModule,
+    HeaderComponent,
+    SortingTabsComponent,
+    StatsSectionComponent,
+    MenuCardComponent,
+    BottomNavComponent,
+    ChartDirective
+  ],
+  templateUrl: './discover.component.html',
+  styleUrls: ['./discover.component.scss']
 })
-export class PageReserve {
-
-}
+export class DiscoverComponent implements OnInit {
+  menuItems: MenuItem[] = [];
+  activeSort: string = 'recommend';
+  showCultureModal = false;
+  currentCulture?: CultureInfo;
+  
+  constructor(
+    private menuService: MenuService,
+    private cultureService: CultureService
+  ) {}
+  
+  ngOnInit(): void {
+    this.loadMenuItems();
+  }
+  
+  loadMenuItems(): void {
+    this.menuItems = this.menuService.getMenuItems(this.activeSort);
+  }
+  
+  onSortChange(sortType: string): void {
+    this.activeSort = sortType;
+    this.loadMenuItems();
+  }
+  
+  onViewCulture(item: MenuItem): void {
+    this.currentCulture = this.cultureService.getCultureInfo(item.id);
+    this.showCultureModal = true;
+  }
+  
+  onAddToCart(item: MenuItem): void {
+    // 这里可以添加购物车逻辑
+    console.log('Added to cart:', item);
+  }
+  
+  closeModal(): void {
+    this.showCultureModal = false;
+  }
+}

+ 54 - 0
menu-web/src/modules/food/mobile/page-reserve/services/culture.service.ts

@@ -0,0 +1,54 @@
+import { Injectable } from '@angular/core';
+import { CultureInfo } from '../models/culture-info.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class CultureService {
+  private cultureData: { [key: number]: CultureInfo } = {
+    1: {
+      id: 1,
+      name: '西湖醋鱼',
+      origin: '杭州传统名菜',
+      era: '宋代起源',
+      history: '西湖醋鱼起源于南宋时期,传说与著名诗人苏轼有关。苏轼任杭州知州时,疏浚西湖,修筑苏堤。当地百姓为感谢他,特制此鱼相赠。因苏轼号东坡,故又名"东坡鱼"。',
+      culture: '西湖醋鱼体现了杭州菜"清淡适中、精致典雅"的特点。其酸甜口味象征着杭州人温和包容的性格,整鱼烹制则寓意"年年有余"的传统祝福。',
+      cooking: '选用西湖草鱼,经饿养1-2天去除土腥味。烹饪时讲究"七刀八片",沸水汆熟后淋上糖醋芡汁。成菜色泽红亮,肉质鲜嫩,酸甜适口。',
+      health: '热量:198千卡/100g | 蛋白质:18.6g | 脂肪:8.2g | 推荐指数:★★★★☆'
+    },
+    2: {
+      id: 2,
+      name: '清蒸鲈鱼',
+      origin: '广东',
+      era: '清代起源',
+      history: '清蒸鲈鱼是粤菜代表作,最早可追溯至清代。因鲈鱼秋季最为肥美,成为文人墨客吟咏的对象,清代诗人袁枚在《随园食单》中详细记载了清蒸鲈鱼的烹饪方法。',
+      culture: '清蒸技法体现了粤菜"清中求鲜、淡中求美"的饮食理念,追求食材本味,符合中医养生之道。',
+      cooking: '选用一斤半左右的鲜活鲈鱼,去鳞洗净后,鱼身切斜刀,用盐、料酒腌制10分钟。配以姜片、葱段,大火蒸8-10分钟,淋上蒸鱼豉油和热油。',
+      health: '热量:132千卡/100g | 蛋白质:19.8g | 脂肪:4.3g | 推荐指数:★★★★★'
+    },
+    3: {
+      id: 3,
+      name: '宫保鸡丁',
+      origin: '四川',
+      era: '清代起源',
+      history: '宫保鸡丁创制于清代,由四川总督丁宝桢所创。丁宝桢曾被封为"太子少保",人称"丁宫保",此菜因而得名。原为贵州菜,后传入四川并改良。',
+      culture: '宫保鸡丁体现了川菜"麻辣鲜香、味型多变"的特点,花生米象征"锦上添花",整道菜红而不辣、辣而不猛,彰显中庸之道。',
+      cooking: '选用鸡腿肉切丁,配以花生米、干辣椒和花椒。关键在于"小荔枝口"调味,酸甜微辣。急火快炒,保持鸡肉鲜嫩,花生酥脆。',
+      health: '热量:215千卡/100g | 蛋白质:17.2g | 脂肪:12.8g | 推荐指数:★★★☆☆'
+    },
+    4: {
+      id: 4,
+      name: '上汤娃娃菜',
+      origin: '广东',
+      era: '清末起源',
+      history: '上汤娃娃菜是粤菜经典素菜,起源于清末广州。当时西关富商讲究饮食养生,厨师用高汤烹制时令蔬菜,既保持营养又提升鲜味,逐渐成为宴席必备菜品。',
+      culture: '此菜体现了粤菜"食不厌精、脍不厌细"的烹饪理念,以及"不时不食"的饮食智慧。娃娃菜形似玉如意,象征吉祥如意。',
+      cooking: '精选娃娃菜心,用鸡汤、火腿、皮蛋熬制上汤。菜心焯水后浸入上汤慢煨,使其充分吸收汤汁精华,保持脆嫩口感。',
+      health: '热量:78千卡/100g | 蛋白质:2.8g | 脂肪:3.2g | 推荐指数:★★★★★'
+    }
+  };
+
+  getCultureInfo(dishId: number): CultureInfo {
+    return this.cultureData[dishId] || null;
+  }
+}

+ 55 - 0
menu-web/src/modules/food/mobile/page-reserve/services/menu.service.ts

@@ -0,0 +1,55 @@
+import { Injectable } from '@angular/core';
+import { MenuItem } from '../models/menu-item.model';
+
+@Injectable({
+  providedIn: 'root'
+})
+export class MenuService {
+  private menuItems: MenuItem[] = [
+    {
+      id: 1,
+      name: '西湖醋鱼',
+      price: 88,
+      description: '杭州传统名菜,鱼肉鲜嫩,酸甜可口',
+      rating: 4.9,
+      time: 15,
+      badge: '掌柜推荐',
+      colors: ['#ff9a9e', '#fad0c4']
+    },
+    {
+      id: 2,
+      name: '清蒸鲈鱼',
+      price: 78,
+      description: '粤菜经典,原汁原味,肉质鲜嫩',
+      rating: 4.8,
+      time: 18,
+      badge: '健康优选',
+      colors: ['#a1c4fd', '#c2e9fb']
+    },
+    {
+      id: 3,
+      name: '宫保鸡丁',
+      price: 42,
+      description: '川菜代表,麻辣鲜香,鸡肉滑嫩',
+      rating: 4.7,
+      time: 8,
+      badge: '快速出餐',
+      colors: ['#ffecd2', '#fcb69f']
+    },
+    {
+      id: 4,
+      name: '上汤娃娃菜',
+      price: 32,
+      description: '健康素菜,汤鲜味美,营养丰富',
+      rating: 4.6,
+      time: 10,
+      badge: '健康优选',
+      colors: ['#84fab0', '#8fd3f4']
+    }
+  ];
+
+  getMenuItems(sortType: string): MenuItem[] {
+    // 这里可以根据不同的排序类型返回不同的数据
+    return [...this.menuItems];
+  }
+}

+ 0 - 0
menu-web/src/modules/food/mobile/page-reserve/shared/chart/chart.directive.spec.ts


+ 34 - 0
menu-web/src/modules/food/mobile/page-reserve/shared/chart/chart.directive.ts

@@ -0,0 +1,34 @@
+import { Directive, ElementRef, Input, OnInit } from '@angular/core';
+import { Chart, ChartType, ChartData, ChartOptions } from 'chart.js';
+
+@Directive({
+  selector: '[appChart]',
+  standalone: true
+})
+export class ChartDirective implements OnInit {
+  @Input() type: ChartType = 'bar';
+  @Input() data: ChartData;
+  @Input() options: ChartOptions;
+  
+  private chart: Chart;
+  
+  constructor(private el: ElementRef) {}
+  
+  ngOnInit(): void {
+    this.createChart();
+  }
+  
+  private createChart(): void {
+    this.chart = new Chart(this.el.nativeElement, {
+      type: this.type,
+      data: this.data,
+      options: this.options
+    });
+  }
+  
+  ngOnDestroy(): void {
+    if (this.chart) {
+      this.chart.destroy();
+    }
+  }
+}

+ 22 - 0
menu-web/src/styles.scss

@@ -72,3 +72,25 @@ body {
     grid-template-columns: 1fr !important;
   }
 }
+/* 全局样式重置 */
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
+  -webkit-tap-highlight-color: transparent;
+}
+
+body {
+  background: linear-gradient(135deg, #fff5f5 0%, #fff0f0 100%);
+  color: var(--dark);
+  line-height: 1.6;
+}
+
+/* 动画 */
+@keyframes fadeInOut {
+  0% { opacity: 0; transform: translate(-50%, -20px); }
+  20% { opacity: 1; transform: translate(-50%, 0); }
+  80% { opacity: 1; transform: translate(-50%, 0); }
+  100% { opacity: 0; transform: translate(-50%, -20px); }
+}