徐滕 1 ماه پیش
والد
کامیت
9cb0f6fbbe
32فایلهای تغییر یافته به همراه2 افزوده شده و 3884 حذف شده
  1. 0 2
      src/api/AppPath.js
  2. 0 66
      src/api/hotel/HotelBusinessHoursService.js
  3. 0 24
      src/api/hotel/HotelConfigService.js
  4. 0 136
      src/api/hotel/HotelDishService.js
  5. 0 88
      src/api/hotel/HotelDishSpecService.js
  6. 0 76
      src/api/hotel/HotelRoomService.js
  7. 0 57
      src/api/hotel/HotelRoomTypeService.js
  8. 2 2
      src/api/sys/OSSService.js
  9. 0 165
      src/store/modules/hotel.js
  10. 0 158
      src/utils/websocket.js
  11. 0 168
      src/views/hotel/admin/BusinessHoursForm.vue
  12. 0 180
      src/views/hotel/admin/BusinessHoursManagement.vue
  13. 0 22
      src/views/hotel/admin/DishManagement.vue
  14. 0 130
      src/views/hotel/admin/DishSpecForm.vue
  15. 0 237
      src/views/hotel/admin/DishSpecManagement.vue
  16. 0 112
      src/views/hotel/admin/DishSpecOptionForm.vue
  17. 0 102
      src/views/hotel/admin/HotelConfigManagement.vue
  18. 0 151
      src/views/hotel/admin/QrCodeDialog.vue
  19. 0 146
      src/views/hotel/admin/RoomDetail.vue
  20. 0 139
      src/views/hotel/admin/RoomForm.vue
  21. 0 310
      src/views/hotel/admin/RoomManagement.vue
  22. 0 100
      src/views/hotel/admin/RoomTypeForm.vue
  23. 0 166
      src/views/hotel/admin/RoomTypeManagement.vue
  24. 0 21
      src/views/hotel/customer/CustomerMenu.vue
  25. 0 104
      src/views/hotel/dish/CategoryForm.vue
  26. 0 127
      src/views/hotel/dish/CategoryManagement.vue
  27. 0 200
      src/views/hotel/dish/DishForm.vue
  28. 0 351
      src/views/hotel/dish/DishImageUpload.vue
  29. 0 262
      src/views/hotel/dish/DishManagement.vue
  30. 0 22
      src/views/hotel/hotelfront/HotelFrontOrders.vue
  31. 0 39
      src/views/hotel/kitchen/KitchenOrders.vue
  32. 0 21
      src/views/hotel/restaurant/RestaurantOrders.vue

+ 0 - 2
src/api/AppPath.js

@@ -19,5 +19,3 @@ export const CCPM_PATH = "/ccpm-server";
 export const CONSULTANCY_PATH = "/consultancy-server";
 //进销存系统
 export const PSI_MANAGEMANT = "/psi-management-server";
-//酒店客房送餐系统
-export const HOTEL_ORDER_PATH = "/hotel";

+ 0 - 66
src/api/hotel/HotelBusinessHoursService.js

@@ -1,66 +0,0 @@
-import httpRequest from "@/utils/httpRequest";
-import { HOTEL_ORDER_PATH as prefix } from "../AppPath";
-
-/**
- * 营业时段管理API服务
- */
-export default class HotelBusinessHoursService {
-	/** 分页查询营业时段列表 */
-	page(params) {
-		return httpRequest({
-			url: prefix + "/config/businessHours/list",
-			method: "get",
-			params,
-		});
-	}
-
-	/** 获取营业时段列表(全部) */
-	all() {
-		return httpRequest({
-			url: prefix + "/config/businessHours/all",
-			method: "get",
-		});
-	}
-
-	/** 获取启用的营业时段 */
-	enabled() {
-		return httpRequest({
-			url: prefix + "/config/businessHours/enabled",
-			method: "get",
-		});
-	}
-
-	/** 查询营业时段详情 */
-	queryById(id) {
-		return httpRequest({
-			url: prefix + "/config/businessHours/queryById",
-			method: "get",
-			params: { id },
-		});
-	}
-
-	/** 新增/编辑营业时段 */
-	save(data) {
-		return httpRequest({
-			url: prefix + "/config/businessHours/save",
-			method: "post",
-			data,
-		});
-	}
-
-	/** 切换状态 */
-	toggle(id) {
-		return httpRequest({
-			url: prefix + "/config/businessHours/toggle/" + id,
-			method: "post",
-		});
-	}
-
-	/** 删除营业时段 */
-	delete(id) {
-		return httpRequest({
-			url: prefix + "/config/businessHours/delete/" + id,
-			method: "post",
-		});
-	}
-}

+ 0 - 24
src/api/hotel/HotelConfigService.js

@@ -1,24 +0,0 @@
-import httpRequest from "@/utils/httpRequest";
-import { HOTEL_ORDER_PATH as prefix } from "../AppPath";
-
-/**
- * 酒店配置管理API服务
- */
-export default class HotelConfigService {
-	/** 获取酒店配置 */
-	get() {
-		return httpRequest({
-			url: prefix + "/config/get",
-			method: "get",
-		});
-	}
-
-	/** 保存/更新酒店配置 */
-	save(data) {
-		return httpRequest({
-			url: prefix + "/config/save",
-			method: "post",
-			data,
-		});
-	}
-}

+ 0 - 136
src/api/hotel/HotelDishService.js

@@ -1,136 +0,0 @@
-import httpRequest from "@/utils/httpRequest";
-import { HOTEL_ORDER_PATH as prefix } from "../AppPath";
-
-export default class HotelDishService {
-    // ============ 菜品管理 ============
-
-    /** 管理后台-菜品分页查询 */
-    page(params) {
-        return httpRequest({
-            url: prefix + "/dish/page",
-            method: "get",
-            params,
-        });
-    }
-
-    /** 客户端-菜品分页查询 */
-    clientPage(params) {
-        return httpRequest({
-            url: prefix + "/dish/clientPage",
-            method: "get",
-            params,
-        });
-    }
-
-    /** 查询菜品详情 */
-    queryById(id) {
-        return httpRequest({
-            url: prefix + "/dish/detail/" + id,
-            method: "get",
-        });
-    }
-
-    /** 新增/编辑菜品 */
-    save(data) {
-        return httpRequest({
-            url: prefix + "/dish/save",
-            method: "post",
-            data,
-        });
-    }
-
-    /** 删除菜品 */
-    delete(id) {
-        return httpRequest({
-            url: prefix + "/dish/delete/" + id,
-            method: "post",
-        });
-    }
-
-    /** 更新菜品状态 */
-    updateStatus(id, status) {
-        return httpRequest({
-            url: prefix + "/dish/updateStatus",
-            method: "post",
-            data: { id: id, status: status },
-        });
-    }
-
-    /** 标记售罄 */
-    markSoldOut(id) {
-        return httpRequest({
-            url: prefix + "/dish/soldOut/" + id,
-            method: "post",
-        });
-    }
-
-    /** 恢复上架 */
-    restore(id) {
-        return httpRequest({
-            url: prefix + "/dish/restore/" + id,
-            method: "post",
-        });
-    }
-
-    /** 批量操作 */
-    batch(data) {
-        return httpRequest({
-            url: prefix + "/dish/batch",
-            method: "post",
-            data,
-        });
-    }
-
-    /** 销量排行 */
-    ranking(limit) {
-        return httpRequest({
-            url: prefix + "/dish/ranking",
-            method: "get",
-            params: { limit: limit || 10 },
-        });
-    }
-
-    // ============ 菜品分类 ============
-
-    /** 分页查询分类 */
-    categoryPage(params) {
-        return httpRequest({
-            url: prefix + "/dishCategory/page",
-            method: "get",
-            params,
-        });
-    }
-
-    /** 查询所有启用的分类 */
-    categoryAll() {
-        return httpRequest({
-            url: prefix + "/dishCategory/all",
-            method: "get",
-        });
-    }
-
-    /** 新增/编辑分类 */
-    categorySave(data) {
-        return httpRequest({
-            url: prefix + "/dishCategory/save",
-            method: "post",
-            data,
-        });
-    }
-
-    /** 切换分类状态 */
-    categoryToggleStatus(id) {
-        return httpRequest({
-            url: prefix + "/dishCategory/toggleStatus/" + id,
-            method: "post",
-        });
-    }
-
-    /** 删除分类 */
-    categoryDelete(id) {
-        return httpRequest({
-            url: prefix + "/dishCategory/delete/" + id,
-            method: "post",
-        });
-    }
-}

+ 0 - 88
src/api/hotel/HotelDishSpecService.js

@@ -1,88 +0,0 @@
-import httpRequest from "@/utils/httpRequest";
-import { HOTEL_ORDER_PATH as prefix } from "../AppPath";
-
-/**
- * 菜品规格管理API服务
- */
-export default class HotelDishSpecService {
-	/** 分页查询规格组列表 */
-	groupPage(params) {
-		return httpRequest({
-			url: prefix + "/dishSpecGroup/page",
-			method: "get",
-			params,
-		});
-	}
-
-	/** 根据菜品ID查询所有规格组(含选项) */
-	findByDishId(dishId) {
-		return httpRequest({
-			url: prefix + "/dishSpecGroup/findByDishId",
-			method: "get",
-			params: { dishId },
-		});
-	}
-
-	/** 查询规格组详情 */
-	queryGroupById(id) {
-		return httpRequest({
-			url: prefix + "/dishSpecGroup/queryById",
-			method: "get",
-			params: { id },
-		});
-	}
-
-	/** 新增/编辑规格组 */
-	saveGroup(data) {
-		return httpRequest({
-			url: prefix + "/dishSpecGroup/save",
-			method: "post",
-			data,
-		});
-	}
-
-	/** 删除规格组 */
-	deleteGroup(id) {
-		return httpRequest({
-			url: prefix + "/dishSpecGroup/delete/" + id,
-			method: "post",
-		});
-	}
-
-	// ==================== 规格选项 ====================
-
-	/** 根据规格组ID查询所有选项 */
-	findOptionsByGroupId(groupId) {
-		return httpRequest({
-			url: prefix + "/dishSpecOption/findByGroupId",
-			method: "get",
-			params: { groupId },
-		});
-	}
-
-	/** 查询规格选项详情 */
-	queryOptionById(id) {
-		return httpRequest({
-			url: prefix + "/dishSpecOption/queryById",
-			method: "get",
-			params: { id },
-		});
-	}
-
-	/** 新增/编辑规格选项 */
-	saveOption(data) {
-		return httpRequest({
-			url: prefix + "/dishSpecOption/save",
-			method: "post",
-			data,
-		});
-	}
-
-	/** 删除规格选项 */
-	deleteOption(id) {
-		return httpRequest({
-			url: prefix + "/dishSpecOption/delete/" + id,
-			method: "post",
-		});
-	}
-}

+ 0 - 76
src/api/hotel/HotelRoomService.js

@@ -1,76 +0,0 @@
-import httpRequest from "@/utils/httpRequest";
-import { HOTEL_ORDER_PATH as prefix } from "../AppPath";
-
-export default class HotelRoomService {
-	/** 分页查询房间列表 */
-	page(params) {
-		return httpRequest({
-			url: prefix + "/room/list",
-			method: "get",
-			params,
-		});
-	}
-
-	/** 查询房间详情 */
-	queryById(id) {
-		return httpRequest({
-			url: prefix + "/room/queryById",
-			method: "get",
-			params: { id },
-		});
-	}
-
-	/** 新增/编辑房间 */
-	save(data) {
-		return httpRequest({
-			url: prefix + "/room/save",
-			method: "post",
-			data,
-		});
-	}
-
-	/** 切换房间状态 */
-	toggleStatus(id, status) {
-		return httpRequest({
-			url: prefix + "/room/toggleStatus",
-			method: "post",
-			params: { id, status },
-		});
-	}
-
-	/** 补打二维码(内容不变) */
-	reprintQr(id) {
-		return httpRequest({
-			url: prefix + "/room/reprintQr",
-			method: "get",
-			params: { id },
-		});
-	}
-
-	/** 重新生成二维码(内容改变) */
-	regenerateQr(id) {
-		return httpRequest({
-			url: prefix + "/room/regenerateQr",
-			method: "post",
-			params: { id },
-		});
-	}
-
-	/** 删除房间 */
-	delete(id) {
-		return httpRequest({
-			url: prefix + "/room/delete",
-			method: "post",
-			params: { id },
-		});
-	}
-
-	/** 批量删除房间 */
-	batchDelete(ids) {
-		return httpRequest({
-			url: prefix + "/room/batchDelete",
-			method: "post",
-			params: { ids },
-		});
-	}
-}

+ 0 - 57
src/api/hotel/HotelRoomTypeService.js

@@ -1,57 +0,0 @@
-import httpRequest from "@/utils/httpRequest";
-import { HOTEL_ORDER_PATH as prefix } from "../AppPath";
-
-export default class HotelRoomTypeService {
-	/** 分页查询房间类型列表 */
-	page(params) {
-		return httpRequest({
-			url: prefix + "/roomType/list",
-			method: "get",
-			params,
-		});
-	}
-
-	/** 查询所有启用的房间类型(下拉框用) */
-	all() {
-		return httpRequest({
-			url: prefix + "/roomType/all",
-			method: "get",
-		});
-	}
-
-	/** 查询房间类型详情 */
-	queryById(id) {
-		return httpRequest({
-			url: prefix + "/roomType/queryById",
-			method: "get",
-			params: { id },
-		});
-	}
-
-	/** 新增/编辑房间类型 */
-	save(data) {
-		return httpRequest({
-			url: prefix + "/roomType/save",
-			method: "post",
-			data,
-		});
-	}
-
-	/** 切换状态 */
-	toggleStatus(id) {
-		return httpRequest({
-			url: prefix + "/roomType/toggleStatus",
-			method: "post",
-			params: { id },
-		});
-	}
-
-	/** 删除房间类型 */
-	delete(id) {
-		return httpRequest({
-			url: prefix + "/roomType/delete",
-			method: "post",
-			params: { id },
-		});
-	}
-}

+ 2 - 2
src/api/sys/OSSService.js

@@ -123,8 +123,8 @@ export const client = new OSS({
 	region: "oss-cn-hangzhou", // oss地址
 	accessKeyId: "LTAI5tKa6kzGr5EyPWJB4EcD", // 通过阿里云控制台创建的AccessKey ID。
 	accessKeySecret: "arHxB7ZPhizrBYf4844TtyaRctPMgW", // 通过阿里云控制台创建的AccessKey Secret。
-	bucket: "xgxm-test", // 仓库名字
-	// bucket: 'xg-pg', // 仓库名字
+	// bucket: "xgxm-test", // 仓库名字
+	bucket: 'xg-pg', // 仓库名字
 	useFetch: true, // 支持上传大于100KB的文件
 	secure: false, // 返回的url为https
 });

+ 0 - 165
src/store/modules/hotel.js

@@ -1,165 +0,0 @@
-/**
- * 酒店客房送餐系统 - Vuex Store Module
- * 管理主题、订单状态等全局状态
- */
-
-const state = {
-  // 主题配置:blue(商务深蓝) / gold(暖金雅致) / green(墨绿清新)
-  theme: localStorage.getItem('hotel_theme') || 'blue',
-  
-  // WebSocket连接状态
-  wsConnected: false,
-  wsToken: null,
-  
-  // 当前用户信息(餐厅/厨房/前台人员)
-  currentUser: null,
-  
-  // 未读新订单通知
-  unreadOrders: [],
-  
-  // 语音播报开关
-  voiceEnabled: true
-}
-
-const mutations = {
-  // 设置主题
-  SET_THEME(state, theme) {
-    state.theme = theme
-    localStorage.setItem('hotel_theme', theme)
-    // 更新HTML data-theme属性
-    document.documentElement.setAttribute('data-theme', theme)
-  },
-  
-  // 设置WebSocket连接状态
-  SET_WS_CONNECTED(state, connected) {
-    state.wsConnected = connected
-  },
-  
-  // 设置WebSocket Token
-  SET_WS_TOKEN(state, token) {
-    state.wsToken = token
-  },
-  
-  // 设置当前用户
-  SET_CURRENT_USER(state, user) {
-    state.currentUser = user
-  },
-  
-  // 添加未读订单
-  ADD_UNREAD_ORDER(state, order) {
-    state.unreadOrders.unshift(order)
-  },
-  
-  // 移除未读订单
-  REMOVE_UNREAD_ORDER(state, orderId) {
-    const index = state.unreadOrders.findIndex(o => o.id === orderId)
-    if (index > -1) {
-      state.unreadOrders.splice(index, 1)
-    }
-  },
-  
-  // 清空未读订单
-  CLEAR_UNREAD_ORDERS(state) {
-    state.unreadOrders = []
-  },
-  
-  // 切换语音播报
-  TOGGLE_VOICE(state) {
-    state.voiceEnabled = !state.voiceEnabled
-  }
-}
-
-const actions = {
-  // 初始化主题
-  initTheme({ commit }) {
-    const savedTheme = localStorage.getItem('hotel_theme') || 'blue'
-    commit('SET_THEME', savedTheme)
-  },
-  
-  // 连接WebSocket
-  connectWebSocket({ commit, state }) {
-    if (!state.wsToken) {
-      console.warn('WebSocket Token未获取,无法连接')
-      return
-    }
-    
-    const wsUrl = `ws://127.0.0.1:9529/ws/hotel?token=${state.wsToken}`
-    const ws = new WebSocket(wsUrl)
-    
-    ws.onopen = () => {
-      commit('SET_WS_CONNECTED', true)
-      console.log('WebSocket连接成功')
-    }
-    
-    ws.onmessage = (event) => {
-      const message = JSON.parse(event.data)
-      handleWebSocketMessage(message, commit)
-    }
-    
-    ws.onerror = (error) => {
-      console.error('WebSocket错误:', error)
-      commit('SET_WS_CONNECTED', false)
-    }
-    
-    ws.onclose = () => {
-      commit('SET_WS_CONNECTED', false)
-      console.log('WebSocket连接关闭,尝试重连...')
-      // 3秒后重连
-      setTimeout(() => {
-        if (state.wsToken) {
-          dispatch('connectWebSocket')
-        }
-      }, 3000)
-    }
-    
-    // 将ws实例保存到window供全局访问
-    window.hotelWS = ws
-  },
-  
-  // 断开WebSocket
-  disconnectWebSocket({ commit }) {
-    if (window.hotelWS) {
-      window.hotelWS.close()
-      window.hotelWS = null
-    }
-    commit('SET_WS_CONNECTED', false)
-  }
-}
-
-// 处理WebSocket消息
-function handleWebSocketMessage(message, commit) {
-  switch (message.type) {
-    case 'NEW_ORDER':
-      // 新订单通知
-      commit('ADD_UNREAD_ORDER', message.data)
-      playVoiceNotification(message.data)
-      break
-    case 'ORDER_STATUS_CHANGED':
-      // 订单状态变更
-      console.log('订单状态变更:', message.data)
-      break
-    default:
-      console.log('未知消息类型:', message.type)
-  }
-}
-
-// 语音播报新订单
-function playVoiceNotification(order) {
-  if (!('speechSynthesis' in window)) {
-    console.warn('浏览器不支持Web Speech API')
-    return
-  }
-  
-  const text = `新订单!房号${order.roomNo},金额${order.totalAmount}元`
-  const utterance = new SpeechSynthesisUtterance(text)
-  utterance.lang = 'zh-CN'
-  utterance.rate = 1.0
-  speechSynthesis.speak(utterance)
-}
-
-export default {
-  namespaced: true,
-  state,
-  mutations,
-  actions
-}

+ 0 - 158
src/utils/websocket.js

@@ -1,158 +0,0 @@
-/**
- * WebSocket工具类 - 酒店客房送餐系统
- * 用于实时接收新订单通知和状态变更
- */
-
-class HotelWebSocket {
-  constructor() {
-    this.ws = null
-    this.reconnectTimer = null
-    this.heartbeatTimer = null
-    this.isConnected = false
-    this.token = null
-  }
-  
-  /**
-   * 连接WebSocket
-   * @param {string} token - WebSocket认证Token
-   * @param {Function} onMessage - 消息回调
-   * @param {Function} onError - 错误回调
-   */
-  connect(token, onMessage, onError) {
-    if (this.isConnected) {
-      console.warn('WebSocket已连接')
-      return
-    }
-    
-    this.token = token
-    const wsUrl = `ws://127.0.0.1:9529/ws/hotel?token=${token}`
-    
-    try {
-      this.ws = new WebSocket(wsUrl)
-      
-      this.ws.onopen = () => {
-        this.isConnected = true
-        console.log('✅ WebSocket连接成功')
-        this.startHeartbeat()
-        if (onMessage) {
-          this.ws.addEventListener('message', onMessage)
-        }
-      }
-      
-      this.ws.onmessage = (event) => {
-        const message = JSON.parse(event.data)
-        console.log('📨 收到WebSocket消息:', message)
-        
-        // 根据消息类型处理
-        if (message.type === 'NEW_ORDER') {
-          this.handleNewOrder(message.data)
-        } else if (message.type === 'ORDER_STATUS_CHANGED') {
-          this.handleStatusChange(message.data)
-        }
-      }
-      
-      this.ws.onerror = (error) => {
-        console.error('❌ WebSocket错误:', error)
-        this.isConnected = false
-        if (onError) {
-          onError(error)
-        }
-      }
-      
-      this.ws.onclose = () => {
-        console.log('🔌 WebSocket连接关闭')
-        this.isConnected = false
-        this.stopHeartbeat()
-        this.attemptReconnect(onMessage, onError)
-      }
-    } catch (error) {
-      console.error('WebSocket连接失败:', error)
-      this.attemptReconnect(onMessage, onError)
-    }
-  }
-  
-  /**
-   * 断开连接
-   */
-  disconnect() {
-    if (this.ws) {
-      this.ws.close()
-      this.ws = null
-    }
-    this.isConnected = false
-    this.stopHeartbeat()
-    if (this.reconnectTimer) {
-      clearTimeout(this.reconnectTimer)
-      this.reconnectTimer = null
-    }
-  }
-  
-  /**
-   * 发送消息
-   */
-  send(data) {
-    if (this.isConnected && this.ws.readyState === WebSocket.OPEN) {
-      this.ws.send(JSON.stringify(data))
-    } else {
-      console.warn('WebSocket未连接,无法发送消息')
-    }
-  }
-  
-  /**
-   * 尝试重连
-   */
-  attemptReconnect(onMessage, onError) {
-    if (this.reconnectTimer) {
-      return
-    }
-    
-    console.log('⏳ 3秒后尝试重连...')
-    this.reconnectTimer = setTimeout(() => {
-      this.reconnectTimer = null
-      if (this.token) {
-        this.connect(this.token, onMessage, onError)
-      }
-    }, 3000)
-  }
-  
-  /**
-   * 开始心跳
-   */
-  startHeartbeat() {
-    this.heartbeatTimer = setInterval(() => {
-      if (this.isConnected) {
-        this.send({ type: 'PING' })
-      }
-    }, 30000) // 每30秒发送一次心跳
-  }
-  
-  /**
-   * 停止心跳
-   */
-  stopHeartbeat() {
-    if (this.heartbeatTimer) {
-      clearInterval(this.heartbeatTimer)
-      this.heartbeatTimer = null
-    }
-  }
-  
-  /**
-   * 处理新订单
-   */
-  handleNewOrder(order) {
-    console.log('🆕 新订单:', order)
-    // 触发自定义事件,供Vue组件监听
-    window.dispatchEvent(new CustomEvent('hotel-new-order', { detail: order }))
-  }
-  
-  /**
-   * 处理订单状态变更
-   */
-  handleStatusChange(data) {
-    console.log('📊 订单状态变更:', data)
-    window.dispatchEvent(new CustomEvent('hotel-order-status-changed', { detail: data }))
-  }
-}
-
-// 导出单例
-export default new HotelWebSocket()

+ 0 - 168
src/views/hotel/admin/BusinessHoursForm.vue

@@ -1,168 +0,0 @@
-<template>
-	<div>
-		<el-dialog :title="title" :close-on-click-modal="false" draggable width="500px" @close="close"
-			@keyup.enter.native="doSubmit" v-model="visible">
-			<el-form :model="inputForm" ref="inputForm" v-loading="loading" :rules="rules" label-width="100px"
-				@submit.native.prevent>
-				<!-- 餐段类型下拉 -->
-				<el-form-item label="餐段类型" prop="mealType">
-					<el-select v-model="inputForm.mealType" placeholder="请选择餐段类型" clearable style="width: 100%"
-						@change="onMealTypeChange">
-						<el-option label="早餐" value="breakfast"></el-option>
-						<el-option label="午餐" value="lunch"></el-option>
-						<el-option label="晚餐" value="dinner"></el-option>
-						<el-option label="夜宵" value="night"></el-option>
-					</el-select>
-				</el-form-item>
-				<!-- 餐段名称(可手动修改) -->
-				<el-form-item label="餐段名称" prop="mealName">
-					<el-input v-model="inputForm.mealName" placeholder="如:早餐、午餐" clearable></el-input>
-				</el-form-item>
-				<!-- 星期选择 -->
-				<el-form-item label="适用星期" prop="dayOfWeek">
-					<el-select v-model="inputForm.dayOfWeek" placeholder="每天(不限制星期)" clearable style="width: 100%">
-						<el-option label="每天" :value="null"></el-option>
-						<el-option v-for="(day, index) in weekDays" :key="index" :label="day" :value="index"></el-option>
-					</el-select>
-				</el-form-item>
-				<!-- 开始时间 -->
-				<el-form-item label="开始时间" prop="startTime">
-					<el-time-select v-model="inputForm.startTime" :picker-options="{ start: '00:00', step: '00:30', end: '23:30' }"
-						placeholder="请选择开始时间" style="width: 100%"></el-time-select>
-				</el-form-item>
-				<!-- 结束时间 -->
-				<el-form-item label="结束时间" prop="endTime">
-					<el-time-select v-model="inputForm.endTime" :picker-options="{ start: '00:00', step: '00:30', end: '23:59', min: inputForm.startTime }"
-						placeholder="请选择结束时间" style="width: 100%"></el-time-select>
-				</el-form-item>
-				<!-- 排序号 -->
-				<el-form-item label="排序号" prop="sortOrder">
-					<el-input-number v-model="inputForm.sortOrder" :min="0" :max="999" style="width: 100%"></el-input-number>
-				</el-form-item>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-					<el-button type="primary" @click="doSubmit()" icon="el-icon-circle-check"
-						v-noMoreClick>确定</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import HotelBusinessHoursService from '@/api/hotel/HotelBusinessHoursService'
-
-/** 餐段类型与默认名称映射 */
-const MEAL_TYPE_NAMES = {
-	breakfast: '早餐',
-	lunch: '午餐',
-	dinner: '晚餐',
-	night: '夜宵'
-}
-
-export default {
-	data() {
-		return {
-			title: '',
-			method: '',
-			visible: false,
-			loading: false,
-			/** 星期映射:索引对应Calendar常量(0=周日) */
-			weekDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
-			rules: {
-				mealType: [{ required: true, message: '请选择餐段类型', trigger: 'change' }],
-				mealName: [{ required: true, message: '请输入餐段名称', trigger: 'blur' }],
-				startTime: [{ required: true, message: '请选择开始时间', trigger: 'change' }],
-				endTime: [{ required: true, message: '请选择结束时间', trigger: 'change' }]
-			},
-			inputForm: {
-				mealType: '',
-				mealName: '',
-				dayOfWeek: null,
-				startTime: '',
-				endTime: '',
-				sortOrder: 0
-			}
-		}
-	},
-	hotelBusinessHoursService: null,
-	created() {
-		this.hotelBusinessHoursService = new HotelBusinessHoursService()
-	},
-	methods: {
-		/**
-		 * 初始化弹窗
-		 * @param {string} method - add或edit
-		 * @param {string} id - 编辑时传入ID
-		 */
-		init(method, id) {
-			this.method = method
-			this.inputForm = {
-				mealType: '',
-				mealName: '',
-				dayOfWeek: null,
-				startTime: '',
-				endTime: '',
-				sortOrder: 0
-			}
-			if (method === 'add') {
-				this.title = '新增营业时段'
-			} else if (method === 'edit') {
-				this.title = '编辑营业时段'
-				this.inputForm.id = id
-			}
-			this.visible = true
-			this.loading = false
-			this.$nextTick(() => {
-				this.$refs.inputForm.resetFields()
-				if (method === 'edit') {
-					this.loading = true
-					this.hotelBusinessHoursService.queryById(id).then((data) => {
-						this.inputForm = {
-							id: data.id,
-							mealType: data.mealType || '',
-							mealName: data.mealName || '',
-							dayOfWeek: data.dayOfWeek !== null && data.dayOfWeek !== undefined ? data.dayOfWeek : null,
-							startTime: data.startTime || '',
-							endTime: data.endTime || '',
-							sortOrder: data.sortOrder || 0
-						}
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		/** 餐段类型变化时自动填充名称 */
-		onMealTypeChange(val) {
-			if (val && MEAL_TYPE_NAMES[val]) {
-				this.inputForm.mealName = MEAL_TYPE_NAMES[val]
-			}
-		},
-		/** 提交表单 */
-		doSubmit() {
-			this.$refs.inputForm.validate((valid) => {
-				if (valid) {
-					this.loading = true
-					this.hotelBusinessHoursService.save(this.inputForm).then(() => {
-						this.$message.success('操作成功')
-						this.close()
-						this.$emit('refreshList')
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		/** 关闭弹窗 */
-		close() {
-			this.$refs.inputForm && this.$refs.inputForm.resetFields()
-			this.visible = false
-		}
-	}
-}
-</script>

+ 0 - 180
src/views/hotel/admin/BusinessHoursManagement.vue

@@ -1,180 +0,0 @@
-<template>
-	<div class="page">
-		<!-- 搜索栏 -->
-		<el-form :inline="true" v-if="searchVisible" class="query-form m-b-10" ref="searchForm" :model="searchForm"
-			@keyup.enter.native="refreshList()" @submit.native.prevent>
-			<el-form-item label="餐段" prop="mealType">
-				<el-select v-model="searchForm.mealType" placeholder="请选择餐段" clearable style="width: 120px">
-					<el-option label="早餐" value="breakfast"></el-option>
-					<el-option label="午餐" value="lunch"></el-option>
-					<el-option label="晚餐" value="dinner"></el-option>
-					<el-option label="夜宵" value="night"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item label="状态" prop="status">
-				<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px">
-					<el-option label="启用" value="ENABLED"></el-option>
-					<el-option label="禁用" value="DISABLED"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item>
-				<el-button type="primary" @click="refreshList()" icon="el-icon-search">查询</el-button>
-				<el-button @click="resetSearch()" icon="el-icon-refresh-right">重置</el-button>
-			</el-form-item>
-		</el-form>
-
-		<div class="jp-table top">
-			<vxe-toolbar :refresh="{ query: refreshList }" custom>
-				<template #buttons>
-					<el-button v-if="hasPermission('hotel:admin:businessHours:add')" type="primary" icon="el-icon-plus"
-						@click="add()">新增时段</el-button>
-				</template>
-				<template #tools>
-					<vxe-button text type="primary" :title="searchVisible ? '收起检索' : '展开检索'" icon="vxe-icon-search"
-						class="tool-btn" @click="searchVisible = !searchVisible"></vxe-button>
-				</template>
-			</vxe-toolbar>
-			<div style="height: calc(100% - 90px)">
-				<vxe-table border="inner" auto-resize resizable height="auto" :loading="loading" ref="dataTable"
-					show-header-overflow show-overflow highlight-hover-row :menu-config="{}" :data="dataList"
-					:checkbox-config="{}">
-					<vxe-column type="seq" width="60" title="序号"></vxe-column>
-					<vxe-column title="餐段名称" field="mealName" min-width="120" align="center"></vxe-column>
-					<vxe-column title="星期" width="120" align="center">
-						<template #default="scope">
-							{{ scope.row.dayOfWeek !== null && scope.row.dayOfWeek !== undefined ? weekDays[scope.row.dayOfWeek] : '每天' }}
-						</template>
-					</vxe-column>
-					<vxe-column title="开始时间" field="startTime" width="120" align="center"></vxe-column>
-					<vxe-column title="结束时间" field="endTime" width="120" align="center"></vxe-column>
-					<vxe-column title="状态" field="status" width="100" align="center">
-						<template #default="scope">
-							<el-tag :type="scope.row.status === 'ENABLED' ? 'success' : 'info'" effect="plain">
-								{{ scope.row.status === 'ENABLED' ? '启用' : '禁用' }}
-							</el-tag>
-						</template>
-					</vxe-column>
-					<vxe-column title="排序号" field="sortOrder" width="100" align="center"></vxe-column>
-					<vxe-column title="操作" width="200" fixed="right" align="center">
-						<template #default="scope">
-							<el-button v-if="hasPermission('hotel:admin:businessHours:edit')" text type="primary" size="small"
-								@click="edit(scope.row.id)">编辑</el-button>
-							<el-button v-if="hasPermission('hotel:admin:businessHours:edit')" text type="primary" size="small"
-								@click="doToggle(scope.row)">{{ scope.row.status === 'ENABLED' ? '禁用' : '启用' }}</el-button>
-							<el-button v-if="hasPermission('hotel:admin:businessHours:delete')" text type="danger" size="small"
-								@click="doDelete(scope.row.id)">删除</el-button>
-						</template>
-					</vxe-column>
-				</vxe-table>
-				<vxe-pager background :current-page="tablePage.currentPage" :page-size="tablePage.pageSize"
-					:total="tablePage.total" :page-sizes="[10, 20, 100, 1000, { label: '全量数据', value: 1000000 }]"
-					:layouts="['PrevPage', 'JumpNumber', 'NextPage', 'FullJump', 'Sizes', 'Total']"
-					@page-change="currentChangeHandle">
-				</vxe-pager>
-			</div>
-		</div>
-		<BusinessHoursForm ref="businessHoursForm" @refreshList="refreshList"></BusinessHoursForm>
-	</div>
-</template>
-
-<script>
-import HotelBusinessHoursService from '@/api/hotel/HotelBusinessHoursService'
-import BusinessHoursForm from './BusinessHoursForm'
-
-export default {
-	data() {
-		return {
-			searchVisible: true,
-			searchForm: {
-				mealType: '',
-				status: ''
-			},
-			dataList: [],
-			tablePage: {
-				total: 0,
-				currentPage: 1,
-				pageSize: 10,
-				orders: []
-			},
-			loading: false,
-			weekDays: ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
-		}
-	},
-	hotelBusinessHoursService: null,
-	created() {
-		this.hotelBusinessHoursService = new HotelBusinessHoursService()
-	},
-	components: {
-		BusinessHoursForm
-	},
-	mounted() {
-		this.refreshList()
-	},
-	activated() {
-		this.refreshList()
-	},
-	methods: {
-		add() {
-			this.$refs.businessHoursForm.init('add', '')
-		},
-		edit(id) {
-			this.$refs.businessHoursForm.init('edit', id)
-		},
-		refreshList() {
-			this.loading = true
-			this.hotelBusinessHoursService.page({
-				'current': this.tablePage.currentPage,
-				'size': this.tablePage.pageSize,
-				'orders': this.tablePage.orders,
-				...this.searchForm
-			}).then((data) => {
-				this.dataList = data.records || []
-				this.tablePage.total = data.total || 0
-				this.tablePage.currentPage = data.current || 1
-				this.loading = false
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		currentChangeHandle({ currentPage, pageSize }) {
-			this.tablePage.currentPage = currentPage
-			this.tablePage.pageSize = pageSize
-			this.refreshList()
-		},
-		resetSearch() {
-			this.$refs.searchForm.resetFields()
-			this.tablePage.currentPage = 1
-			this.refreshList()
-		},
-		doToggle(row) {
-			const action = row.status === 'ENABLED' ? '禁用' : '启用'
-			this.$confirm(`确定${action}该营业时段吗?`, '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.hotelBusinessHoursService.toggle(row.id).then(() => {
-					this.$message.success('操作成功')
-					this.refreshList()
-				})
-			})
-		},
-		doDelete(id) {
-			this.$confirm('确定删除该营业时段吗?', '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelBusinessHoursService.delete(id).then(() => {
-					this.$message.success('删除成功')
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		}
-	}
-}
-</script>

+ 0 - 22
src/views/hotel/admin/DishManagement.vue

@@ -1,22 +0,0 @@
-<template>
-  <div class="admin-dish-page">
-    <!-- 后台管理 - 菜品管理页 -->
-    <h2>后台管理 - 菜品管理</h2>
-    <p>待实现:参考HTML原型 admin-dish.html</p>
-    <p>关键功能:菜品CRUD、分类管理、上下架</p>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-
-onMounted(() => {
-  console.log('AdminDishManagement mounted')
-})
-</script>
-
-<style scoped>
-.admin-dish-page {
-  padding: 20px;
-}
-</style>

+ 0 - 130
src/views/hotel/admin/DishSpecForm.vue

@@ -1,130 +0,0 @@
-<template>
-	<div>
-		<el-dialog :title="title" :close-on-click-modal="false" draggable width="500px" @close="close"
-			@keyup.enter.native="doSubmit" v-model="visible">
-			<el-form :model="inputForm" ref="inputForm" v-loading="loading" :rules="rules" label-width="100px"
-				@submit.native.prevent>
-				<el-form-item label="关联菜品" prop="dishId">
-					<el-select v-model="inputForm.dishId" placeholder="请选择菜品" clearable filterable style="width: 100%">
-						<el-option v-for="item in dishList" :key="item.id" :label="item.name" :value="item.id"></el-option>
-					</el-select>
-				</el-form-item>
-				<el-form-item label="规格组名称" prop="groupName">
-					<el-input v-model="inputForm.groupName" placeholder="如:熟度、酱汁、份量" clearable></el-input>
-				</el-form-item>
-				<el-form-item label="是否必选" prop="required">
-					<el-radio-group v-model="inputForm.required">
-						<el-radio :label="1">必选</el-radio>
-						<el-radio :label="0">可选</el-radio>
-					</el-radio-group>
-				</el-form-item>
-				<el-form-item label="排序号" prop="sortOrder">
-					<el-input-number v-model="inputForm.sortOrder" :min="0" :max="999" style="width: 100%"></el-input-number>
-				</el-form-item>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-					<el-button type="primary" @click="doSubmit()" icon="el-icon-circle-check"
-						v-noMoreClick>确定</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import HotelDishSpecService from '@/api/hotel/HotelDishSpecService'
-import HotelDishService from '@/api/hotel/HotelDishService'
-
-export default {
-	data() {
-		return {
-			title: '',
-			method: '',
-			visible: false,
-			loading: false,
-			dishList: [],
-			rules: {
-				dishId: [{ required: true, message: '请选择关联菜品', trigger: 'change' }],
-				groupName: [{ required: true, message: '请输入规格组名称', trigger: 'blur' }]
-			},
-			inputForm: {
-				dishId: '',
-				groupName: '',
-				required: 0,
-				sortOrder: 0
-			}
-		}
-	},
-	hotelDishSpecService: null,
-	hotelDishService: null,
-	created() {
-		this.hotelDishSpecService = new HotelDishSpecService()
-		this.hotelDishService = new HotelDishService()
-	},
-	methods: {
-		/** 加载菜品列表(下拉框用) */
-		loadDishList() {
-			this.hotelDishService.page({ 'current': 1, 'size': 10000 }).then((data) => {
-				this.dishList = (data.records || []).filter(d => d.status === 'ON_SALE')
-			})
-		},
-		init(method, id) {
-			this.method = method
-			this.inputForm = {
-				dishId: '',
-				groupName: '',
-				required: 0,
-				sortOrder: 0
-			}
-			if (method === 'add') {
-				this.title = '新增规格组'
-			} else if (method === 'edit') {
-				this.title = '编辑规格组'
-				this.inputForm.id = id
-			}
-			this.visible = true
-			this.loading = false
-			this.loadDishList()
-			this.$nextTick(() => {
-				this.$refs.inputForm.resetFields()
-				if (method === 'edit') {
-					this.loading = true
-					this.hotelDishSpecService.queryGroupById(id).then((data) => {
-						this.inputForm = {
-							id: data.id,
-							dishId: data.dishId,
-							groupName: data.groupName,
-							required: data.required || 0,
-							sortOrder: data.sortOrder || 0
-						}
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		doSubmit() {
-			this.$refs.inputForm.validate((valid) => {
-				if (valid) {
-					this.loading = true
-					this.hotelDishSpecService.saveGroup(this.inputForm).then(() => {
-						this.$message.success('操作成功')
-						this.close()
-						this.$emit('refreshList')
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		close() {
-			this.$refs.inputForm && this.$refs.inputForm.resetFields()
-			this.visible = false
-		}
-	}
-}
-</script>

+ 0 - 237
src/views/hotel/admin/DishSpecManagement.vue

@@ -1,237 +0,0 @@
-<template>
-	<div class="page">
-		<!-- 搜索栏 -->
-		<el-form :inline="true" v-if="searchVisible" class="query-form m-b-10" ref="searchForm" :model="searchForm"
-			@keyup.enter.native="refreshList()" @submit.native.prevent>
-			<el-form-item label="菜品名称" prop="dishName">
-				<el-input v-model="searchForm.dishName" placeholder="请输入菜品名称" clearable></el-input>
-			</el-form-item>
-			<el-form-item label="规格组名称" prop="groupName">
-				<el-input v-model="searchForm.groupName" placeholder="请输入规格组名称" clearable></el-input>
-			</el-form-item>
-			<el-form-item>
-				<el-button type="primary" @click="refreshList()" icon="el-icon-search">查询</el-button>
-				<el-button @click="resetSearch()" icon="el-icon-refresh-right">重置</el-button>
-			</el-form-item>
-		</el-form>
-
-		<div class="jp-table top">
-			<vxe-toolbar :refresh="{ query: refreshList }" custom>
-				<template #buttons>
-					<el-button v-if="hasPermission('hotel:admin:dishSpec:add')" type="primary" icon="el-icon-plus"
-						@click="addGroup()">新增规格组</el-button>
-				</template>
-				<template #tools>
-					<vxe-button text type="primary" :title="searchVisible ? '收起检索' : '展开检索'" icon="vxe-icon-search"
-						class="tool-btn" @click="searchVisible = !searchVisible"></vxe-button>
-				</template>
-			</vxe-toolbar>
-			<div style="height: calc(100% - 90px)">
-				<vxe-table border="inner" auto-resize resizable height="auto" :loading="loading" ref="dataTable"
-					show-header-overflow show-overflow highlight-hover-row :menu-config="{}" :data="dataList"
-					:checkbox-config="{}">
-					<vxe-column type="seq" width="60" title="序号"></vxe-column>
-					<vxe-column title="菜品名称" field="dishName" min-width="150" align="center"></vxe-column>
-					<vxe-column title="规格组名称" field="groupName" min-width="150" align="center"></vxe-column>
-					<vxe-column title="是否必选" field="required" width="100" align="center">
-						<template #default="scope">
-							<el-tag :type="scope.row.required === 1 ? 'danger' : 'info'" effect="plain" size="small">
-								{{ scope.row.required === 1 ? '必选' : '可选' }}
-							</el-tag>
-						</template>
-					</vxe-column>
-					<vxe-column title="选项数量" width="100" align="center">
-						<template #default="scope">
-							<el-button text type="primary" size="small" @click="showOptions(scope.row)">
-								{{ (scope.row.options || []).length }} 个选项
-							</el-button>
-						</template>
-					</vxe-column>
-					<vxe-column title="排序号" field="sortOrder" width="100" align="center"></vxe-column>
-					<vxe-column title="操作" width="200" fixed="right" align="center">
-						<template #default="scope">
-							<el-button v-if="hasPermission('hotel:admin:dishSpec:edit')" text type="primary" size="small"
-								@click="editGroup(scope.row.id)">编辑</el-button>
-							<el-button text type="primary" size="small" @click="showOptions(scope.row)">管理选项</el-button>
-							<el-button v-if="hasPermission('hotel:admin:dishSpec:delete')" text type="danger" size="small"
-								@click="doDeleteGroup(scope.row.id)">删除</el-button>
-						</template>
-					</vxe-column>
-				</vxe-table>
-				<vxe-pager background :current-page="tablePage.currentPage" :page-size="tablePage.pageSize"
-					:total="tablePage.total" :page-sizes="[10, 20, 100, 1000, { label: '全量数据', value: 1000000 }]"
-					:layouts="['PrevPage', 'JumpNumber', 'NextPage', 'FullJump', 'Sizes', 'Total']"
-					@page-change="currentChangeHandle">
-				</vxe-pager>
-			</div>
-		</div>
-
-		<!-- 规格组表单弹窗 -->
-		<DishSpecForm ref="dishSpecForm" @refreshList="refreshList"></DishSpecForm>
-
-		<!-- 规格选项管理弹窗 -->
-		<el-dialog title="规格选项管理" :close-on-click-modal="false" draggable width="600px" v-model="optionsVisible">
-			<div v-if="currentGroup">
-				<p style="margin-bottom: 12px; color: #606266;">
-					规格组:<strong>{{ currentGroup.groupName }}</strong>
-					({{ currentGroup.required === 1 ? '必选' : '可选' }})
-				</p>
-				<el-button type="primary" size="small" icon="el-icon-plus" @click="addOption()" style="margin-bottom: 12px;">
-					新增选项
-				</el-button>
-				<vxe-table border="inner" auto-resize resizable height="300" :data="optionList"
-					show-header-overflow show-overflow>
-					<vxe-column type="seq" width="60" title="序号"></vxe-column>
-					<vxe-column title="选项名称" field="optionName" min-width="150" align="center"></vxe-column>
-					<vxe-column title="加价金额" field="priceExtra" width="120" align="center">
-						<template #default="scope">
-							{{ scope.row.priceExtra > 0 ? '+¥' + scope.row.priceExtra : '-' }}
-						</template>
-					</vxe-column>
-					<vxe-column title="排序号" field="sortOrder" width="100" align="center"></vxe-column>
-					<vxe-column title="操作" width="150" fixed="right" align="center">
-						<template #default="scope">
-							<el-button text type="primary" size="small" @click="editOption(scope.row.id)">编辑</el-button>
-							<el-button text type="danger" size="small" @click="doDeleteOption(scope.row.id)">删除</el-button>
-						</template>
-					</vxe-column>
-				</vxe-table>
-			</div>
-		</el-dialog>
-
-		<!-- 规格选项表单弹窗 -->
-		<DishSpecOptionForm ref="dishSpecOptionForm" @refreshOptions="loadOptions"></DishSpecOptionForm>
-	</div>
-</template>
-
-<script>
-import HotelDishSpecService from '@/api/hotel/HotelDishSpecService'
-import DishSpecForm from './DishSpecForm'
-import DishSpecOptionForm from './DishSpecOptionForm'
-
-export default {
-	data() {
-		return {
-			searchVisible: true,
-			searchForm: {
-				dishName: '',
-				groupName: ''
-			},
-			dataList: [],
-			tablePage: {
-				total: 0,
-				currentPage: 1,
-				pageSize: 10,
-				orders: []
-			},
-			loading: false,
-			// 选项管理
-			optionsVisible: false,
-			currentGroup: null,
-			optionList: []
-		}
-	},
-	hotelDishSpecService: null,
-	created() {
-		this.hotelDishSpecService = new HotelDishSpecService()
-	},
-	components: {
-		DishSpecForm,
-		DishSpecOptionForm
-	},
-	mounted() {
-		this.refreshList()
-	},
-	activated() {
-		this.refreshList()
-	},
-	methods: {
-		/** 刷新规格组列表 */
-		refreshList() {
-			this.loading = true
-			this.hotelDishSpecService.groupPage({
-				'current': this.tablePage.currentPage,
-				'size': this.tablePage.pageSize,
-				'orders': this.tablePage.orders,
-				...this.searchForm
-			}).then((data) => {
-				this.dataList = data.records || []
-				this.tablePage.total = data.total || 0
-				this.tablePage.currentPage = data.current || 1
-				this.loading = false
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		currentChangeHandle({ currentPage, pageSize }) {
-			this.tablePage.currentPage = currentPage
-			this.tablePage.pageSize = pageSize
-			this.refreshList()
-		},
-		resetSearch() {
-			this.$refs.searchForm.resetFields()
-			this.tablePage.currentPage = 1
-			this.refreshList()
-		},
-		/** 新增规格组 */
-		addGroup() {
-			this.$refs.dishSpecForm.init('add', '')
-		},
-		/** 编辑规格组 */
-		editGroup(id) {
-			this.$refs.dishSpecForm.init('edit', id)
-		},
-		/** 删除规格组 */
-		doDeleteGroup(id) {
-			this.$confirm('确定删除该规格组吗?(将同时删除其下所有选项)', '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelDishSpecService.deleteGroup(id).then(() => {
-					this.$message.success('删除成功')
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		},
-		/** 显示规格选项管理弹窗 */
-		showOptions(group) {
-			this.currentGroup = group
-			this.optionsVisible = true
-			this.loadOptions()
-		},
-		/** 加载规格选项列表 */
-		loadOptions() {
-			if (!this.currentGroup) return
-			this.hotelDishSpecService.findOptionsByGroupId(this.currentGroup.id).then((data) => {
-				this.optionList = Array.isArray(data) ? data : []
-			})
-		},
-		/** 新增选项 */
-		addOption() {
-			this.$refs.dishSpecOptionForm.init('add', '', this.currentGroup.id)
-		},
-		/** 编辑选项 */
-		editOption(id) {
-			this.$refs.dishSpecOptionForm.init('edit', id, this.currentGroup.id)
-		},
-		/** 删除选项 */
-		doDeleteOption(id) {
-			this.$confirm('确定删除该规格选项吗?', '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.hotelDishSpecService.deleteOption(id).then(() => {
-					this.$message.success('删除成功')
-					this.loadOptions()
-				})
-			})
-		}
-	}
-}
-</script>

+ 0 - 112
src/views/hotel/admin/DishSpecOptionForm.vue

@@ -1,112 +0,0 @@
-<template>
-	<div>
-		<el-dialog :title="title" :close-on-click-modal="false" draggable width="460px" @close="close"
-			@keyup.enter.native="doSubmit" v-model="visible">
-			<el-form :model="inputForm" ref="inputForm" v-loading="loading" :rules="rules" label-width="80px"
-				@submit.native.prevent>
-				<el-form-item label="选项名称" prop="optionName">
-					<el-input v-model="inputForm.optionName" placeholder="如:三分熟、黑椒汁" clearable></el-input>
-				</el-form-item>
-				<el-form-item label="加价金额" prop="priceExtra">
-					<el-input-number v-model="inputForm.priceExtra" :min="0" :max="9999" :precision="2"
-						style="width: 100%"></el-input-number>
-				</el-form-item>
-				<el-form-item label="排序号" prop="sortOrder">
-					<el-input-number v-model="inputForm.sortOrder" :min="0" :max="999" style="width: 100%"></el-input-number>
-				</el-form-item>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-					<el-button type="primary" @click="doSubmit()" icon="el-icon-circle-check"
-						v-noMoreClick>确定</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import HotelDishSpecService from '@/api/hotel/HotelDishSpecService'
-
-export default {
-	data() {
-		return {
-			title: '',
-			method: '',
-			visible: false,
-			loading: false,
-			groupId: '',
-			rules: {
-				optionName: [{ required: true, message: '请输入选项名称', trigger: 'blur' }]
-			},
-			inputForm: {
-				optionName: '',
-				priceExtra: 0,
-				sortOrder: 0
-			}
-		}
-	},
-	hotelDishSpecService: null,
-	created() {
-		this.hotelDishSpecService = new HotelDishSpecService()
-	},
-	methods: {
-		init(method, id, groupId) {
-			this.method = method
-			this.groupId = groupId
-			this.inputForm = {
-				optionName: '',
-				priceExtra: 0,
-				sortOrder: 0
-			}
-			if (method === 'add') {
-				this.title = '新增规格选项'
-				this.inputForm.groupId = groupId
-			} else if (method === 'edit') {
-				this.title = '编辑规格选项'
-				this.inputForm.id = id
-			}
-			this.visible = true
-			this.loading = false
-			this.$nextTick(() => {
-				this.$refs.inputForm.resetFields()
-				if (method === 'edit') {
-					this.loading = true
-					this.hotelDishSpecService.queryOptionById(id).then((data) => {
-						this.inputForm = {
-							id: data.id,
-							groupId: data.groupId,
-							optionName: data.optionName,
-							priceExtra: data.priceExtra || 0,
-							sortOrder: data.sortOrder || 0
-						}
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		doSubmit() {
-			this.$refs.inputForm.validate((valid) => {
-				if (valid) {
-					this.loading = true
-					this.hotelDishSpecService.saveOption(this.inputForm).then(() => {
-						this.$message.success('操作成功')
-						this.close()
-						this.$emit('refreshOptions')
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		close() {
-			this.$refs.inputForm && this.$refs.inputForm.resetFields()
-			this.visible = false
-		}
-	}
-}
-</script>

+ 0 - 102
src/views/hotel/admin/HotelConfigManagement.vue

@@ -1,102 +0,0 @@
-<template>
-	<div class="page">
-		<div class="jp-table top">
-			<vxe-toolbar :refresh="{ query: loadConfig }" custom>
-				<template #tools>
-					<vxe-button text type="primary" :title="searchVisible ? '收起检索' : '展开检索'" icon="vxe-icon-search"
-						class="tool-btn" @click="searchVisible = !searchVisible"></vxe-button>
-				</template>
-			</vxe-toolbar>
-			<div style="padding: 20px;" v-loading="loading">
-				<el-form :model="configForm" ref="configForm" label-width="140px" style="max-width: 600px; margin: 0 auto;">
-					<el-form-item label="酒店名称" prop="hotelName">
-						<el-input v-model="configForm.hotelName" placeholder="请输入酒店名称" clearable></el-input>
-					</el-form-item>
-					<el-form-item label="主题配色" prop="theme">
-						<el-select v-model="configForm.theme" placeholder="请选择主题配色" style="width: 100%">
-							<el-option label="商务深蓝" value="blue"></el-option>
-							<el-option label="暖金雅致" value="gold"></el-option>
-							<el-option label="墨绿清新" value="green"></el-option>
-						</el-select>
-					</el-form-item>
-					<el-form-item label="配送费(元)" prop="deliveryFee">
-						<el-input-number v-model="configForm.deliveryFee" :min="0" :max="999" :precision="2"
-							style="width: 100%"></el-input-number>
-					</el-form-item>
-					<el-form-item label="预计配送时长" prop="estimatedDeliveryMinutes">
-						<el-input-number v-model="configForm.estimatedDeliveryMinutes" :min="1" :max="120"
-							style="width: 100%"></el-input-number>
-						<span style="color: #909399; font-size: 12px; margin-left: 8px;">分钟</span>
-					</el-form-item>
-					<el-form-item>
-						<el-button type="primary" @click="doSave()" icon="el-icon-circle-check" v-noMoreClick>
-							保存配置
-						</el-button>
-						<el-button @click="loadConfig()" icon="el-icon-refresh-right">重置</el-button>
-					</el-form-item>
-				</el-form>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script>
-import HotelConfigService from '@/api/hotel/HotelConfigService'
-
-/**
- * 酒店配置管理页面
- * 管理酒店基本信息:名称、主题、配送费、预计配送时长
- */
-export default {
-	data() {
-		return {
-			searchVisible: true,
-			loading: false,
-			configForm: {
-				hotelName: '',
-				theme: 'blue',
-				deliveryFee: 0,
-				estimatedDeliveryMinutes: 30
-			}
-		}
-	},
-	hotelConfigService: null,
-	created() {
-		this.hotelConfigService = new HotelConfigService()
-	},
-	mounted() {
-		this.loadConfig()
-	},
-	activated() {
-		this.loadConfig()
-	},
-	methods: {
-		/** 加载酒店配置 */
-		loadConfig() {
-			this.loading = true
-			this.hotelConfigService.get().then((data) => {
-				const config = data.config || data || {}
-				this.configForm = {
-					hotelName: config.hotelName || '',
-					theme: config.theme || 'blue',
-					deliveryFee: config.deliveryFee || 0,
-					estimatedDeliveryMinutes: config.estimatedDeliveryMinutes || 30
-				}
-				this.loading = false
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		/** 保存酒店配置 */
-		doSave() {
-			this.loading = true
-			this.hotelConfigService.save(this.configForm).then(() => {
-				this.$message.success('配置保存成功')
-				this.loading = false
-			}).catch(() => {
-				this.loading = false
-			})
-		}
-	}
-}
-</script>

+ 0 - 151
src/views/hotel/admin/QrCodeDialog.vue

@@ -1,151 +0,0 @@
-<template>
-	<div>
-		<el-dialog title="房间二维码" :close-on-click-modal="false" draggable width="480px" @close="close"
-			v-model="visible">
-			<div v-loading="loading" class="qr-dialog-content">
-				<div class="qr-info">
-					<span class="qr-room-label">房间号</span>
-					<span class="qr-room-no">{{ roomNo }}</span>
-				</div>
-				<div class="qr-image-wrapper">
-					<img v-if="qrCodeImage" :src="'data:image/png;base64,' + qrCodeImage" alt="二维码" />
-					<div v-else class="qr-placeholder">
-						<el-icon style="font-size: 48px; color: #c0c4cc;"><Picture /></el-icon>
-						<p>暂无二维码</p>
-					</div>
-				</div>
-				<div class="qr-url-box" v-if="qrCodeUrl" :title="qrCodeUrl">
-					<el-icon><Link /></el-icon>
-					<span>{{ qrCodeUrl }}</span>
-				</div>
-				<div class="qr-actions">
-					<el-button type="primary" icon="el-icon-download" @click="downloadQr">下载二维码</el-button>
-				</div>
-			</div>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-export default {
-	data() {
-		return {
-			visible: false,
-			loading: false,
-			roomNo: '',
-			qrCodeUrl: '',
-			qrCodeImage: ''
-		}
-	},
-	methods: {
-		init(roomNo, qrCodeUrl, qrCodeImage) {
-			this.roomNo = roomNo
-			this.qrCodeUrl = qrCodeUrl || ''
-			this.qrCodeImage = qrCodeImage || ''
-			this.visible = true
-			this.loading = false
-		},
-		downloadQr() {
-			if (!this.qrCodeImage) return
-			const link = document.createElement('a')
-			const byteCharacters = atob(this.qrCodeImage)
-			const byteNumbers = new Array(byteCharacters.length)
-			for (let i = 0; i < byteCharacters.length; i++) {
-				byteNumbers[i] = byteCharacters.charCodeAt(i)
-			}
-			const byteArray = new Uint8Array(byteNumbers)
-			const blob = new Blob([byteArray], { type: 'image/png' })
-			link.href = URL.createObjectURL(blob)
-			link.download = `二维码_房间${this.roomNo}.png`
-			link.click()
-			URL.revokeObjectURL(link.href)
-		},
-		close() {
-			this.visible = false
-			this.qrCodeImage = ''
-			this.qrCodeUrl = ''
-		}
-	}
-}
-</script>
-
-<style scoped>
-.qr-dialog-content {
-	text-align: center;
-}
-.qr-info {
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	gap: 12px;
-	margin-bottom: 16px;
-}
-.qr-room-label {
-	font-size: 14px;
-	color: #909399;
-}
-.qr-room-no {
-	font-size: 22px;
-	font-weight: 700;
-	color: #303133;
-	letter-spacing: 2px;
-}
-.qr-image-wrapper {
-	display: flex;
-	justify-content: center;
-	margin-bottom: 12px;
-}
-.qr-image-wrapper img {
-	width: 260px;
-	height: 260px;
-	border: 1px solid #ebeef5;
-	border-radius: 8px;
-	box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
-}
-.qr-placeholder {
-	display: flex;
-	flex-direction: column;
-	align-items: center;
-	justify-content: center;
-	width: 260px;
-	height: 260px;
-	border: 1px dashed #dcdfe6;
-	border-radius: 8px;
-	color: #c0c4cc;
-}
-.qr-placeholder p {
-	margin-top: 8px;
-	font-size: 14px;
-}
-.qr-url-box {
-	display: flex;
-	align-items: center;
-	gap: 6px;
-	padding: 8px 12px;
-	background: #f5f7fa;
-	border-radius: 4px;
-	font-size: 12px;
-	color: #909399;
-	overflow: hidden;
-	text-overflow: ellipsis;
-	white-space: nowrap;
-	margin-bottom: 16px;
-}
-.qr-url-box .el-icon {
-	flex-shrink: 0;
-}
-.qr-url-box span {
-	overflow: hidden;
-	text-overflow: ellipsis;
-	white-space: nowrap;
-}
-.qr-actions {
-	display: flex;
-	justify-content: center;
-}
-</style>

+ 0 - 146
src/views/hotel/admin/RoomDetail.vue

@@ -1,146 +0,0 @@
-<template>
-	<div>
-		<el-dialog title="房间详情" :close-on-click-modal="false" draggable width="560px" @close="close"
-			v-model="visible">
-			<div v-loading="loading">
-				<el-descriptions :column="2" border>
-					<el-descriptions-item label="房间号">{{ detail.roomNo }}</el-descriptions-item>
-					<el-descriptions-item label="房间类型">{{ detail.roomTypeName || '-' }}</el-descriptions-item>
-					<el-descriptions-item label="楼层">{{ detail.floor || '-' }}</el-descriptions-item>
-					<el-descriptions-item label="状态">
-						<el-tag :type="statusTagType(detail.status)" effect="plain">
-							{{ statusLabel(detail.status) }}
-						</el-tag>
-					</el-descriptions-item>
-					<el-descriptions-item label="创建时间">{{ detail.createTime || '-' }}</el-descriptions-item>
-					<el-descriptions-item label="更新时间">{{ detail.updateTime || '-' }}</el-descriptions-item>
-				</el-descriptions>
-				<div class="qr-section" v-if="detail.qrCodeImage">
-					<div class="qr-section-title">点餐二维码</div>
-					<div class="qr-detail-image">
-						<img :src="'data:image/png;base64,' + detail.qrCodeImage" alt="二维码" />
-					</div>
-					<div class="qr-url-box" v-if="detail.qrCodeUrl">
-						<span class="qr-url-label">二维码链接:</span>
-						<el-input :model-value="detail.qrCodeUrl" readonly size="small">
-							<template #append>
-								<el-button icon="el-icon-copy-document" @click="copyUrl(detail.qrCodeUrl)"></el-button>
-							</template>
-						</el-input>
-					</div>
-					<div class="qr-actions">
-						<el-button type="primary" size="small" icon="el-icon-download" @click="downloadQr">下载二维码</el-button>
-					</div>
-				</div>
-				<div v-else class="qr-empty-section">
-					<el-empty description="暂无二维码" :image-size="60"></el-empty>
-				</div>
-			</div>
-			<template #footer>
-				<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import HotelRoomService from '@/api/hotel/HotelRoomService'
-export default {
-	data() {
-		return {
-			visible: false,
-			loading: false,
-			detail: {}
-		}
-	},
-	hotelRoomService: null,
-	created() {
-		this.hotelRoomService = new HotelRoomService()
-	},
-	methods: {
-		init(id) {
-			this.visible = true
-			this.loading = true
-			this.detail = {}
-			this.hotelRoomService.queryById(id).then((data) => {
-				this.detail = data
-				this.loading = false
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		statusLabel(status) {
-			const map = { '0': '正常', '1': '暂停', '2': '停用' }
-			return map[status] || '未知'
-		},
-		statusTagType(status) {
-			const map = { '0': 'success', '1': 'warning', '2': 'info' }
-			return map[status] || 'info'
-		},
-		copyUrl(url) {
-			navigator.clipboard.writeText(url).then(() => {
-				this.$message.success('已复制到剪贴板')
-			}).catch(() => {
-				this.$message.error('复制失败')
-			})
-		},
-		downloadQr() {
-			const link = document.createElement('a')
-			const byteCharacters = atob(this.detail.qrCodeImage)
-			const byteNumbers = new Array(byteCharacters.length)
-			for (let i = 0; i < byteCharacters.length; i++) {
-				byteNumbers[i] = byteCharacters.charCodeAt(i)
-			}
-			const byteArray = new Uint8Array(byteNumbers)
-			const blob = new Blob([byteArray], { type: 'image/png' })
-			link.href = URL.createObjectURL(blob)
-			link.download = `二维码_房间${this.detail.roomNo}.png`
-			link.click()
-			URL.revokeObjectURL(link.href)
-		},
-		close() {
-			this.visible = false
-		}
-	}
-}
-</script>
-
-<style scoped>
-.qr-section {
-	margin-top: 20px;
-	padding: 16px;
-	background: #f5f7fa;
-	border-radius: 6px;
-}
-.qr-section-title {
-	font-size: 14px;
-	font-weight: 600;
-	color: #303133;
-	margin-bottom: 12px;
-}
-.qr-detail-image {
-	text-align: center;
-	margin-bottom: 12px;
-}
-.qr-detail-image img {
-	width: 160px;
-	height: 160px;
-	border: 1px solid #ebeef5;
-	border-radius: 8px;
-}
-.qr-url-box {
-	margin-bottom: 12px;
-}
-.qr-url-label {
-	font-size: 12px;
-	color: #909399;
-	margin-bottom: 4px;
-	display: block;
-}
-.qr-actions {
-	text-align: center;
-}
-.qr-empty-section {
-	margin-top: 20px;
-}
-</style>

+ 0 - 139
src/views/hotel/admin/RoomForm.vue

@@ -1,139 +0,0 @@
-<template>
-	<div>
-		<el-dialog :title="title" :close-on-click-modal="false" draggable width="560px" @close="close"
-			@keyup.enter.native="doSubmit" v-model="visible">
-			<el-form :model="inputForm" ref="inputForm" v-loading="loading" :rules="rules" label-width="100px"
-				@submit.native.prevent>
-				<el-row :gutter="15">
-					<el-col :span="22">
-						<el-form-item label="房间号" prop="roomNo">
-							<el-input v-model="inputForm.roomNo" placeholder="请输入房间号" clearable></el-input>
-						</el-form-item>
-					</el-col>
-					<el-col :span="22">
-						<el-form-item label="房间类型" prop="roomTypeId">
-							<el-select v-model="inputForm.roomTypeId" placeholder="请选择房间类型" clearable style="width: 140px">
-								<el-option v-for="item in roomTypeList" :key="item.id" :label="item.name" :value="item.id"></el-option>
-							</el-select>
-						</el-form-item>
-					</el-col>
-					<el-col :span="22">
-						<el-form-item label="楼层" prop="floor">
-							<el-input-number v-model="inputForm.floor" :min="1" :max="99" style="width: 100%"></el-input-number>
-						</el-form-item>
-					</el-col>
-					<el-col :span="22">
-						<el-form-item label="状态" prop="status">
-							<el-radio-group v-model="inputForm.status">
-								<el-radio label="0">正常</el-radio>
-								<el-radio label="1">暂停</el-radio>
-								<el-radio label="2">停用</el-radio>
-							</el-radio-group>
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-					<el-button type="primary" @click="doSubmit()" icon="el-icon-circle-check"
-						v-noMoreClick>确定</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import HotelRoomService from '@/api/hotel/HotelRoomService'
-import HotelRoomTypeService from '@/api/hotel/HotelRoomTypeService'
-export default {
-	data() {
-		return {
-			title: '',
-			method: '',
-			visible: false,
-			loading: false,
-			rules: {
-				roomNo: [{ required: true, message: '房间号不能为空', trigger: 'blur' }]
-			},
-			roomTypeList: [],
-			inputForm: {
-				roomNo: '',
-				roomTypeId: '',
-				floor: 1,
-				status: '0'
-			}
-		}
-	},
-	hotelRoomService: null,
-	hotelRoomTypeService: null,
-	created() {
-		this.hotelRoomService = new HotelRoomService()
-		this.hotelRoomTypeService = new HotelRoomTypeService()
-		this.loadRoomTypes()
-	},
-	methods: {
-		init(method, id) {
-			this.method = method
-			this.inputForm = {
-				roomNo: '',
-				roomTypeId: '',
-				floor: 1,
-				status: '0'
-			}
-			if (method === 'add') {
-				this.title = '新增房间'
-			} else if (method === 'edit') {
-				this.title = '编辑房间'
-				this.inputForm.id = id
-			}
-			this.visible = true
-			this.loading = false
-			this.loadRoomTypes()
-			this.$nextTick(() => {
-				this.$refs.inputForm.resetFields()
-				if (method === 'edit') {
-					this.loading = true
-					this.hotelRoomService.queryById(id).then((data) => {
-						this.inputForm = {
-							id: data.id,
-							roomNo: data.roomNo,
-							roomTypeId: data.roomTypeId,
-							floor: data.floor,
-							status: data.status || '0'
-						}
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		doSubmit() {
-			this.$refs.inputForm.validate((valid) => {
-				if (valid) {
-					this.loading = true
-					this.hotelRoomService.save(this.inputForm).then(() => {
-						this.$message.success('操作成功')
-						this.close()
-						this.$emit('refreshList')
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		close() {
-			this.$refs.inputForm && this.$refs.inputForm.resetFields()
-			this.visible = false
-		},
-		loadRoomTypes() {
-			this.hotelRoomTypeService.all().then((data) => {
-				this.roomTypeList = Array.isArray(data) ? data : (data.list || [])
-			})
-		}
-	}
-}
-</script>

+ 0 - 310
src/views/hotel/admin/RoomManagement.vue

@@ -1,310 +0,0 @@
-<template>
-	<div class="page">
-		<el-form :inline="true" v-if="searchVisible" class="query-form m-b-10" ref="searchForm" :model="searchForm"
-			@keyup.enter.native="refreshList()" @submit.native.prevent>
-			<el-form-item label="房间号" prop="roomNo">
-				<el-input v-model="searchForm.roomNo" placeholder="请输入房间号" clearable></el-input>
-			</el-form-item>
-			<el-form-item label="房间类型" prop="roomTypeId">
-				<el-select v-model="searchForm.roomTypeId" placeholder="请选择类型" clearable style="width: 140px">
-					<el-option v-for="item in roomTypeList" :key="item.id" :label="item.name" :value="item.id"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item label="状态" prop="status">
-				<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px">
-					<el-option label="正常" value="0"></el-option>
-					<el-option label="暂停" value="1"></el-option>
-					<el-option label="停用" value="2"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item>
-				<el-button type="primary" @click="refreshList()" icon="el-icon-search">查询</el-button>
-				<el-button @click="resetSearch()" icon="el-icon-refresh-right">重置</el-button>
-			</el-form-item>
-		</el-form>
-
-		<div class="jp-table top">
-			<vxe-toolbar :refresh="{ query: refreshList }" custom>
-				<template #buttons>
-					<el-button v-if="hasPermission('hotel:admin:room:add')" type="primary" icon="el-icon-plus"
-						@click="add()">新增房间</el-button>
-					<el-button v-if="hasPermission('hotel:admin:room:list')" type="success" icon="el-icon-download"
-						:disabled="selectedRows.length === 0"
-						@click="batchDownloadQr()" plain>批量下载二维码</el-button>
-				</template>
-				<template #tools>
-					<vxe-button text type="primary" :title="searchVisible ? '收起检索' : '展开检索'" icon="vxe-icon-search"
-						class="tool-btn" @click="searchVisible = !searchVisible"></vxe-button>
-				</template>
-			</vxe-toolbar>
-			<div style="height: calc(100% - 90px)">
-				<vxe-table border="inner" auto-resize resizable height="auto" :loading="loading" ref="dataTable"
-					show-header-overflow show-overflow highlight-hover-row :menu-config="{}" :data="dataList"
-					:checkbox-config="{}"
-					@checkbox-change="handleCheckboxChange"
-					@checkbox-all="handleCheckboxChange">
-					<vxe-column type="seq" width="60" title="序号"></vxe-column>
-					<vxe-column type="checkbox" width="60"></vxe-column>
-					<vxe-column title="房间号" field="roomNo" min-width="120" align="center">
-						<template #default="scope">
-							<el-button text type="primary" @click="viewDetail(scope.row.id)">{{ scope.row.roomNo }}</el-button>
-						</template>
-					</vxe-column>
-					<vxe-column title="房间类型" field="roomTypeName" min-width="120" align="center"></vxe-column>
-					<vxe-column title="楼层" field="floor" min-width="80" align="center"></vxe-column>
-					<vxe-column title="状态" field="status" min-width="100" align="center">
-						<template #default="scope">
-							<el-tag :type="statusTagType(scope.row.status)" effect="plain">
-								{{ statusLabel(scope.row.status) }}
-							</el-tag>
-						</template>
-					</vxe-column>
-					<vxe-column title="二维码" field="qrCodeImage" min-width="100" align="center">
-						<template #default="scope">
-							<div v-if="scope.row.qrCodeImage" class="qr-thumb" @click="viewQr(scope.row)">
-								<img :src="'data:image/png;base64,' + scope.row.qrCodeImage" alt="二维码" />
-							</div>
-							<span v-else class="qr-empty">未生成</span>
-						</template>
-					</vxe-column>
-					<vxe-column title="生成时间" field="qrGeneratedTime" min-width="170" align="center"></vxe-column>
-					<vxe-column title="操作" width="280" fixed="right" align="center">
-						<template #default="scope">
-							<el-button v-if="hasPermission('hotel:admin:room:edit')" text type="primary" size="small"
-								@click="edit(scope.row.id)">编辑</el-button>
-							<el-button v-if="hasPermission('hotel:admin:room:edit')" text type="primary" size="small"
-								@click="doToggleStatus(scope.row)">{{ statusAction(scope.row.status) }}</el-button>
-							<el-button v-if="hasPermission('hotel:admin:room:list') && scope.row.qrCodeImage" text
-								type="primary" size="small" @click="viewQr(scope.row)">查看</el-button>
-							<el-button v-if="hasPermission('hotel:admin:room:edit') && scope.row.qrCodeImage" text
-								type="primary" size="small" @click="doRegenerateQr(scope.row)">重新生成</el-button>
-							<el-button v-if="hasPermission('hotel:admin:room:delete')" text type="danger" size="small"
-								@click="doDelete(scope.row.id)">删除</el-button>
-						</template>
-					</vxe-column>
-				</vxe-table>
-				<vxe-pager background :current-page="tablePage.currentPage" :page-size="tablePage.pageSize"
-					:total="tablePage.total" :page-sizes="[10, 20, 100, 1000, { label: '全量数据', value: 1000000 }]"
-					:layouts="['PrevPage', 'JumpNumber', 'NextPage', 'FullJump', 'Sizes', 'Total']"
-					@page-change="currentChangeHandle">
-				</vxe-pager>
-			</div>
-		</div>
-		<RoomForm ref="roomForm" @refreshList="refreshList"></RoomForm>
-		<QrCodeDialog ref="qrCodeDialog"></QrCodeDialog>
-		<RoomDetail ref="roomDetail"></RoomDetail>
-	</div>
-</template>
-
-<script>
-import HotelRoomService from '@/api/hotel/HotelRoomService'
-import HotelRoomTypeService from '@/api/hotel/HotelRoomTypeService'
-import JSZip from 'jszip'
-import RoomForm from './RoomForm'
-import QrCodeDialog from './QrCodeDialog'
-import RoomDetail from './RoomDetail'
-export default {
-	data() {
-		return {
-			searchVisible: true,
-			searchForm: {
-				roomNo: '',
-				roomTypeId: '',
-				status: ''
-			},
-			roomTypeList: [],
-			dataList: [],
-			selectedRows: [],
-			tablePage: {
-				total: 0,
-				currentPage: 1,
-				pageSize: 10,
-				orders: []
-			},
-			loading: false
-		}
-	},
-	hotelRoomService: null,
-	hotelRoomTypeService: null,
-	created() {
-		this.hotelRoomService = new HotelRoomService()
-		this.hotelRoomTypeService = new HotelRoomTypeService()
-		this.loadRoomTypes()
-	},
-	components: {
-		RoomForm,
-		QrCodeDialog,
-		RoomDetail
-	},
-	mounted() {
-		this.refreshList()
-	},
-	activated() {
-		this.refreshList()
-		this.loadRoomTypes()
-	},
-	methods: {
-		add() {
-			this.$refs.roomForm.init('add', '')
-		},
-		edit(id) {
-			this.$refs.roomForm.init('edit', id)
-		},
-		viewDetail(id) {
-			this.$refs.roomDetail.init(id)
-		},
-		refreshList() {
-			this.loading = true
-			this.hotelRoomService.page({
-				'current': this.tablePage.currentPage,
-				'size': this.tablePage.pageSize,
-				'orders': this.tablePage.orders,
-				...this.searchForm
-			}).then((data) => {
-				this.dataList = data.records || []
-				this.tablePage.total = data.total || 0
-				this.tablePage.currentPage = data.current || 1
-				this.loading = false
-				this.selectedRows = []
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		currentChangeHandle({ currentPage, pageSize }) {
-			this.tablePage.currentPage = currentPage
-			this.tablePage.pageSize = pageSize
-			this.refreshList()
-		},
-		resetSearch() {
-			this.$refs.searchForm.resetFields()
-			this.searchForm.roomTypeId = ''
-			this.tablePage.currentPage = 1
-			this.refreshList()
-		},
-		handleCheckboxChange() {
-			this.selectedRows = this.$refs.dataTable.getCheckboxRecords()
-		},
-		doToggleStatus(row) {
-			const statusMap = { '0': '1', '1': '0', '2': '0' }
-			const targetStatus = statusMap[row.status] || '0'
-			const labelMap = { '0': '暂停', '1': '恢复', '2': '恢复' }
-			this.$confirm(`确定${labelMap[row.status]}房间【${row.roomNo}】吗?`, '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelRoomService.toggleStatus(row.id, targetStatus).then(() => {
-					this.$message.success('操作成功')
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		},
-		viewQr(row) {
-			this.$refs.qrCodeDialog.init(row.roomNo, row.qrCodeUrl, row.qrCodeImage)
-		},
-		doRegenerateQr(row) {
-			this.$confirm(`确定重新生成房间【${row.roomNo}】的二维码吗?旧二维码将失效。`, '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelRoomService.regenerateQr(row.id).then((data) => {
-					this.$message.success('重新生成成功')
-					this.$refs.qrCodeDialog.init(row.roomNo, data.qrCodeUrl || row.qrCodeUrl, data.qrCodeImage)
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		},
-		doDelete(id) {
-			this.$confirm('确定删除该房间吗?', '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelRoomService.delete(id).then(() => {
-					this.$message.success('删除成功')
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		},
-		async batchDownloadQr() {
-			const rows = this.selectedRows.filter(r => r.qrCodeImage)
-			if (rows.length === 0) {
-				this.$message.warning('所选房间中没有已生成二维码的房间')
-				return
-			}
-			this.loading = true
-			try {
-				const zip = new JSZip()
-				const folder = zip.folder('房间二维码')
-				rows.forEach((row) => {
-					folder.file(`房间${row.roomNo}.png`, row.qrCodeImage, { base64: true })
-				})
-				const content = await zip.generateAsync({ type: 'blob' })
-				const link = document.createElement('a')
-				link.href = URL.createObjectURL(content)
-				link.download = `房间二维码_${new Date().toLocaleDateString()}.zip`
-				link.click()
-				URL.revokeObjectURL(link.href)
-				this.$message.success(`已打包下载 ${rows.length} 个二维码`)
-			} catch (e) {
-				this.$message.error('打包失败:' + e.message)
-			} finally {
-				this.loading = false
-			}
-		},
-		statusLabel(status) {
-			const map = { '0': '正常', '1': '暂停', '2': '停用' }
-			return map[status] || '未知'
-		},
-		statusTagType(status) {
-			const map = { '0': 'success', '1': 'warning', '2': 'info' }
-			return map[status] || 'info'
-		},
-		statusAction(status) {
-			const map = { '0': '暂停', '1': '恢复', '2': '恢复' }
-			return map[status] || '恢复'
-		},
-		loadRoomTypes() {
-			this.hotelRoomTypeService.all().then((data) => {
-				this.roomTypeList = Array.isArray(data) ? data : (data.list || [])
-			})
-		}
-	}
-}
-</script>
-
-<style scoped>
-.qr-thumb {
-	display: inline-block;
-	cursor: pointer;
-	border: 1px solid #ebeef5;
-	border-radius: 4px;
-	overflow: hidden;
-	transition: box-shadow 0.2s;
-}
-.qr-thumb:hover {
-	box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
-}
-.qr-thumb img {
-	display: block;
-	width: 48px;
-	height: 48px;
-}
-.qr-empty {
-	color: #c0c4cc;
-	font-size: 12px;
-}
-</style>

+ 0 - 100
src/views/hotel/admin/RoomTypeForm.vue

@@ -1,100 +0,0 @@
-<template>
-	<div>
-		<el-dialog :title="title" :close-on-click-modal="false" draggable width="460px" @close="close"
-			@keyup.enter.native="doSubmit" v-model="visible">
-			<el-form :model="inputForm" ref="inputForm" v-loading="loading" :rules="rules" label-width="80px"
-				@submit.native.prevent>
-				<el-form-item label="类型名称" prop="name">
-					<el-input v-model="inputForm.name" placeholder="请输入类型名称" clearable></el-input>
-				</el-form-item>
-				<el-form-item label="排序号" prop="sortOrder">
-					<el-input-number v-model="inputForm.sortOrder" :min="0" :max="999" style="width: 100%"></el-input-number>
-				</el-form-item>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-					<el-button type="primary" @click="doSubmit()" icon="el-icon-circle-check"
-						v-noMoreClick>确定</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import HotelRoomTypeService from '@/api/hotel/HotelRoomTypeService'
-export default {
-	data() {
-		return {
-			title: '',
-			method: '',
-			visible: false,
-			loading: false,
-			rules: {
-				name: [{ required: true, message: '类型名称不能为空', trigger: 'blur' }]
-			},
-			inputForm: {
-				name: '',
-				sortOrder: 0
-			}
-		}
-	},
-	hotelRoomTypeService: null,
-	created() {
-		this.hotelRoomTypeService = new HotelRoomTypeService()
-	},
-	methods: {
-		init(method, id) {
-			this.method = method
-			this.inputForm = {
-				name: '',
-				sortOrder: 0
-			}
-			if (method === 'add') {
-				this.title = '新增房间类型'
-			} else if (method === 'edit') {
-				this.title = '编辑房间类型'
-				this.inputForm.id = id
-			}
-			this.visible = true
-			this.loading = false
-			this.$nextTick(() => {
-				this.$refs.inputForm.resetFields()
-				if (method === 'edit') {
-					this.loading = true
-					this.hotelRoomTypeService.queryById(id).then((data) => {
-						this.inputForm = {
-							id: data.id,
-							name: data.name,
-							sortOrder: data.sortOrder || 0
-						}
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		doSubmit() {
-			this.$refs.inputForm.validate((valid) => {
-				if (valid) {
-					this.loading = true
-					this.hotelRoomTypeService.save(this.inputForm).then(() => {
-						this.$message.success('操作成功')
-						this.close()
-						this.$emit('refreshList')
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		close() {
-			this.$refs.inputForm && this.$refs.inputForm.resetFields()
-			this.visible = false
-		}
-	}
-}
-</script>

+ 0 - 166
src/views/hotel/admin/RoomTypeManagement.vue

@@ -1,166 +0,0 @@
-<template>
-	<div class="page">
-		<el-form :inline="true" v-if="searchVisible" class="query-form m-b-10" ref="searchForm" :model="searchForm"
-			@keyup.enter.native="refreshList()" @submit.native.prevent>
-			<el-form-item label="类型名称" prop="name">
-				<el-input v-model="searchForm.name" placeholder="请输入类型名称" clearable></el-input>
-			</el-form-item>
-			<el-form-item label="状态" prop="status">
-				<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px">
-					<el-option label="启用" value="ENABLED"></el-option>
-					<el-option label="禁用" value="DISABLED"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item>
-				<el-button type="primary" @click="refreshList()" icon="el-icon-search">查询</el-button>
-				<el-button @click="resetSearch()" icon="el-icon-refresh-right">重置</el-button>
-			</el-form-item>
-		</el-form>
-
-		<div class="jp-table top">
-			<vxe-toolbar :refresh="{ query: refreshList }" custom>
-				<template #buttons>
-					<el-button v-if="hasPermission('hotel:admin:roomType:add')" type="primary" icon="el-icon-plus"
-						@click="add()">新增类型</el-button>
-				</template>
-				<template #tools>
-					<vxe-button text type="primary" :title="searchVisible ? '收起检索' : '展开检索'" icon="vxe-icon-search"
-						class="tool-btn" @click="searchVisible = !searchVisible"></vxe-button>
-				</template>
-			</vxe-toolbar>
-			<div style="height: calc(100% - 90px)">
-				<vxe-table border="inner" auto-resize resizable height="auto" :loading="loading" ref="dataTable"
-					show-header-overflow show-overflow highlight-hover-row :menu-config="{}" :data="dataList"
-					:checkbox-config="{}">
-					<vxe-column type="seq" width="60" title="序号"></vxe-column>
-					<vxe-column title="类型名称" field="name" min-width="200" align="center"></vxe-column>
-					<vxe-column title="排序号" field="sortOrder" width="100" align="center"></vxe-column>
-					<vxe-column title="状态" field="status" width="100" align="center">
-						<template #default="scope">
-							<el-tag :type="scope.row.status === 'ENABLED' ? 'success' : 'info'" effect="plain">
-								{{ scope.row.status === 'ENABLED' ? '启用' : '禁用' }}
-							</el-tag>
-						</template>
-					</vxe-column>
-					<vxe-column title="创建时间" field="createTime" min-width="170" align="center"></vxe-column>
-					<vxe-column title="操作" width="200" fixed="right" align="center">
-						<template #default="scope">
-							<el-button v-if="hasPermission('hotel:admin:roomType:edit')" text type="primary" size="small"
-								@click="edit(scope.row.id)">编辑</el-button>
-							<el-button v-if="hasPermission('hotel:admin:roomType:edit')" text type="primary" size="small"
-								@click="doToggleStatus(scope.row)">{{ scope.row.status === 'ENABLED' ? '禁用' : '启用' }}</el-button>
-							<el-button v-if="hasPermission('hotel:admin:roomType:delete')" text type="danger" size="small"
-								@click="doDelete(scope.row.id)">删除</el-button>
-						</template>
-					</vxe-column>
-				</vxe-table>
-				<vxe-pager background :current-page="tablePage.currentPage" :page-size="tablePage.pageSize"
-					:total="tablePage.total" :page-sizes="[10, 20, 100, 1000, { label: '全量数据', value: 1000000 }]"
-					:layouts="['PrevPage', 'JumpNumber', 'NextPage', 'FullJump', 'Sizes', 'Total']"
-					@page-change="currentChangeHandle">
-				</vxe-pager>
-			</div>
-		</div>
-		<RoomTypeForm ref="roomTypeForm" @refreshList="refreshList"></RoomTypeForm>
-	</div>
-</template>
-
-<script>
-import HotelRoomTypeService from '@/api/hotel/HotelRoomTypeService'
-import RoomTypeForm from './RoomTypeForm'
-export default {
-	data() {
-		return {
-			searchVisible: true,
-			searchForm: {
-				name: '',
-				status: ''
-			},
-			dataList: [],
-			tablePage: {
-				total: 0,
-				currentPage: 1,
-				pageSize: 10,
-				orders: []
-			},
-			loading: false
-		}
-	},
-	hotelRoomTypeService: null,
-	created() {
-		this.hotelRoomTypeService = new HotelRoomTypeService()
-	},
-	components: {
-		RoomTypeForm
-	},
-	mounted() {
-		this.refreshList()
-	},
-	activated() {
-		this.refreshList()
-	},
-	methods: {
-		add() {
-			this.$refs.roomTypeForm.init('add', '')
-		},
-		edit(id) {
-			this.$refs.roomTypeForm.init('edit', id)
-		},
-		refreshList() {
-			this.loading = true
-			this.hotelRoomTypeService.page({
-				'current': this.tablePage.currentPage,
-				'size': this.tablePage.pageSize,
-				'orders': this.tablePage.orders,
-				...this.searchForm
-			}).then((data) => {
-				this.dataList = data.records || []
-				this.tablePage.total = data.total || 0
-				this.tablePage.currentPage = data.current || 1
-				this.loading = false
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		currentChangeHandle({ currentPage, pageSize }) {
-			this.tablePage.currentPage = currentPage
-			this.tablePage.pageSize = pageSize
-			this.refreshList()
-		},
-		resetSearch() {
-			this.$refs.searchForm.resetFields()
-			this.tablePage.currentPage = 1
-			this.refreshList()
-		},
-		doToggleStatus(row) {
-			const action = row.status === 'ENABLED' ? '禁用' : '启用'
-			this.$confirm(`确定${action}该房间类型吗?`, '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.hotelRoomTypeService.toggleStatus(row.id).then(() => {
-					this.$message.success('操作成功')
-					this.refreshList()
-				})
-			})
-		},
-		doDelete(id) {
-			this.$confirm('确定删除该房间类型吗?', '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelRoomTypeService.delete(id).then(() => {
-					this.$message.success('删除成功')
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		}
-	}
-}
-</script>

+ 0 - 21
src/views/hotel/customer/CustomerMenu.vue

@@ -1,21 +0,0 @@
-<template>
-  <div class="customer-menu-page">
-    <!-- 顾客端 - 点餐菜单页 -->
-    <h2>顾客点餐菜单</h2>
-    <p>待实现:参考HTML原型 customer-menu.html</p>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-
-onMounted(() => {
-  console.log('CustomerMenu mounted')
-})
-</script>
-
-<style scoped>
-.customer-menu-page {
-  padding: 20px;
-}
-</style>

+ 0 - 104
src/views/hotel/dish/CategoryForm.vue

@@ -1,104 +0,0 @@
-<template>
-	<div>
-		<el-dialog :title="title" :close-on-click-modal="false" draggable width="560px" @close="close"
-			@keyup.enter.native="doSubmit" v-model="visible">
-			<el-form :model="inputForm" ref="inputForm" v-loading="loading"
-				label-width="100px" @submit.native.prevent>
-				<el-row :gutter="15">
-					<el-col :span="22">
-						<el-form-item label="分类名称" prop="name"
-							:rules="[{ required: true, message: '分类名称不能为空', trigger: 'blur' }]">
-							<el-input v-model="inputForm.name" placeholder="请输入分类名称"></el-input>
-						</el-form-item>
-					</el-col>
-					<el-col :span="22">
-						<el-form-item label="排序号" prop="sortOrder">
-							<el-input-number v-model="inputForm.sortOrder" :min="0" style="width: 100%"></el-input-number>
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-					<el-button type="primary" @click="doSubmit()" icon="el-icon-circle-check" v-noMoreClick>确定</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import HotelDishService from '@/api/hotel/HotelDishService'
-export default {
-	data() {
-		return {
-			title: '',
-			method: '',
-			visible: false,
-			loading: false,
-			inputForm: {
-				id: '',
-				name: '',
-				sortOrder: 0
-			}
-		}
-	},
-	hotelDishService: null,
-	created() {
-		this.hotelDishService = new HotelDishService()
-	},
-	methods: {
-		init(method, id) {
-			this.method = method
-			this.inputForm = {
-				id: '',
-				name: '',
-				sortOrder: 0
-			}
-			if (method === 'add') {
-				this.title = '新建分类'
-			} else if (method === 'edit') {
-				this.inputForm.id = id
-				this.title = '编辑分类'
-			}
-			this.visible = true
-			this.loading = false
-			this.$nextTick(() => {
-				if (method === 'edit') {
-					this.loading = true
-					this.hotelDishService.categoryPage({ current: 1, size: 999 }).then((data) => {
-						const records = data.records || []
-						const found = records.find(item => item.id === id)
-						if (found) {
-							this.inputForm = { ...this.inputForm, ...found }
-						}
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		doSubmit() {
-			this.$refs['inputForm'].validate((valid) => {
-				if (valid) {
-					this.loading = true
-					this.hotelDishService.categorySave(this.inputForm).then((data) => {
-						this.close()
-						this.$message.success(data)
-						this.$emit('refreshDataList')
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		close() {
-			this.$refs.inputForm && this.$refs.inputForm.resetFields()
-			this.visible = false
-		}
-	}
-}
-</script>

+ 0 - 127
src/views/hotel/dish/CategoryManagement.vue

@@ -1,127 +0,0 @@
-<template>
-	<div class="page">
-		<div class="jp-table top">
-			<vxe-toolbar :refresh="{ query: refreshList }" custom>
-				<template #buttons>
-					<el-button v-if="hasPermission('hotel:admin:category:add')" type="primary" icon="el-icon-plus"
-						@click="add()">新建分类</el-button>
-				</template>
-			</vxe-toolbar>
-			<div style="height: calc(100% - 50px)">
-				<vxe-table border="inner" auto-resize resizable height="auto" :loading="loading" ref="dataTable"
-					show-header-overflow show-overflow highlight-hover-row :menu-config="{}"
-					:data="dataList" :checkbox-config="{}">
-					<vxe-column type="seq" width="60" title="序号"></vxe-column>
-					<vxe-column title="分类名称" field="name" align="left" min-width="150"></vxe-column>
-					<vxe-column title="排序号" field="sortOrder" width="100" align="center"></vxe-column>
-					<vxe-column title="菜品数量" field="dishCount" width="100" align="center"></vxe-column>
-					<vxe-column title="状态" field="status" width="100" align="center">
-						<template #default="scope">
-							<el-tag :type="scope.row.status === 'ENABLED' ? 'success' : 'info'" size="small">
-								{{ scope.row.status === 'ENABLED' ? '启用' : '禁用' }}
-							</el-tag>
-						</template>
-					</vxe-column>
-					<vxe-column title="操作" width="200px" fixed="right" align="center">
-						<template #default="scope">
-							<el-button v-if="hasPermission('hotel:admin:category:edit')" text type="primary"
-								@click="edit(scope.row.id)">编辑</el-button>
-							<el-button v-if="hasPermission('hotel:admin:category:edit')" text type="primary"
-								@click="doToggleStatus(scope.row.id)">
-								{{ scope.row.status === 'ENABLED' ? '禁用' : '启用' }}
-							</el-button>
-							<el-button v-if="hasPermission('hotel:admin:category:delete')" text type="danger"
-								@click="doDelete(scope.row.id)">删除</el-button>
-						</template>
-					</vxe-column>
-				</vxe-table>
-			</div>
-		</div>
-		<CategoryForm ref="categoryForm" @refreshDataList="refreshList"></CategoryForm>
-	</div>
-</template>
-
-<script>
-import HotelDishService from '@/api/hotel/HotelDishService'
-import CategoryForm from './CategoryForm'
-export default {
-	data() {
-		return {
-			dataList: [],
-			tablePage: {
-				total: 0,
-				currentPage: 1,
-				pageSize: 10
-			},
-			loading: false
-		}
-	},
-	hotelDishService: null,
-	created() {
-		this.hotelDishService = new HotelDishService()
-	},
-	components: {
-		CategoryForm
-	},
-	mounted() {
-		this.refreshList()
-	},
-	activated() {
-		this.refreshList()
-	},
-	methods: {
-		add() {
-			this.$refs.categoryForm.init('add', '')
-		},
-		edit(id) {
-			this.$refs.categoryForm.init('edit', id)
-		},
-		refreshList() {
-			this.loading = true
-			const params = {
-				current: this.tablePage.currentPage,
-				size: this.tablePage.pageSize
-			}
-			this.hotelDishService.categoryPage(params).then((data) => {
-				this.dataList = data.records || []
-				this.tablePage.total = data.total || 0
-				this.tablePage.currentPage = data.current || 1
-				this.loading = false
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		handlePageChange({ currentPage, pageSize }) {
-			this.tablePage.currentPage = currentPage
-			this.tablePage.pageSize = pageSize
-			this.refreshList()
-		},
-		doToggleStatus(id) {
-			this.loading = true
-			this.hotelDishService.categoryToggleStatus(id).then((data) => {
-				this.$message.success(data)
-				this.refreshList()
-				this.loading = false
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		doDelete(id) {
-			this.$confirm('确定删除该分类吗?', '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelDishService.categoryDelete(id).then((data) => {
-					this.$message.success(data)
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		}
-	}
-}
-</script>

+ 0 - 200
src/views/hotel/dish/DishForm.vue

@@ -1,200 +0,0 @@
-<template>
-	<div>
-		<el-dialog :title="title" :close-on-click-modal="false" draggable width="900px" @close="close"
-			@keyup.enter.native="doSubmit" v-model="visible">
-			<el-form :model="inputForm" ref="inputForm" v-loading="loading"
-				:class="method === 'view' ? 'readonly' : ''"
-				:disabled="method === 'view'" label-width="110px" @submit.native.prevent>
-				<el-row :gutter="20">
-					<el-col :span="12">
-						<el-form-item label="菜品名称" prop="name"
-							:rules="[{ required: true, message: '菜品名称不能为空', trigger: 'blur' }]">
-							<el-input v-model="inputForm.name" placeholder="请填写菜品名称"></el-input>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12">
-						<el-form-item label="分类" prop="categoryId"
-							:rules="[{ required: true, message: '请选择分类', trigger: 'change' }]">
-							<el-select v-model="inputForm.categoryId" placeholder="请选择分类" style="width: 100%">
-								<el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="item.id"></el-option>
-							</el-select>
-						</el-form-item>
-					</el-col>
-				</el-row>
-				<el-row :gutter="20">
-					<el-col :span="12">
-						<el-form-item label="价格(元)" prop="price"
-							:rules="[{ required: true, message: '价格不能为空', trigger: 'blur' }]">
-							<el-input-number v-model="inputForm.price" :min="0" :precision="2"
-								style="width: 100%"></el-input-number>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12">
-						<el-form-item label="制作时长(分钟)" prop="prepTime">
-							<el-input-number v-model="inputForm.prepTime" :min="0" style="width: 100%"></el-input-number>
-						</el-form-item>
-					</el-col>
-				</el-row>
-				<el-row :gutter="20">
-					<el-col :span="12">
-						<el-form-item label="标签" prop="tags">
-							<el-input v-model="inputForm.tags" placeholder="多个标签用逗号分隔,如:辣,推荐,新品"></el-input>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12">
-						<el-form-item label="排序号" prop="sortOrder">
-							<el-input-number v-model="inputForm.sortOrder" :min="0" style="width: 100%"></el-input-number>
-						</el-form-item>
-					</el-col>
-				</el-row>
-				<el-row :gutter="20">
-					<el-col :span="12">
-						<el-form-item label="主厨推荐" prop="isRecommended">
-							<el-switch v-model="inputForm.isRecommended" :active-value="1" :inactive-value="0"></el-switch>
-						</el-form-item>
-					</el-col>
-					<el-col :span="12">
-						<el-form-item label="状态" prop="status">
-							<el-radio-group v-model="inputForm.status">
-								<el-radio label="ON_SALE">在售</el-radio>
-								<el-radio label="OFF_SHELF">下架</el-radio>
-							</el-radio-group>
-						</el-form-item>
-					</el-col>
-				</el-row>
-				<el-row :gutter="20">
-					<el-col :span="24">
-						<el-form-item label="描述" prop="description">
-							<el-input v-model="inputForm.description" type="textarea" :rows="3"
-								placeholder="请输入菜品描述"></el-input>
-						</el-form-item>
-					</el-col>
-				</el-row>
-				<el-row :gutter="20">
-					<el-col :span="12">
-						<el-form-item label="菜品图片" prop="image">
-							<DishImageUpload ref="dishImageUpload" v-model="inputForm.image" :auth="method"
-								directory="hotel-dish" :max-size="10" :limit="1">
-							</DishImageUpload>
-						</el-form-item>
-					</el-col>
-				</el-row>
-			</el-form>
-			<template #footer>
-				<span class="dialog-footer">
-					<el-button @click="close()" icon="el-icon-circle-close">关闭</el-button>
-					<el-button type="primary" v-if="method !== 'view'" @click="doSubmit()" icon="el-icon-circle-check"
-						v-noMoreClick>确定</el-button>
-				</span>
-			</template>
-		</el-dialog>
-	</div>
-</template>
-
-<script>
-import HotelDishService from '@/api/hotel/HotelDishService'
-import DishImageUpload from './DishImageUpload'
-export default {
-	data() {
-		return {
-			title: '',
-			method: '',
-			visible: false,
-			loading: false,
-			categoryList: [],
-			inputForm: {
-				id: '',
-				name: '',
-				categoryId: '',
-				price: 0,
-				tags: '',
-				prepTime: null,
-				image: '',
-				description: '',
-				isRecommended: 0,
-				sortOrder: 0,
-				status: 'ON_SALE'
-			}
-		}
-	},
-	hotelDishService: null,
-	created() {
-		this.hotelDishService = new HotelDishService()
-	},
-	components: {
-		DishImageUpload
-	},
-	methods: {
-		init(method, id) {
-			this.method = method
-			this.inputForm = {
-				id: '',
-				name: '',
-				categoryId: '',
-				price: 0,
-				tags: '',
-				prepTime: null,
-				image: '',
-				description: '',
-				isRecommended: 0,
-				sortOrder: 0,
-				status: 'ON_SALE'
-			}
-			if (method === 'add') {
-				this.title = '新建菜品'
-			} else if (method === 'edit') {
-				this.title = '修改菜品'
-				this.inputForm.id = id
-			} else if (method === 'view') {
-				this.title = '查看菜品'
-				this.inputForm.id = id
-			}
-			this.visible = true
-			this.loading = false
-			this.loadCategories()
-			this.$nextTick(() => {
-				if (method === 'edit' || method === 'view') {
-					this.loading = true
-					this.$refs.inputForm.resetFields()
-					this.hotelDishService.queryById(this.inputForm.id).then((data) => {
-						const dish = data || {}
-						this.inputForm = { ...this.inputForm, ...dish }
-						this.inputForm = JSON.parse(JSON.stringify(this.inputForm))
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		loadCategories() {
-			this.hotelDishService.categoryAll().then((data) => {
-				this.categoryList = Array.isArray(data) ? data : (data.list || [])
-			})
-		},
-		doSubmit() {
-			this.$refs.inputForm.validate((valid) => {
-				if (valid) {
-					if (this.$refs.dishImageUpload && this.$refs.dishImageUpload.checkProgress()) {
-						return
-					}
-					this.loading = true
-					this.hotelDishService.save(this.inputForm).then((data) => {
-						this.$message.success(data)
-						this.close()
-						this.$emit('refreshList')
-						this.loading = false
-					}).catch(() => {
-						this.loading = false
-					})
-				}
-			})
-		},
-		close() {
-			this.$refs.inputForm && this.$refs.inputForm.resetFields()
-			this.$refs.dishImageUpload && this.$refs.dishImageUpload.clearUpload()
-			this.visible = false
-		}
-	}
-}
-</script>

+ 0 - 351
src/views/hotel/dish/DishImageUpload.vue

@@ -1,351 +0,0 @@
-<template>
-	<div :key="uploadKey" class="dish-image-upload">
-		<el-upload ref="upload" action="" :limit="limit" :http-request="httpRequest" :multiple="limit > 1"
-			:disabled="currentAuth === 'view'" :on-exceed="onExceed" :show-file-list="false" :on-change="changes"
-			:on-progress="uploadProcess" :file-list="fileList" accept=".jpg,.jpeg,.png,.gif,.bmp">
-			<template v-if="currentAuth === 'view'" #tip>
-				<el-button :loading="loading" type="primary" size="default" :disabled="true">点击上传</el-button>
-			</template>
-			<template v-else #trigger>
-				<el-button :loading="loading" type="primary" size="default">点击上传</el-button>
-			</template>
-			<template #tip>
-				<div class="el-upload__tip">
-					<template v-if="limit === 1">仅支持上传1张图片,重新上传将覆盖当前图片,格式限 JPG、JPEG、PNG、GIF、BMP,不超过 {{ maxValue }}MB</template>
-					<template v-else>支持上传多张图片(最多{{ limit }}张),格式限 JPG、JPEG、PNG、GIF、BMP,单张不超过 {{ maxValue }}MB</template>
-				</div>
-			</template>
-		</el-upload>
-
-		<el-progress v-if="progressFlag" :percentage="loadProgress" style="margin-top: 10px"></el-progress>
-
-		<div class="image-list">
-			<div class="image-item" v-for="(item, index) in dataListNew" :key="item.url || item.uid || index">
-				<el-image class="image-thumb" :src="item.lsUrl || item.url" :preview-src-list="previewList"
-					:initial-index="index" fit="cover" hide-on-click-modal :preview-teleported="true" :z-index="9999">
-					<template #error>
-						<div class="image-error">图片</div>
-					</template>
-				</el-image>
-				<div v-if="currentAuth !== 'view'" class="image-actions">
-					<el-tooltip effect="dark" content="删除" placement="top">
-						<el-icon style="cursor: pointer;">
-							<Delete @click="deleteById(item, index)" />
-						</el-icon>
-					</el-tooltip>
-				</div>
-				<el-tooltip effect="dark" :content="item.name" placement="top">
-					<div class="image-name">{{ item.name }}</div>
-				</el-tooltip>
-			</div>
-		</div>
-	</div>
-</template>
-
-<script>
-import OSSSerivce, {
-	httpRequest,
-	fileName,
-	beforeAvatarUpload
-} from '@/api/sys/OSSService'
-import moment from 'moment'
-export default {
-	props: {
-		modelValue: {
-			type: String,
-			default: ''
-		},
-		auth: {
-			type: String,
-			default: ''
-		},
-		limit: {
-			type: Number,
-			default: 9
-		},
-		directory: {
-			type: String,
-			default: 'dish'
-		},
-		maxSize: {
-			type: Number,
-			default: 10
-		}
-	},
-	data() {
-		return {
-			uploadKey: '',
-			progressFlag: false,
-			loadProgress: 0,
-			fileList: [],
-			dataList: [],
-			dataListNew: [],
-			ossService: null,
-			localAuth: '',
-			uploadDirectory: 'dish',
-			maxValue: 10,
-			loading: false,
-			fileLoading: true,
-			innerValue: '',
-			setValueKey: 0
-		}
-	},
-	computed: {
-		previewList() {
-			return this.dataListNew.map(item => item.lsUrl || item.url).filter(item => item)
-		},
-		currentAuth() {
-			return this.localAuth || this.auth
-		}
-	},
-	watch: {
-		modelValue(val) {
-			if (val !== this.innerValue) {
-				this.setValue(val)
-			}
-		}
-	},
-	created() {
-		this.ossService = new OSSSerivce()
-	},
-	mounted() {
-		this.uploadDirectory = this.directory || 'dish'
-		this.maxValue = this.maxSize || 10
-		this.setValue(this.modelValue)
-	},
-	methods: {
-		async newUpload(auth, value, directory, maxValue) {
-			this.localAuth = auth
-			this.uploadDirectory = directory || 'dish'
-			this.maxValue = maxValue || 10
-			await this.setValue(value)
-		},
-		async setValue(value) {
-			const key = ++this.setValueKey
-			const urls = this.parseValue(value)
-			this.innerValue = urls.join(',')
-			this.dataList = []
-			this.dataListNew = []
-			this.fileList = []
-			this.fileLoading = false
-			for (const url of urls) {
-				const item = {
-					name: this.getFileNameByUrl(url),
-					url: url,
-					status: 'success'
-				}
-				await this.fillPreviewUrl(item)
-				if (key !== this.setValueKey) {
-					return
-				}
-				this.dataList.push(item)
-				this.dataListNew.push(item)
-				this.fileList.push(item)
-			}
-			this.fileLoading = true
-		},
-		parseValue(value) {
-			if (value === undefined || value === null || value === '') {
-				return []
-			}
-			if (Array.isArray(value)) {
-				return value.map(item => item.url || item).filter(item => item)
-			}
-			return value.split(',').map(item => item.trim()).filter(item => item)
-		},
-		async httpRequest(file) {
-			await httpRequest(file, fileName(file), this.uploadDirectory, this.maxValue)
-		},
-		uploadProcess(event) {
-			this.progressFlag = true
-			this.loadProgress = parseInt(event.percent)
-			if (this.loadProgress >= 100) {
-				this.loadProgress = 100
-				setTimeout(() => {
-					this.progressFlag = false
-				}, 1000)
-			}
-		},
-		async changes(file) {
-			if (file.status === 'ready') {
-				return
-			}
-			if (!this.isImage(file.name)) {
-				this.$message.warning('仅支持 JPG、JPEG、PNG、GIF、BMP 图片')
-				return
-			}
-			if (!beforeAvatarUpload(file.raw || file, [], this.maxValue)) {
-				this.$message.error('图片大小不能超过 ' + this.maxValue + ' MB')
-				return
-			}
-			const url = file.raw && file.raw.url ? file.raw.url : file.url
-			if (!url) {
-				return
-			}
-			// 单图模式:直接覆盖
-			if (this.limit === 1) {
-				this.dataList = []
-				this.dataListNew = []
-				this.fileList = []
-			} else {
-				if (this.dataListNew.length >= this.limit) {
-					this.$message.warning(`当前限制上传 ${this.limit} 张图片`)
-					return
-				}
-				const exists = this.dataListNew.some(item => item.url === url || item.name === file.name)
-				if (exists) {
-					this.$message.error(`${file.name}已存在,无法重复上传`)
-					return
-				}
-			}
-			const item = {
-				name: file.name,
-				url: url,
-				status: 'success',
-				createTime: moment(new Date()).format('YYYY-MM-DD HH:mm:ss'),
-				createBy: {
-					id: this.$store.state.user.id,
-					name: this.$store.state.user.name
-				}
-			}
-			await this.fillPreviewUrl(item)
-			this.fileList.push(item)
-			this.dataList.push(item)
-			this.dataListNew.push(item)
-			this.emitValue()
-		},
-		async fillPreviewUrl(item) {
-			if (!item.url) {
-				return
-			}
-			if (item.url.indexOf('http') === 0) {
-				item.lsUrl = item.url
-				return
-			}
-			try {
-				item.lsUrl = await this.ossService.getTemporaryUrl(item.url)
-			} catch (e) {
-				item.lsUrl = item.url
-			}
-		},
-		deleteById(row, index) {
-			this.dataListNew.splice(index, 1)
-			this.dataList = this.dataList.filter(item => item.url !== row.url)
-			this.fileList = this.fileList.filter(item => item.url !== row.url)
-			this.emitValue()
-		},
-		clearUpload() {
-			this.$refs.upload && this.$refs.upload.clearFiles()
-			this.dataList = []
-			this.dataListNew = []
-			this.fileList = []
-			this.innerValue = ''
-			this.$emit('update:modelValue', '')
-		},
-		getDataList() {
-			return this.dataListNew
-		},
-		checkProgress() {
-			if (this.progressFlag === true) {
-				this.$message.warning('请等待图片上传完成后再进行操作')
-				return true
-			}
-			if (this.fileLoading === false) {
-				this.$message.warning('请等待图片加载完成后再进行操作')
-				return true
-			}
-			const invalidFile = this.dataListNew.find(file => !file.url)
-			if (invalidFile) {
-				this.$message.warning(`${invalidFile.name}的URL为空,请检查后重新上传`)
-				return true
-			}
-			return false
-		},
-		emitValue() {
-			const value = this.dataListNew.map(item => item.url).filter(item => item).join(',')
-			this.innerValue = value
-			this.$emit('update:modelValue', value)
-		},
-		isImage(name) {
-			const suffix = (name || '').substring((name || '').lastIndexOf('.') + 1).toLowerCase()
-			return ['jpg', 'jpeg', 'png', 'gif', 'bmp'].includes(suffix)
-		},
-		getFileNameByUrl(url) {
-			if (!url) {
-				return ''
-			}
-			const arr = url.split('/')
-			return arr[arr.length - 1]
-		},
-		onExceed() {
-			this.$message.warning(`当前限制上传 ${this.limit} 张图片`)
-		}
-	}
-}
-</script>
-
-<style scoped>
-.dish-image-upload {
-	width: 100%;
-}
-
-.image-list {
-	display: flex;
-	flex-wrap: wrap;
-	gap: 10px;
-	margin-top: 10px;
-}
-
-.image-item {
-	width: 104px;
-	position: relative;
-	border-radius: 4px;
-	background-color: #f7f9fa;
-	padding: 6px;
-}
-
-.image-thumb {
-	width: 92px;
-	height: 92px;
-	border-radius: 4px;
-	background-color: #f0f2f5;
-}
-
-.image-actions {
-	position: absolute;
-	top: 6px;
-	right: 6px;
-	width: 24px;
-	height: 24px;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	border-radius: 0 4px 0 4px;
-	background: rgba(245, 108, 108, 0.9);
-	color: #fff;
-}
-
-.image-name {
-	margin-top: 4px;
-	font-size: 12px;
-	line-height: 18px;
-	overflow: hidden;
-	white-space: nowrap;
-	text-overflow: ellipsis;
-}
-
-.image-error {
-	width: 100%;
-	height: 100%;
-	display: flex;
-	align-items: center;
-	justify-content: center;
-	color: #909399;
-	font-size: 12px;
-}
-
-.el-upload__tip {
-	color: #909399;
-	line-height: 20px;
-}
-</style>

+ 0 - 262
src/views/hotel/dish/DishManagement.vue

@@ -1,262 +0,0 @@
-<template>
-	<div class="page">
-		<el-form :inline="true" v-if="searchVisible" class="query-form m-b-10" ref="searchForm" :model="searchForm"
-			@keyup.enter.native="refreshList()" @submit.native.prevent>
-			<el-form-item label="菜品名称" prop="name">
-				<el-input v-model="searchForm.name" placeholder="请输入菜品名称" clearable></el-input>
-			</el-form-item>
-			<el-form-item label="分类" prop="categoryId">
-				<el-select v-model="searchForm.categoryId" placeholder="请选择分类" clearable style="width: 140px">
-					<el-option v-for="item in categoryList" :key="item.id" :label="item.name" :value="item.id"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item label="状态" prop="status">
-				<el-select v-model="searchForm.status" placeholder="请选择状态" clearable style="width: 120px">
-					<el-option label="在售" value="ON_SALE"></el-option>
-					<el-option label="下架" value="OFF_SHELF"></el-option>
-				</el-select>
-			</el-form-item>
-			<el-form-item>
-				<el-button type="primary" @click="refreshList()" icon="el-icon-search">查询</el-button>
-				<el-button @click="resetSearch()" icon="el-icon-refresh-right">重置</el-button>
-			</el-form-item>
-		</el-form>
-
-		<div class="jp-table top">
-			<vxe-toolbar :refresh="{ query: refreshList }" custom>
-				<template #buttons>
-					<el-button v-if="hasPermission('hotel:admin:dish:add')" type="primary" icon="el-icon-plus"
-						@click="add()">新建</el-button>
-					<el-button v-if="hasPermission('hotel:admin:dish:delete')" type="danger" icon="el-icon-delete"
-						@click="batchDelete()"
-						:disabled="$refs.dataTable && $refs.dataTable.getCheckboxRecords().length === 0"
-						plain>删除</el-button>
-				</template>
-				<template #tools>
-					<vxe-button text type="primary" :title="searchVisible ? '收起检索' : '展开检索'" icon="vxe-icon-search"
-						class="tool-btn" @click="searchVisible = !searchVisible"></vxe-button>
-				</template>
-			</vxe-toolbar>
-			<div style="height: calc(100% - 90px)">
-				<vxe-table border="inner" auto-resize resizable height="auto" :loading="loading" ref="dataTable"
-					show-header-overflow show-overflow highlight-hover-row :menu-config="{}" :data="dataList"
-					:checkbox-config="{}">
-					<vxe-column type="seq" width="60" title="序号"></vxe-column>
-					<vxe-column type="checkbox" width="60"></vxe-column>
-					<vxe-column width="80" align="center" title="菜品图片" field="image">
-						<template #default="scope">
-							<el-image v-if="scope.row.previewImageUrl" :src="scope.row.previewImageUrl"
-								:preview-src-list="scope.row.previewImageList" fit="cover"
-								style="width: 44px; height: 44px; border-radius: 4px;" hide-on-click-modal
-								append-to-body :z-index="9999"></el-image>
-							<span v-else>暂无图片</span>
-						</template>
-					</vxe-column>
-					<vxe-column min-width="150" align="left" title="菜品名称" field="name"></vxe-column>
-					<vxe-column width="100" align="center" title="分类" field="categoryName"></vxe-column>
-					<vxe-column width="90" align="right" title="价格" field="price">
-						<template #default="scope">¥{{ scope.row.price }}</template>
-					</vxe-column>
-					<vxe-column width="100" align="center" title="制作时长" field="prepTime">
-						<template #default="scope">{{ scope.row.prepTime ? scope.row.prepTime + '分钟' : '-' }}</template>
-					</vxe-column>
-					<vxe-column width="80" align="center" title="月销" field="sales"></vxe-column>
-					<vxe-column width="100" align="center" title="状态" field="status">
-						<template #default="scope">
-							<el-tag :type="scope.row.status === 'ON_SALE' ? 'success' : 'info'">
-								{{ scope.row.status === 'ON_SALE' ? '在售' : '下架' }}
-							</el-tag>
-						</template>
-					</vxe-column>
-					<vxe-column width="80" align="center" title="售罄" field="soldOut">
-						<template #default="scope">
-							<el-tag v-if="scope.row.soldOut === 1" type="danger">售罄</el-tag>
-							<span v-else>-</span>
-						</template>
-					</vxe-column>
-					<vxe-column width="80" align="center" title="推荐" field="isRecommended">
-						<template #default="scope">
-							<el-tag v-if="scope.row.isRecommended === 1" type="warning">推荐</el-tag>
-							<span v-else>-</span>
-						</template>
-					</vxe-column>
-					<vxe-column width="70" align="center" title="排序" field="sortOrder"></vxe-column>
-					<vxe-column title="操作" width="220px" fixed="right" align="center">
-						<template #default="scope">
-							<el-button v-if="hasPermission('hotel:admin:dish:edit')" text type="primary" size="small"
-								@click="edit(scope.row.id)">修改</el-button>
-							<el-button v-if="hasPermission('hotel:admin:dish:edit')" text type="primary" size="small"
-								@click="changeStatus(scope.row)">{{ scope.row.status === 'ON_SALE' ? '下架' : '上架' }}</el-button>
-							<el-button v-if="hasPermission('hotel:admin:dish:delete')" text type="primary" size="small"
-								@click="doDelete(scope.row.id)">删除</el-button>
-						</template>
-					</vxe-column>
-				</vxe-table>
-				<vxe-pager background :current-page="tablePage.currentPage" :page-size="tablePage.pageSize"
-					:total="tablePage.total" :page-sizes="[10, 20, 100, 1000, { label: '全量数据', value: 1000000 }]"
-					:layouts="['PrevPage', 'JumpNumber', 'NextPage', 'FullJump', 'Sizes', 'Total']"
-					@page-change="currentChangeHandle">
-				</vxe-pager>
-			</div>
-		</div>
-		<DishForm ref="dishForm" @refreshList="refreshList"></DishForm>
-	</div>
-</template>
-
-<script>
-import HotelDishService from '@/api/hotel/HotelDishService'
-import DishForm from './DishForm'
-import OSSSerivce from '@/api/sys/OSSService'
-export default {
-	data() {
-		return {
-			searchVisible: true,
-			searchForm: {
-				name: '',
-				categoryId: '',
-				status: ''
-			},
-			categoryList: [],
-			dataList: [],
-			tablePage: {
-				total: 0,
-				currentPage: 1,
-				pageSize: 10,
-				orders: []
-			},
-			loading: false
-		}
-	},
-	hotelDishService: null,
-	ossService: null,
-	created() {
-		this.hotelDishService = new HotelDishService()
-		this.ossService = new OSSSerivce()
-	},
-	components: {
-		DishForm
-	},
-	mounted() {
-		this.refreshList()
-		this.loadCategories()
-	},
-	activated() {
-		this.refreshList()
-	},
-	methods: {
-		add() {
-			this.$refs.dishForm.init('add', '')
-		},
-		edit(id) {
-			this.$refs.dishForm.init('edit', id)
-		},
-		loadCategories() {
-			this.hotelDishService.categoryAll().then((data) => {
-				this.categoryList = Array.isArray(data) ? data : (data.list || [])
-			})
-		},
-		refreshList() {
-			this.loading = true
-			this.hotelDishService.page({
-				'current': this.tablePage.currentPage,
-				'size': this.tablePage.pageSize,
-				'orders': this.tablePage.orders,
-				...this.searchForm
-			}).then((data) => {
-				this.dataList = data.records || []
-				this.tablePage.total = data.total || 0
-				this.loading = false
-				this.loadImagePreview()
-			}).catch(() => {
-				this.loading = false
-			})
-		},
-		loadImagePreview() {
-			this.dataList.forEach((row) => {
-				const urls = this.getImageUrls(row.image)
-				if (urls.length === 0) {
-					row.previewImageUrl = ''
-					row.previewImageList = []
-					return
-				}
-				row.previewImageList = urls
-				row.previewImageUrl = urls[0]
-				Promise.all(urls.map(url => this.getPreviewUrl(url))).then((list) => {
-					row.previewImageList = list
-					row.previewImageUrl = list[0]
-				})
-			})
-		},
-		getImageUrls(value) {
-			if (!value) {
-				return []
-			}
-			return value.split(',').map(item => item.trim()).filter(item => item)
-		},
-		getPreviewUrl(url) {
-			if (url.indexOf('http') === 0) {
-				return Promise.resolve(url)
-			}
-			return this.ossService.getTemporaryUrl(url).catch(() => url)
-		},
-		currentChangeHandle({ currentPage, pageSize }) {
-			this.tablePage.currentPage = currentPage
-			this.tablePage.pageSize = pageSize
-			this.refreshList()
-		},
-		changeStatus(row) {
-			const status = row.status === 'ON_SALE' ? 'OFF_SHELF' : 'ON_SALE'
-			const message = status === 'ON_SALE' ? '确定上架该菜品吗?' : '确定下架该菜品吗?'
-			this.$confirm(message, '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.hotelDishService.updateStatus(row.id, status).then((data) => {
-					this.$message.success(data)
-					this.refreshList()
-				})
-			})
-		},
-		doDelete(id) {
-			this.$confirm('确定删除该菜品吗?删除后不可恢复!', '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelDishService.delete(id).then((data) => {
-					this.$message.success(data)
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		},
-		batchDelete() {
-			const ids = this.$refs.dataTable.getCheckboxRecords().map(item => item.id).join(',')
-			this.$confirm(`确定删除所选的 ${ids.split(',').length} 个菜品吗?`, '提示', {
-				confirmButtonText: '确定',
-				cancelButtonText: '取消',
-				type: 'warning'
-			}).then(() => {
-				this.loading = true
-				this.hotelDishService.batch({ ids: ids.split(','), operation: 'DELETE' }).then((data) => {
-					this.$message.success(data)
-					this.refreshList()
-					this.loading = false
-				}).catch(() => {
-					this.loading = false
-				})
-			})
-		},
-		resetSearch() {
-			this.$refs.searchForm.resetFields()
-			this.searchForm.categoryId = ''
-			this.tablePage.currentPage = 1
-			this.refreshList()
-		}
-	}
-}
-</script>

+ 0 - 22
src/views/hotel/hotelfront/HotelFrontOrders.vue

@@ -1,22 +0,0 @@
-<template>
-  <div class="hotelfront-orders-page">
-    <!-- 宾馆前台 - 订单总览页 -->
-    <h2>宾馆前台订单总览</h2>
-    <p>待实现:参考HTML原型 hotelfront-orders.html</p>
-    <p>关键功能:所有订单查看、退单管理</p>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-
-onMounted(() => {
-  console.log('HotelFrontOrders mounted')
-})
-</script>
-
-<style scoped>
-.hotelfront-orders-page {
-  padding: 20px;
-}
-</style>

+ 0 - 39
src/views/hotel/kitchen/KitchenOrders.vue

@@ -1,39 +0,0 @@
-<template>
-  <div class="kitchen-orders-page">
-    <!-- 厨房端 - 订单接收页(需WebSocket + 语音播报) -->
-    <h2>厨房订单接收</h2>
-    <p>待实现:参考HTML原型 kitchen-orders.html</p>
-    <p>关键功能:WebSocket实时接收、Web Speech API语音播报</p>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted, onUnmounted } from 'vue'
-import hotelWS from '@/utils/websocket'
-
-onMounted(() => {
-  console.log('KitchenOrders mounted')
-  // TODO: 连接WebSocket
-  // hotelWS.connect(token, handleMessage, handleError)
-})
-
-onUnmounted(() => {
-  // TODO: 断开WebSocket
-  // hotelWS.disconnect()
-})
-
-function handleMessage(event) {
-  const message = JSON.parse(event.data)
-  console.log('收到消息:', message)
-}
-
-function handleError(error) {
-  console.error('WebSocket错误:', error)
-}
-</script>
-
-<style scoped>
-.kitchen-orders-page {
-  padding: 20px;
-}
-</style>

+ 0 - 21
src/views/hotel/restaurant/RestaurantOrders.vue

@@ -1,21 +0,0 @@
-<template>
-  <div class="restaurant-orders-page">
-    <!-- 餐厅前台 - 订单管理页 -->
-    <h2>餐厅前台订单管理</h2>
-    <p>待实现:参考HTML原型 restaurant-orders.html</p>
-  </div>
-</template>
-
-<script setup>
-import { ref, onMounted } from 'vue'
-
-onMounted(() => {
-  console.log('RestaurantOrders mounted')
-})
-</script>
-
-<style scoped>
-.restaurant-orders-page {
-  padding: 20px;
-}
-</style>