0235648 4 өдөр өмнө
parent
commit
d53b7a8687

+ 9 - 0
menu-web/src/app/app.routes.ts

@@ -33,5 +33,14 @@ export const routes: Routes = [
       },
     ]
   }
+  ,
+ {
+    path: 'menus',
+    loadComponent: () => import('../modules/food/mobile/page-menus/page-menus').then(m => m.PageMenus)
+  },
+      {
+    path: 'dish',
+    loadComponent: () => import('../modules/food/mobile/pages/dish-management/dish-management.component').then(m => m.DishManagementComponent)
+  }
      
 ];

+ 4 - 2
menu-web/src/app/app.ts

@@ -1,9 +1,11 @@
 import { Component } from '@angular/core';
-import { RouterOutlet } from '@angular/router';
+import { RouterModule, RouterOutlet } from '@angular/router';
 
 @Component({
   selector: 'app-root',
-  imports: [RouterOutlet],
+  imports: [RouterOutlet
+    ,RouterModule
+  ],
   templateUrl: './app.html',
   styleUrl: './app.scss'
 })

+ 431 - 0
menu-web/src/lib/ncloud.ts

@@ -0,0 +1,431 @@
+// CloudObject.ts
+
+let serverURL = `https://dev.fmode.cn/parse`;
+if (location.protocol == "http:") {
+    serverURL = `http://dev.fmode.cn:1337/parse`;
+}
+
+export class CloudObject {
+    className: string;
+    id: string | undefined = undefined;
+    createdAt: any;
+    updatedAt: any;
+    data: Record<string, any> = {};
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    toPointer() {
+        return { "__type": "Pointer", "className": this.className, "objectId": this.id };
+    }
+
+    set(json: Record<string, any>) {
+        Object.keys(json).forEach(key => {
+            if (["objectId", "id", "createdAt", "updatedAt"].indexOf(key) > -1) {
+                return;
+            }
+            this.data[key] = json[key];
+        });
+    }
+
+    get(key: string) {
+        return this.data[key] || null;
+    }
+
+    async save() {
+        let method = "POST";
+        let url = serverURL + `/classes/${this.className}`;
+
+        // 更新
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        const body = JSON.stringify(this.data);
+        const response = await fetch(url, {
+            headers: {
+                "content-type": "application/json;charset=UTF-8",
+                "x-parse-application-id": "dev"
+            },
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        return this;
+    }
+
+    async destroy() {
+        if (!this.id) return;
+        const response = await fetch(serverURL + `/classes/${this.className}/${this.id}`, {
+            headers: {
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "DELETE",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result) {
+            this.id = undefined;
+        }
+        return true;
+    }
+}
+
+// CloudQuery.ts
+export class CloudQuery {
+    className: string;
+    queryParams: Record<string, any> = { where: {} };
+
+    constructor(className: string) {
+        this.className = className;
+    }
+
+    include(...fileds: string[]) {
+        this.queryParams["include"] = fileds;
+    }
+    greaterThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gt"] = value;
+    }
+
+    greaterThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$gte"] = value;
+    }
+
+    lessThan(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lt"] = value;
+    }
+
+    lessThanAndEqualTo(key: string, value: any) {
+        if (!this.queryParams["where"][key]) this.queryParams["where"][key] = {};
+        this.queryParams["where"][key]["$lte"] = value;
+    }
+
+    equalTo(key: string, value: any) {
+        if (!this.queryParams["where"]) this.queryParams["where"] = {};
+        this.queryParams["where"][key] = value;
+    }
+
+    async get(id: string) {
+        const url = serverURL + `/classes/${this.className}/${id}?`;
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        if (json) {
+            let existsObject = this.dataToObj(json)
+            return existsObject;
+        }
+        return null
+    }
+
+    async find(): Promise<Array<CloudObject>> {
+        let url = serverURL + `/classes/${this.className}?`;
+
+        let queryStr = ``
+        Object.keys(this.queryParams).forEach(key => {
+            let paramStr = JSON.stringify(this.queryParams[key]);
+            if (key == "include") {
+                paramStr = this.queryParams[key]?.join(",")
+            }
+            if (queryStr) {
+                url += `${key}=${paramStr}`;
+            } else {
+                url += `&${key}=${paramStr}`;
+            }
+        })
+        // if (Object.keys(this.queryParams["where"]).length) {
+
+        // }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        let list = json?.results || []
+        let objList = list.map((item: any) => this.dataToObj(item))
+        return objList || [];
+    }
+
+
+    async first() {
+        let url = serverURL + `/classes/${this.className}?`;
+
+        if (Object.keys(this.queryParams["where"]).length) {
+            const whereStr = JSON.stringify(this.queryParams["where"]);
+            url += `where=${whereStr}&limit=1`;
+        }
+
+        const response = await fetch(url, {
+            headers: {
+                "if-none-match": "W/\"1f0-ghxH2EwTk6Blz0g89ivf2adBDKY\"",
+                "x-parse-application-id": "dev"
+            },
+            body: null,
+            method: "GET",
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const json = await response?.json();
+        const exists = json?.results?.[0] || null;
+        if (exists) {
+            let existsObject = this.dataToObj(exists)
+            return existsObject;
+        }
+        return null
+    }
+
+    dataToObj(exists: any): CloudObject {
+        let existsObject = new CloudObject(this.className);
+        Object.keys(exists).forEach(key => {
+            if (exists[key]?.__type == "Object") {
+                exists[key] = this.dataToObj(exists[key])
+            }
+        })
+        existsObject.set(exists);
+        existsObject.id = exists.objectId;
+        existsObject.createdAt = exists.createdAt;
+        existsObject.updatedAt = exists.updatedAt;
+        return existsObject;
+    }
+}
+
+// CloudUser.ts
+export class CloudUser extends CloudObject {
+    constructor() {
+        super("_User"); // 假设用户类在Parse中是"_User"
+        // 读取用户缓存信息
+        let userCacheStr = localStorage.getItem("NCloud/dev/User")
+        if (userCacheStr) {
+            let userData = JSON.parse(userCacheStr)
+            // 设置用户信息
+            this.id = userData?.objectId;
+            this.sessionToken = userData?.sessionToken;
+            this.data = userData; // 保存用户数据
+        }
+    }
+
+    sessionToken: string | null = ""
+    /** 获取当前用户信息 */
+    async current() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return null;
+        }
+        return this;
+        // const response = await fetch(serverURL + `/users/me`, {
+        //     headers: {
+        //         "x-parse-application-id": "dev",
+        //         "x-parse-session-token": this.sessionToken // 使用sessionToken进行身份验证
+        //     },
+        //     method: "GET"
+        // });
+
+        // const result = await response?.json();
+        // if (result?.error) {
+        //     console.error(result?.error);
+        //     return null;
+        // }
+        // return result;
+    }
+
+    /** 登录 */
+    async login(username: string, password: string): Promise<CloudUser | null> {
+        const response = await fetch(serverURL + `/login`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify({ username, password }),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        return this;
+    }
+
+    /** 登出 */
+    async logout() {
+        if (!this.sessionToken) {
+            console.error("用户未登录");
+            return;
+        }
+
+        const response = await fetch(serverURL + `/logout`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "x-parse-session-token": this.sessionToken
+            },
+            method: "POST"
+        });
+
+        let result = await response?.json();
+
+        if (result?.error) {
+            console.error(result?.error);
+            if (result?.error == "Invalid session token") {
+                this.clearUserCache()
+                return true;
+            }
+            return false;
+        }
+
+        this.clearUserCache()
+        return true;
+    }
+    clearUserCache() {
+        // 清除用户信息
+        localStorage.removeItem("NCloud/dev/User")
+        this.id = undefined;
+        this.sessionToken = null;
+        this.data = {};
+    }
+
+    /** 注册 */
+    async signUp(username: string, password: string, additionalData: Record<string, any> = {}) {
+        const userData = {
+            username,
+            password,
+            ...additionalData // 合并额外的用户数据
+        };
+
+        const response = await fetch(serverURL + `/users`, {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            body: JSON.stringify(userData),
+            method: "POST"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+            return null;
+        }
+
+        // 设置用户信息
+        // 缓存用户信息
+        console.log(result)
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(result))
+        this.id = result?.objectId;
+        this.sessionToken = result?.sessionToken;
+        this.data = result; // 保存用户数据
+        return this;
+    }
+
+    override async save() {
+        let method = "POST";
+        let url = serverURL + `/users`;
+
+        // 更新用户信息
+        if (this.id) {
+            url += `/${this.id}`;
+            method = "PUT";
+        }
+
+        let data: any = JSON.parse(JSON.stringify(this.data))
+        delete data.createdAt
+        delete data.updatedAt
+        delete data.ACL
+        delete data.objectId
+        const body = JSON.stringify(data);
+        let headersOptions: any = {
+            "content-type": "application/json;charset=UTF-8",
+            "x-parse-application-id": "dev",
+            "x-parse-session-token": this.sessionToken, // 添加sessionToken以进行身份验证
+        }
+        const response = await fetch(url, {
+            headers: headersOptions,
+            body: body,
+            method: method,
+            mode: "cors",
+            credentials: "omit"
+        });
+
+        const result = await response?.json();
+        if (result?.error) {
+            console.error(result?.error);
+        }
+        if (result?.objectId) {
+            this.id = result?.objectId;
+        }
+        localStorage.setItem("NCloud/dev/User", JSON.stringify(this.data))
+        return this;
+    }
+}
+
+export class CloudApi {
+    async fetch(path: string, body: any, options?: {
+        method: string
+        body: any
+    }) {
+
+        let reqOpts: any = {
+            headers: {
+                "x-parse-application-id": "dev",
+                "Content-Type": "application/json"
+            },
+            method: options?.method || "POST",
+            mode: "cors",
+            credentials: "omit"
+        }
+        if (body || options?.body) {
+            reqOpts.body = JSON.stringify(body || options?.body);
+            reqOpts.json = true;
+        }
+        let host = `https://dev.fmode.cn`
+        // host = `http://127.0.0.1:1337`
+        let url = `${host}/api/` + path
+        console.log(url, reqOpts)
+        const response = await fetch(url, reqOpts);
+        let json = await response.json();
+        return json
+    }
+}

+ 3 - 0
menu-web/src/modules/food/mobile/page-order/components/cart-item/cart-item.component/cart-item.component.ts

@@ -2,6 +2,7 @@ import { Component, Input, Output, EventEmitter } from '@angular/core';
 import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
 import { faTrashAlt } from '@fortawesome/free-solid-svg-icons';
 
+
 @Component({
   selector: 'app-cart-item', // 修正选择器名称
   standalone: true,
@@ -22,5 +23,7 @@ export class CartItemComponent {
 
   removeItem(): void {
     this.remove.emit();
+
   }
+ 
 }

+ 1 - 1
menu-web/src/modules/food/mobile/page-order/components/history-item/history-item.component/history-item.component.html

@@ -1,4 +1,4 @@
-<div class="history-item">
+<div class="history-item" routerlink="/menu-web/src/modules/food/mobile/page-menus">
   <div class="history-logo">{{ item.initial }}</div>
   <div class="history-name">{{ item.name }}</div>
   <div class="history-date">{{ item.date }}</div>

+ 46 - 20
menu-web/src/modules/food/mobile/page-order/components/history-item/history-item.component/history-item.component.scss

@@ -1,36 +1,62 @@
 .history-item {
-  background: var(--light-gray);
-  border-radius: 12px;
-  padding: 15px;
-  text-align: center;
-  cursor: pointer;
-  transition: var(--transition);
-
-  &:hover {
-    transform: translateY(-3px);
-    box-shadow: var(--card-shadow);
+  display: flex;
+  align-items: center;
+  padding: 12px;
+  border-bottom: 1px solid #eee;
+  position: relative;
+  cursor: default;
+  
+  &.theme-restaurant {
+    background-color: #f9f9ff;
+    cursor: pointer;
+    
+    &:hover {
+      background-color: #f0f0ff;
+    }
+    
+    .history-name {
+      font-weight: bold;
+      color: #4a4aff;
+    }
   }
 }
 
 .history-logo {
-  width: 50px;
-  height: 50px;
-  border-radius: 10px;
-  background: linear-gradient(45deg, #ffecd2, #fcb69f);
-  margin: 0 auto 10px;
+  width: 40px;
+  height: 40px;
+  background-color: #e0e0ff;
+  border-radius: 50%;
   display: flex;
   align-items: center;
   justify-content: center;
-  color: var(--white);
   font-weight: bold;
+  margin-right: 12px;
 }
 
 .history-name {
-  font-weight: 600;
-  margin-bottom: 5px;
+  flex: 1;
 }
 
 .history-date {
-  font-size: 12px;
-  color: var(--gray);
+  color: #888;
+  font-size: 0.9em;
+}
+
+.menu-hint {
+  position: absolute;
+  right: 12px;
+  top: 50%;
+  transform: translateY(-50%);
+  background-color: #4a4aff;
+  color: white;
+  padding: 4px 8px;
+  border-radius: 4px;
+  font-size: 0.8em;
+  animation: pulse 1.5s infinite;
+}
+
+@keyframes pulse {
+  0% { opacity: 0.8; }
+  50% { opacity: 1; }
+  100% { opacity: 0.8; }
 }

+ 10 - 0
menu-web/src/modules/food/mobile/page-order/components/history-item/history-item.component/history-item.component.ts

@@ -1,5 +1,6 @@
 import { Component, Input } from '@angular/core';
 import { CommonModule } from '@angular/common';
+import { Router } from '@angular/router'; // 添加导入
 
 @Component({
   selector: 'app-history-item',
@@ -10,4 +11,13 @@ import { CommonModule } from '@angular/common';
 })
 export class HistoryItemComponent {
   @Input() item: any;
+  
+  constructor(private router: Router) {} // 注入Router
+  
+  // 添加导航方法
+  navigateToMenu() {
+    if (this.item.name === '点菜喵主题餐厅') {
+      this.router.navigate(['/menus']);
+    }
+  }
 }

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

@@ -79,7 +79,7 @@
       <h2 class="section-title">历史购买店铺</h2>
       <div class="history-grid">
         @for (historyItem of historyItems; track historyItem.id) {
-          <app-history-item [item]="historyItem"></app-history-item>
+          <app-history-item [item]="historyItem" routerLink="/menus"></app-history-item>
         }
       </div>
     </div>

+ 7 - 0
menu-web/src/modules/food/mobile/pages/dish-management/dish-management.component.css

@@ -0,0 +1,7 @@
+/* dish-management.component.css */
+.truncate {
+  max-width: 200px;
+  white-space: nowrap;
+  overflow: hidden;
+  text-overflow: ellipsis;
+}

+ 231 - 0
menu-web/src/modules/food/mobile/pages/dish-management/dish-management.component.html

@@ -0,0 +1,231 @@
+<!-- dish-management.component.html -->
+<div class="container mx-auto p-4">
+  <!-- 搜索和操作区域 -->
+  <div class="flex justify-between items-center mb-6">
+    <div class="flex gap-2">
+      <input 
+        type="text" 
+        placeholder="搜索菜品..." 
+        class="input input-bordered w-full max-w-xs"
+        [(ngModel)]="searchTerm"
+      />
+      <button class="btn btn-primary" (click)="search()">
+        <i class="fas fa-search"></i> 搜索
+      </button>
+    </div>
+    
+    <button class="btn btn-success" (click)="startCreate()">
+      <i class="fas fa-plus"></i> 添加菜品
+    </button>
+  </div>
+
+  <!-- 表单区域 -->
+  @if (showForm) {
+  <div class="card bg-base-100 shadow-xl mb-6">
+    <div class="card-body">
+      <h2 class="card-title">
+        {{ isEditing ? '编辑菜品' : '添加新菜品' }}
+      </h2>
+      
+      <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
+        <!-- 基本信息 -->
+        <div class="space-y-4">
+          <div class="form-control">
+            <label class="label">
+              <span class="label-text">菜品名称 *</span>
+            </label>
+            <input 
+              type="text" 
+              class="input input-bordered w-full" 
+              [(ngModel)]="currentDish.name"
+              required
+            />
+          </div>
+          
+          <div class="form-control">
+            <label class="label">
+              <span class="label-text">描述</span>
+            </label>
+            <textarea 
+              class="textarea textarea-bordered w-full" 
+              [(ngModel)]="currentDish.description"
+            ></textarea>
+          </div>
+          
+          <div class="form-control">
+            <label class="label cursor-pointer">
+              <span class="label-text">是否上架</span>
+              <input 
+                type="checkbox" 
+                class="toggle toggle-primary" 
+                [(ngModel)]="currentDish.isActive"
+              />
+            </label>
+          </div>
+        </div>
+        
+        <!-- 分类和属性 -->
+        <div class="space-y-4">
+          <div class="form-control">
+            <label class="label">
+              <span class="label-text">分类</span>
+            </label>
+            <select class="select select-bordered w-full" [(ngModel)]="currentDish.category">
+              <option [value]="null">-- 请选择分类 --</option>
+              @for (category of categories; track category.objectId) {
+              <option [value]="category.objectId">
+                {{ category.get('name') }}
+              </option>
+              }
+            </select>
+          </div>
+          
+          <div class="grid grid-cols-2 gap-4">
+            <div class="form-control">
+              <label class="label">
+                <span class="label-text">制作时间(分钟)</span>
+              </label>
+              <input 
+                type="number" 
+                class="input input-bordered w-full" 
+                [(ngModel)]="currentDish.preparationTime"
+                min="0"
+              />
+            </div>
+            
+            <div class="form-control">
+              <label class="label">
+                <span class="label-text">辣度(0-5)</span>
+              </label>
+              <input 
+                type="number" 
+                class="input input-bordered w-full" 
+                [(ngModel)]="currentDish.spicyLevel"
+                min="0" 
+                max="5"
+              />
+            </div>
+          </div>
+          
+          <!-- 标签管理 -->
+          <div class="form-control">
+            <label class="label">
+              <span class="label-text">标签</span>
+            </label>
+            <div class="flex gap-2">
+              <input 
+                type="text" 
+                class="input input-bordered flex-grow" 
+                [(ngModel)]="newTag"
+                placeholder="添加标签..."
+                (keyup.enter)="addTag()"
+              />
+              <button class="btn btn-outline" (click)="addTag()">
+                <i class="fas fa-plus"></i>
+              </button>
+            </div>
+            
+            <div class="flex flex-wrap gap-2 mt-2">
+              @for (tag of currentDish.tags; track tag) {
+              <div class="badge badge-primary gap-2">
+                {{ tag }}
+                <button class="text-xs" (click)="removeTag(tag)">
+                  <i class="fas fa-times"></i>
+                </button>
+              </div>
+              }
+            </div>
+          </div>
+        </div>
+      </div>
+      
+      <div class="card-actions justify-end mt-4">
+        <button class="btn btn-ghost" (click)="cancelEdit()">取消</button>
+        <button class="btn btn-primary" (click)="saveDish()">保存</button>
+      </div>
+    </div>
+  </div>
+  }
+
+  <!-- 加载状态 -->
+  @if (isLoading) {
+  <div class="text-center py-10">
+    <span class="loading loading-spinner loading-lg"></span>
+    <p>加载中...</p>
+  </div>
+  }
+
+  <!-- 菜品列表 -->
+  @if (!isLoading && dishes.length === 0) {
+  <div class="alert alert-info shadow-lg">
+    <div>
+      <i class="fas fa-info-circle"></i>
+      <span>没有找到菜品数据</span>
+    </div>
+  </div>
+  }
+
+  @if (!isLoading && dishes.length > 0) {
+  <div class="overflow-x-auto">
+    <table class="table table-zebra w-full">
+      <thead>
+        <tr>
+          <th>名称</th>
+          <th>描述</th>
+          <th>分类</th>
+          <th>状态</th>
+          <th>制作时间</th>
+          <th>辣度</th>
+          <th>操作</th>
+        </tr>
+      </thead>
+      <tbody>
+        @for (dish of dishes; track dish.objectId) {
+        <tr>
+          <td>{{ dish.get('name') }}</td>
+          <td class="max-w-xs truncate">{{ dish.get('description') }}</td>
+          <td>
+            @if (dish.get('category')) {
+            {{ dish.get('category').get('name') }}
+            } @else {
+            <span class="text-gray-400">未分类</span>
+            }
+          </td>
+          <td>
+            <button 
+              class="btn btn-xs" 
+              [class.btn-success]="dish.get('isActive')"
+              [class.btn-error]="!dish.get('isActive')"
+              (click)="toggleStatus(dish)"
+            >
+              {{ dish.get('isActive') ? '上架中' : '已下架' }}
+            </button>
+          </td>
+          <td>{{ dish.get('preparationTime') }} 分钟</td>
+          <td>
+            <div class="rating rating-sm">
+              @for (star of [1,2,3,4,5]; track star) {
+              <input 
+                type="radio" 
+                class="mask mask-star-2 bg-orange-400" 
+                [checked]="star <= dish.get('spicyLevel')"
+                disabled
+              />
+              }
+            </div>
+          </td>
+          <td class="flex gap-2">
+            <button class="btn btn-xs btn-info" (click)="startEdit(dish)">
+              <i class="fas fa-edit"></i> 编辑
+            </button>
+            <button class="btn btn-xs btn-error" (click)="deleteDish(dish.objectId)">
+              <i class="fas fa-trash"></i> 删除
+            </button>
+          </td>
+        </tr>
+        }
+      </tbody>
+    </table>
+  </div>
+  }
+</div>

+ 190 - 0
menu-web/src/modules/food/mobile/pages/dish-management/dish-management.component.ts

@@ -0,0 +1,190 @@
+// dish-management.component.ts
+import { Component, OnInit } from '@angular/core';
+import { CommonModule } from '@angular/common';
+import { FormsModule } from '@angular/forms';
+import { CloudObject, CloudQuery } from '../../../../../lib/ncloud';
+
+@Component({
+  selector: 'app-dish-management',
+  standalone: true,
+  imports: [CommonModule, FormsModule],
+  templateUrl: './dish-management.component.html',
+  styleUrls: ['./dish-management.component.css']
+})
+export class DishManagementComponent implements OnInit {
+  // 菜品数据
+  dishes: any[] = [];
+  categories: any[] = [];
+  
+  // 当前编辑的菜品
+  currentDish: any = {
+    name: '',
+    description: '',
+    isActive: true,
+    category: null,
+    preparationTime: 0,
+    spicyLevel: 0,
+    tags: []
+  };
+  
+  // UI状态
+  isEditing = false;
+  newTag = '';
+  isLoading = true;
+  showForm = false;
+  searchTerm = '';
+
+  async ngOnInit() {
+    await this.loadDishes();
+    await this.loadCategories();
+  }
+
+  // 加载菜品列表
+  async loadDishes() {
+    this.isLoading = true;
+    try {
+      const query = new CloudQuery('Dish');
+      query.include('category');
+    //   query.descending('createdAt');
+      
+      // 添加搜索条件
+      if (this.searchTerm) {
+        query.equalTo('name', this.searchTerm);
+      }
+      
+      this.dishes = await query.find();
+    } catch (error) {
+      console.error('加载菜品失败:', error);
+    } finally {
+      this.isLoading = false;
+    }
+  }
+
+  // 加载分类
+  async loadCategories() {
+    try {
+      const query = new CloudQuery('Category');
+    //   query.ascending('sortOrder');
+      this.categories = await query.find();
+    } catch (error) {
+      console.error('加载分类失败:', error);
+    }
+  }
+
+  // 开始创建新菜品
+  startCreate() {
+    this.isEditing = false;
+    this.showForm = true;
+    this.currentDish = {
+      name: '',
+      description: '',
+      isActive: true,
+      category: null,
+      preparationTime: 0,
+      spicyLevel: 0,
+      tags: []
+    };
+  }
+
+  // 开始编辑菜品
+  startEdit(dish: any) {
+    this.isEditing = true;
+    this.showForm = true;
+    this.currentDish = {
+      ...dish,
+      category: dish.category?.objectId || null
+    };
+  }
+
+  // 取消编辑
+  cancelEdit() {
+    this.showForm = false;
+    this.currentDish = {
+      name: '',
+      description: '',
+      isActive: true,
+      category: null,
+      preparationTime: 0,
+      spicyLevel: 0,
+      tags: []
+    };
+  }
+
+  // 添加标签
+  addTag() {
+    if (this.newTag.trim() && !this.currentDish.tags.includes(this.newTag.trim())) {
+      this.currentDish.tags.push(this.newTag.trim());
+      this.newTag = '';
+    }
+  }
+
+  // 移除标签
+  removeTag(tag: string) {
+    this.currentDish.tags = this.currentDish.tags.filter((t: string) => t !== tag);
+  }
+
+  // 保存菜品
+  async saveDish() {
+    const dish = new CloudObject('Dish');
+    
+    // 如果是编辑,设置对象ID
+    if (this.isEditing && this.currentDish.objectId) {
+      dish.id = this.currentDish.objectId;
+    }
+    
+    // 设置菜品属性
+    dish.set({
+      name: this.currentDish.name,
+      description: this.currentDish.description,
+      isActive: this.currentDish.isActive,
+      category: this.currentDish.category 
+        ? { __type: 'Pointer', className: 'Category', objectId: this.currentDish.category } 
+        : null,
+      preparationTime: Number(this.currentDish.preparationTime),
+      spicyLevel: Number(this.currentDish.spicyLevel),
+      tags: this.currentDish.tags
+    });
+
+    try {
+      await dish.save();
+      this.showForm = false;
+      await this.loadDishes();
+    } catch (error) {
+      console.error('保存菜品失败:', error);
+    }
+  }
+
+  // 删除菜品
+  async deleteDish(dishId: string) {
+    if (confirm('确定要删除这个菜品吗?')) {
+      const dish = new CloudObject('Dish');
+      dish.id = dishId;
+      
+      try {
+        await dish.destroy();
+        await this.loadDishes();
+      } catch (error) {
+        console.error('删除菜品失败:', error);
+      }
+    }
+  }
+
+  // 切换菜品状态
+  async toggleStatus(dish: any) {
+    const dishObj = new CloudObject('Dish');
+    dishObj.id = dish.objectId;
+    dishObj.set({ isActive: !dish.get('isActive') });
+    
+    try {
+      await dishObj.save();
+      await this.loadDishes();
+    } catch (error) {
+      console.error('更新状态失败:', error);
+    }
+  }
+
+  // 搜索菜品
+  async search() {
+    await this.loadDishes();
+  }
+}