Selaa lähdekoodia

feat : home-tab

0235694 6 päivää sitten
vanhempi
commit
5be9250f5a

+ 35 - 4
picture-web/package-lock.json

@@ -8,14 +8,17 @@
       "name": "picture-web",
       "version": "0.0.0",
       "dependencies": {
-        "@angular/common": "^20.0.0",
+        "@angular/cdk": "^20.0.4",
+        "@angular/common": "^20.0.5",
         "@angular/compiler": "^20.0.0",
-        "@angular/core": "^20.0.0",
-        "@angular/forms": "^20.0.0",
+        "@angular/core": "^20.0.5",
+        "@angular/forms": "^20.0.5",
         "@angular/material": "^20.0.4",
         "@angular/platform-browser": "^20.0.0",
         "@angular/router": "^20.0.0",
+        "@types/swiper": "^6.0.0",
         "rxjs": "~7.8.0",
+        "swiper": "^11.2.10",
         "tslib": "^2.3.0",
         "zone.js": "~0.15.0"
       },
@@ -214,7 +217,6 @@
       "resolved": "https://registry.npmmirror.com/@angular/cdk/-/cdk-20.0.4.tgz",
       "integrity": "sha512-NCUuw0qQXwawLsT14JHApNB9or3XGs7D1pWXlOIix/fKqzHVfi4un9xHmpjH2Q1uCiwonuak7fDof8B+IXhbug==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "parse5": "^7.1.2",
         "tslib": "^2.3.0"
@@ -3300,6 +3302,16 @@
         "undici-types": "~7.8.0"
       }
     },
+    "node_modules/@types/swiper": {
+      "version": "6.0.0",
+      "resolved": "https://registry.npmmirror.com/@types/swiper/-/swiper-6.0.0.tgz",
+      "integrity": "sha512-QPZRgxZ+ivXXtzV43B3LxpXUIC7FE/EoKM+rtxngmgt2M7eeUYypZhyqZD8UxJtlBcUDw/ATGoVeSNpvBBrz2w==",
+      "deprecated": "This is a stub types definition. swiper provides its own type definitions, so you do not need this installed.",
+      "license": "MIT",
+      "dependencies": {
+        "swiper": "*"
+      }
+    },
     "node_modules/@vitejs/plugin-basic-ssl": {
       "version": "2.0.0",
       "resolved": "https://registry.npmmirror.com/@vitejs/plugin-basic-ssl/-/plugin-basic-ssl-2.0.0.tgz",
@@ -8122,6 +8134,25 @@
         "url": "https://github.com/sponsors/ljharb"
       }
     },
+    "node_modules/swiper": {
+      "version": "11.2.10",
+      "resolved": "https://registry.npmmirror.com/swiper/-/swiper-11.2.10.tgz",
+      "integrity": "sha512-RMeVUUjTQH+6N3ckimK93oxz6Sn5la4aDlgPzB+rBrG/smPdCTicXyhxa+woIpopz+jewEloiEE3lKo1h9w2YQ==",
+      "funding": [
+        {
+          "type": "patreon",
+          "url": "https://www.patreon.com/swiperjs"
+        },
+        {
+          "type": "open_collective",
+          "url": "http://opencollective.com/swiper"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">= 4.7.0"
+      }
+    },
     "node_modules/tar": {
       "version": "6.2.1",
       "resolved": "https://registry.npmmirror.com/tar/-/tar-6.2.1.tgz",

+ 6 - 3
picture-web/package.json

@@ -20,14 +20,17 @@
   },
   "private": true,
   "dependencies": {
-    "@angular/common": "^20.0.0",
+    "@angular/cdk": "^20.0.4",
+    "@angular/common": "^20.0.5",
     "@angular/compiler": "^20.0.0",
-    "@angular/core": "^20.0.0",
-    "@angular/forms": "^20.0.0",
+    "@angular/core": "^20.0.5",
+    "@angular/forms": "^20.0.5",
     "@angular/material": "^20.0.4",
     "@angular/platform-browser": "^20.0.0",
     "@angular/router": "^20.0.0",
+    "@types/swiper": "^6.0.0",
     "rxjs": "~7.8.0",
+    "swiper": "^11.2.10",
     "tslib": "^2.3.0",
     "zone.js": "~0.15.0"
   },

+ 1 - 0
picture-web/src/index.html

@@ -6,6 +6,7 @@
   <base href="/">
   <meta name="viewport" content="width=device-width, initial-scale=1">
   <link rel="icon" type="image/x-icon" href="favicon.ico">
+  <link href="https://cdn.bootcdn.net/ajax/libs/bootstrap-icons/1.11.3/font/bootstrap-icons.css" rel="stylesheet">
 </head>
 <body>
   <app-root></app-root>

+ 182 - 1
picture-web/src/modules/picture/home-tab/home-tab.html

@@ -1 +1,182 @@
-<p>首页</p>
+<!-- 顶部导航栏 -->
+<header class="header">
+  <div class="logo">播菜汪</div>
+  <div class="nav-icons">
+    <i class="bi bi-heart"></i>
+    <i class="bi bi-bell"></i>
+    <i class="bi bi-person"></i>
+  </div>
+</header>
+
+<div class="container">
+  <!-- 搜索区域 -->
+  <section class="search-section">
+    <div class="search-bar">
+      <i class="bi bi-search search-icon"></i>
+      <input type="text" class="search-input" placeholder="请输入关键词">
+    </div>
+    
+    <div class="hot-search">
+      <h3>热门搜索</h3>
+      <div class="hot-tags">
+        <div class="hot-tag" *ngFor="let tag of hotTags">{{ tag }}</div>
+      </div>
+    </div>
+  </section>
+  
+  <!-- 智能推荐 -->
+  <section class="recommend-section">
+    <div class="section-title">
+      <h2>智能推荐</h2>
+      <a href="#">更多推荐</a>
+    </div>
+    
+    <div class="swiper-container">
+    <div class="swiper-wrapper">
+      <div class="swiper-slide">
+        <img src="https://img.freepik.com/free-photo/top-view-table-full-delicious-food-composition_23-2149141352.jpg" alt="推荐菜品">
+        <div class="slide-content">
+          <h3>川味水煮鱼</h3>
+          <p>麻辣鲜香,鱼肉嫩滑,川菜经典</p>
+        </div>
+      </div>
+      <div class="swiper-slide">
+        <img src="https://img.freepik.com/free-photo/flat-lay-table-full-delicious-food_23-2149141308.jpg" alt="推荐菜品">
+        <div class="slide-content">
+          <h3>广式早茶</h3>
+          <p>精致点心,传统粤式风味</p>
+        </div>
+      </div>
+      <div class="swiper-slide">
+        <img src="https://img.freepik.com/free-photo/top-view-table-full-delicious-food-composition_23-2149141353.jpg" alt="推荐菜品">
+        <div class="slide-content">
+          <h3>法式甜点</h3>
+          <p>精致法式烘焙,甜蜜享受</p>
+        </div>
+      </div>
+      <div class="swiper-slide">
+        <img src="https://img.freepik.com/free-photo/flat-lay-table-full-delicious-food_23-2149141309.jpg" alt="推荐菜品">
+        <div class="slide-content">
+          <h3>日式寿司</h3>
+          <p>新鲜食材,精致摆盘</p>
+        </div>
+      </div>
+      <div class="swiper-slide">
+        <img src="https://img.freepik.com/free-photo/top-view-table-full-delicious-food-composition_23-2149141354.jpg" alt="推荐菜品">
+        <div class="slide-content">
+          <h3>意大利面</h3>
+          <p>地道意式风味,浓郁酱汁</p>
+        </div>
+      </div>
+    </div>
+    <!-- Add pagination if needed -->
+    <div class="swiper-pagination"></div>
+    
+    <!-- Add navigation buttons if needed -->
+    <div class="swiper-button-prev"></div>
+    <div class="swiper-button-next"></div>
+  </div>
+
+
+
+  </section>
+  
+  <!-- 瀑布流图片 -->
+  <section class="waterfall-section">
+    <div class="section-title">
+      <h2>美食发现</h2>
+    </div>
+    
+    <div class="category-tabs">
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'all'"
+        (click)="changeCategory('all')">
+        全部
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'chuan'"
+        (click)="changeCategory('chuan')">
+        川菜
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'lu'"
+        (click)="changeCategory('lu')">
+        鲁菜
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'yue'"
+        (click)="changeCategory('yue')">
+        粤菜
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'su'"
+        (click)="changeCategory('su')">
+        苏菜
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'zhe'"
+        (click)="changeCategory('zhe')">
+        浙菜
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'min'"
+        (click)="changeCategory('min')">
+        闽菜
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'xiang'"
+        (click)="changeCategory('xiang')">
+        湘菜
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'hui'"
+        (click)="changeCategory('hui')">
+        徽菜
+      </div>
+      <div 
+        class="category-tab" 
+        [class.active]="currentCategory === 'more'"
+        (click)="changeCategory('more')">
+        更多
+      </div>
+    </div>
+    
+    <div 
+      class="refresh-container"
+      (touchstart)="onTouchStart($event)"
+      (touchmove)="onTouchMove($event)"
+      (touchend)="onTouchEnd()">
+      <div class="refresh-indicator">
+        <i class="bi bi-arrow-clockwise refresh-spinner"></i>
+        <span>下拉刷新...</span>
+      </div>
+      <div class="waterfall" id="waterfall-container">
+        <!-- 动态加载的内容 -->
+        <div class="waterfall-item" *ngFor="let dish of dishes">
+          <img [src]="dish.image" [alt]="dish.name">
+          <div class="item-info">
+            <h3>
+              <span class="dish-name">{{ dish.name }}</span>
+              <span class="dish-category">{{ categoryNames[dish.category] }} · {{ dish.name }}</span>
+            </h3>
+            <div class="popularity">
+              <i class="bi bi-fire"></i> {{ dish.popularity }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <div class="loading-spinner" *ngIf="isLoading">
+        <i class="bi bi-arrow-clockwise"></i> 加载中...
+      </div>
+    </div>
+  </section>
+</div>

+ 301 - 0
picture-web/src/modules/picture/home-tab/home-tab.scss

@@ -0,0 +1,301 @@
+:host {
+  --primary-color: #FF6B35;
+  --secondary-color: #F7C59F;
+  --accent-color: #EFEFD0;
+  --text-dark: #333;
+  --text-light: #fff;
+  --bg-gradient: linear-gradient(135deg, #FF6B35 0%, #FFBE0B 100%);
+}
+
+* {
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
+}
+
+body {
+  background-color: #f8f8f8;
+  color: var(--text-dark);
+  line-height: 1.6;
+  overflow-x: hidden;
+}
+
+.container {
+  max-width: 100%;
+  padding: 0 15px;
+}
+
+/* 顶部导航栏 */
+.header {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  background: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(10px);
+  -webkit-backdrop-filter: blur(10px);
+  z-index: 1000;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  padding: 10px 15px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+}
+
+.logo {
+  font-size: 20px;
+  font-weight: bold;
+  background: var(--bg-gradient);
+  -webkit-background-clip: text;
+  background-clip: text;
+  color: transparent;
+}
+
+.nav-icons {
+  display: flex;
+  gap: 15px;
+}
+
+.nav-icons i {
+  font-size: 20px;
+  color: var(--primary-color);
+}
+
+/* 搜索区域 */
+.search-section {
+  margin-top: 70px;
+  padding: 15px 0;
+}
+
+.search-bar {
+  position: relative;
+  margin-bottom: 15px;
+}
+
+.search-input {
+  width: 100%;
+  padding: 12px 20px 12px 45px;
+  border-radius: 25px;
+  border: none;
+  background-color: white;
+  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
+  font-size: 16px;
+  outline: none;
+}
+
+.search-icon {
+  position: absolute;
+  left: 15px;
+  top: 50%;
+  transform: translateY(-50%);
+  color: var(--primary-color);
+}
+
+.hot-search {
+  margin-bottom: 20px;
+}
+
+.hot-search h3 {
+  font-size: 16px;
+  margin-bottom: 10px;
+  color: var(--text-dark);
+}
+
+.hot-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+.hot-tag {
+  padding: 6px 12px;
+  background-color: white;
+  border-radius: 15px;
+  font-size: 14px;
+  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
+  color: var(--primary-color);
+  cursor: pointer;
+  transition: all 0.3s ease;
+}
+
+.hot-tag:hover {
+  background-color: var(--primary-color);
+  color: white;
+}
+
+/* 智能推荐轮播 */
+.recommend-section {
+  margin-bottom: 25px;
+}
+
+.section-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15px;
+}
+
+.section-title h2 {
+  font-size: 18px;
+  color: var(--text-dark);
+}
+
+.section-title a {
+  font-size: 14px;
+  color: var(--primary-color);
+  text-decoration: none;
+}
+
+.swiper-container {
+  width: 100%;
+  height: 180px;
+  border-radius: 12px;
+  overflow: hidden;
+}
+
+.swiper-slide {
+  position: relative;
+  background: #ddd;
+}
+
+.swiper-slide img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+}
+
+.slide-content {
+  position: absolute;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  padding: 15px;
+  background: linear-gradient(to top, rgba(0, 0, 0, 0.7), transparent);
+  color: white;
+}
+
+.slide-content h3 {
+  font-size: 16px;
+  margin-bottom: 5px;
+}
+
+.slide-content p {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+/* 瀑布流分类 */
+.category-tabs {
+  display: flex;
+  overflow-x: auto;
+  padding-bottom: 10px;
+  margin-bottom: 15px;
+  gap: 10px;
+}
+
+.category-tabs::-webkit-scrollbar {
+  display: none;
+}
+
+.category-tab {
+  flex-shrink: 0;
+  padding: 8px 15px;
+  border-radius: 20px;
+  background-color: white;
+  font-size: 14px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  box-shadow: 0 1px 5px rgba(0, 0, 0, 0.05);
+}
+
+.category-tab.active {
+  background: var(--bg-gradient);
+  color: white;
+}
+
+/* 瀑布流图片 */
+.waterfall {
+  column-count: 2;
+  column-gap: 10px;
+}
+
+.waterfall-item {
+  break-inside: avoid;
+  margin-bottom: 10px;
+  border-radius: 8px;
+  overflow: hidden;
+  background-color: white;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  position: relative;
+}
+
+.waterfall-item img {
+  width: 100%;
+  display: block;
+}
+
+.item-info {
+  padding: 10px;
+}
+
+.item-info h3 {
+  font-size: 14px;
+  margin-bottom: 5px;
+  display: flex;
+  flex-direction: column;
+}
+
+.dish-name {
+  font-weight: bold;
+}
+
+.dish-category {
+  font-size: 12px;
+  color: var(--primary-color);
+  margin-top: 2px;
+}
+
+.popularity {
+  display: flex;
+  align-items: center;
+  color: var(--primary-color);
+  font-size: 12px;
+  margin-top: 5px;
+}
+
+/* 下拉刷新 */
+.refresh-container {
+  position: relative;
+  overflow: hidden;
+}
+
+.refresh-indicator {
+  height: 50px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #999;
+  font-size: 14px;
+  transform: translateY(-100%);
+}
+
+.refresh-spinner {
+  margin-right: 8px;
+  animation: spin 1s linear infinite;
+}
+
+@keyframes spin {
+  0% { transform: rotate(0deg); }
+  100% { transform: rotate(360deg); }
+}
+
+/* 加载动画 */
+.loading-spinner {
+  text-align: center;
+  padding: 10px 0;
+}
+
+.loading-spinner i {
+  animation: spin 1s linear infinite;
+}

+ 18 - 18
picture-web/src/modules/picture/home-tab/home-tab.spec.ts

@@ -1,23 +1,23 @@
-import { ComponentFixture, TestBed } from '@angular/core/testing';
+// import { ComponentFixture, TestBed } from '@angular/core/testing';
 
-import { HomeTab } from './home-tab';
+// import { HomeTab } from './home-tab';
 
-describe('HomeTab', () => {
-  let component: HomeTab;
-  let fixture: ComponentFixture<HomeTab>;
+// describe('HomeTab', () => {
+//   let component: HomeTab;
+//   let fixture: ComponentFixture<HomeTab>;
 
-  beforeEach(async () => {
-    await TestBed.configureTestingModule({
-      imports: [HomeTab]
-    })
-    .compileComponents();
+//   beforeEach(async () => {
+//     await TestBed.configureTestingModule({
+//       imports: [HomeTab]
+//     })
+//     .compileComponents();
 
-    fixture = TestBed.createComponent(HomeTab);
-    component = fixture.componentInstance;
-    fixture.detectChanges();
-  });
+//     fixture = TestBed.createComponent(HomeTab);
+//     component = fixture.componentInstance;
+//     fixture.detectChanges();
+//   });
 
-  it('should create', () => {
-    expect(component).toBeTruthy();
-  });
-});
+//   it('should create', () => {
+//     expect(component).toBeTruthy();
+//   });
+// });

+ 211 - 5
picture-web/src/modules/picture/home-tab/home-tab.ts

@@ -1,11 +1,217 @@
-import { Component } from '@angular/core';
+import { Component, OnInit, AfterViewInit, HostListener } from '@angular/core';
+import { CommonModule } from '@angular/common';
+// import { SwiperModule } from 'swiper/angular';
+import Swiper from 'swiper';
 
+import { register } from 'swiper/element/bundle';
+register()
 @Component({
   selector: 'app-home-tab',
-  imports: [],
   templateUrl: './home-tab.html',
-  styleUrl: './home-tab.scss'
+  styleUrls: ['./home-tab.scss'],   
+  standalone: true,
+  imports: [CommonModule]
 })
-export class HomeTab {
+export class HomeTab implements OnInit, AfterViewInit {
+  // 分类名称映射
+  categoryNames: { [key: string]: string } = {
+    all: "全部",
+    chuan: "川菜",
+    lu: "鲁菜",
+    yue: "粤菜",
+    su: "苏菜",
+    zhe: "浙菜",
+    min: "闽菜",
+    xiang: "湘菜",
+    hui: "徽菜",
+    more: "国际"
+  };
 
-}
+  currentCategory = 'all';
+  isLoading = false;
+  loadedItemsCount = 0;
+  dishes: any[] = [];
+  isRefreshing = false;
+  startY = 0;
+  isThrottled = false;
+  swiper?: Swiper;
+
+  // 热门搜索标签
+  hotTags = ['川菜', '家常菜', '早餐', '低卡', '烘焙', '火锅', '海鲜', '甜品'];
+
+  ngOnInit(): void {
+    this.renderDishes(this.currentCategory);
+  }
+
+  ngAfterViewInit(): void {
+    this.initSwiper();
+  }
+
+  initSwiper(): void {
+    this.swiper = new Swiper('.swiper-container', {
+      loop: true,
+      autoplay: {
+        delay: 3000,
+        disableOnInteraction: false,
+      }
+    });
+  }
+
+  // 菜品数据生成函数
+  generateDishes(category: string, count: number, existingItems = 0): any[] {
+    const baseImages = [
+      "https://img.freepik.com/free-photo/top-view-table-full-delicious-food-composition_23-2149141352.jpg",
+      "https://img.freepik.com/free-photo/flat-lay-table-full-delicious-food_23-2149141308.jpg",
+      "https://img.freepik.com/free-photo/top-view-table-full-delicious-food-composition_23-2149141353.jpg",
+      "https://img.freepik.com/free-photo/flat-lay-table-full-delicious-food_23-2149141309.jpg",
+      "https://img.freepik.com/free-photo/top-view-table-full-delicious-food-composition_23-2149141354.jpg",
+      "https://img.freepik.com/free-photo/delicious-vietnamese-food-including-pho-ga-noodles-spring-rolls-white-table_181624-34062.jpg",
+      "https://img.freepik.com/free-photo/top-view-table-full-delicious-food-composition_23-2149141355.jpg",
+      "https://img.freepik.com/free-photo/flat-lay-table-full-delicious-food_23-2149141310.jpg",
+      "https://img.freepik.com/free-photo/top-view-table-full-delicious-food-composition_23-2149141356.jpg",
+      "https://img.freepik.com/free-photo/flat-lay-table-full-delicious-food_23-2149141311.jpg"
+    ];
+
+    const dishNames: { [key: string]: string[] } = {
+      chuan: ["水煮鱼", "回锅肉", "夫妻肺片", "麻婆豆腐", "宫保鸡丁", "鱼香肉丝", "口水鸡", "担担面", "酸菜鱼", "辣子鸡"],
+      lu: ["九转大肠", "糖醋鲤鱼", "葱烧海参", "油爆双脆", "德州扒鸡", "四喜丸子", "锅塌豆腐", "奶汤蒲菜", "黄焖鸡块", "爆炒腰花"],
+      yue: ["白切鸡", "烧鹅", "虾饺", "叉烧", "肠粉", "煲仔饭", "豉汁蒸排骨", "干炒牛河", "蜜汁叉烧包", "萝卜牛腩"],
+      su: ["松鼠桂鱼", "清炖狮子头", "盐水鸭", "大煮干丝", "蟹粉豆腐", "水晶肴肉", "响油鳝糊", "无锡排骨", "鸭血粉丝汤", "三套鸭"],
+      zhe: ["西湖醋鱼", "东坡肉", "龙井虾仁", "宋嫂鱼羹", "干炸响铃", "叫化童鸡", "油焖春笋", "蜜汁火方", "雪菜大汤黄鱼", "糟烩鞭笋"],
+      min: ["佛跳墙", "沙县小吃", "荔枝肉", "蚵仔煎", "土笋冻", "福州鱼丸", "闽南咸饭", "红糟鱼", "海蛎煎蛋", "八宝红鲟饭"],
+      xiang: ["剁椒鱼头", "毛氏红烧肉", "腊味合蒸", "永州血鸭", "东安子鸡", "辣椒炒肉", "湘西酸肉", "组庵鱼翅", "宁乡口味蛇", "湘味臭豆腐"],
+      hui: ["徽州毛豆腐", "黄山炖鸽", "臭鳜鱼", "胡适一品锅", "问政山笋", "刀板香", "中和汤", "绩溪炒粉丝", "深渡包袱饺", "葛粉圆子"],
+      more: ["意大利面", "日式寿司", "法式甜点", "印度咖喱", "泰国冬阴功", "韩国泡菜", "越南河粉", "墨西哥卷饼", "德国香肠", "美式汉堡"]
+    };
+
+    // 如果是全部分类,合并所有菜系的菜品
+    if (category === "all") {
+      const allDishes: any[] = [];
+      for (const key in dishNames) {
+        if (key !== "all") {
+          dishNames[key].forEach((name, index) => {
+            allDishes.push({
+              name: name,
+              category: key,
+              image: baseImages[(index + existingItems) % baseImages.length],
+              popularity: (Math.random() * 5 + 5).toFixed(1) + "k"
+            });
+          });
+        }
+      }
+      // 随机排序
+      return allDishes.sort(() => Math.random() - 0.5).slice(0, count);
+    } else {
+      // 其他分类生成对应菜品
+      const dishes: any[] = [];
+      for (let i = 0; i < count; i++) {
+        const nameIndex = (i + existingItems) % dishNames[category].length;
+        dishes.push({
+          name: dishNames[category][nameIndex],
+          category: category,
+          image: baseImages[(i + existingItems) % baseImages.length],
+          popularity: (Math.random() * 5 + 5).toFixed(1) + "k"
+        });
+      }
+      return dishes;
+    }
+  }
+
+  // 渲染菜品函数
+  renderDishes(category: string, isRefresh = false): void {
+    if (this.isLoading) return;
+    this.isLoading = true;
+
+    // 如果是刷新,重置状态
+    if (isRefresh) {
+      this.dishes = [];
+      this.loadedItemsCount = 0;
+    }
+
+    // 模拟API请求延迟
+    setTimeout(() => {
+      // 生成更多菜品数据
+      const newDishes = this.generateDishes(category, 10, this.loadedItemsCount);
+      
+      // 添加菜品到列表
+      this.dishes = [...this.dishes, ...newDishes];
+      
+      // 更新已加载项目计数
+      this.loadedItemsCount += newDishes.length;
+      this.currentCategory = category;
+      this.isLoading = false;
+    }, 800);
+  }
+
+  // 切换分类
+  changeCategory(category: string): void {
+    this.currentCategory = category;
+    this.renderDishes(category, true);
+  }
+
+  // 检查滚动位置
+  checkScroll(): void {
+    if (window.innerHeight + window.scrollY >= document.body.offsetHeight - 500) {
+      this.renderDishes(this.currentCategory);
+    }
+  }
+
+  // 滚动事件监听
+  @HostListener('window:scroll', [])
+  onWindowScroll(): void {
+    if (!this.isThrottled) {
+      this.checkScroll();
+      this.isThrottled = true;
+      setTimeout(() => {
+        this.isThrottled = false;
+      }, 200);
+    }
+  }
+
+  // 下拉刷新相关事件
+  onTouchStart(event: TouchEvent): void {
+    if (this.isRefreshing || window.scrollY > 0) return;
+    this.startY = event.touches[0].clientY;
+  }
+
+  onTouchMove(event: TouchEvent): void {
+    if (this.isRefreshing || window.scrollY > 0) return;
+    
+    const y = event.touches[0].clientY;
+    const diff = y - this.startY;
+    
+    if (diff > 0) {
+      event.preventDefault();
+      const progress = Math.min(diff / 100, 1);
+      const indicator = document.querySelector('.refresh-indicator') as HTMLElement;
+      if (indicator) {
+        indicator.style.transform = `translateY(${-50 + progress * 50}px)`;
+      }
+      
+      if (diff > 100 && !this.isRefreshing) {
+        this.isRefreshing = true;
+        const indicator = document.querySelector('.refresh-indicator') as HTMLElement;
+        if (indicator) {
+          indicator.innerHTML = '<i class="bi bi-arrow-clockwise refresh-spinner"></i><span>正在刷新...</span>';
+        }
+        
+        // 模拟刷新数据
+        setTimeout(() => {
+          this.renderDishes(this.currentCategory, true);
+          if (indicator) {
+            indicator.style.transform = 'translateY(-100%)';
+          }
+          this.isRefreshing = false;
+        }, 1000);
+      }
+    }
+  }
+
+  onTouchEnd(): void {
+    if (this.isRefreshing) return;
+    const indicator = document.querySelector('.refresh-indicator') as HTMLElement;
+    if (indicator) {
+      indicator.style.transform = 'translateY(-100%)';
+    }
+  }
+}

+ 5 - 2
picture-web/src/modules/picture/nav-mobile-tabs/nav-mobile-tabs.html

@@ -5,7 +5,7 @@
   </div>
   
   <!-- 底部导航栏 -->
-  <nav mat-tab-nav-bar class="bottom-nav">
+  <nav mat-tab-nav-bar class="bottom-nav" [tabPanel]="tabPanel">
     <a 
       mat-tab-link
       *ngFor="let link of navLinks"
@@ -17,4 +17,7 @@
       <span>{{link.label}}</span>
     </a>
   </nav>
-</div>
+
+  <!-- Add this panel (can be empty) -->
+  <mat-tab-nav-panel #tabPanel></mat-tab-nav-panel>
+</div>