瀏覽代碼

fix(types): fix string | null mismatch in index.vue

Ensured appStore.lang is explicitly cast to string in the app store
definition to comply with formatAddress's expected type.
FanLide 2 周之前
父節點
當前提交
a2e9c25222
共有 59 個文件被更改,包括 8989 次插入2797 次删除
  1. 171 0
      VANT_TYPE_GUIDE.md
  2. 0 0
      doc/DEVELOPMENT_SUMMARY.md
  3. 164 0
      doc/GEOCODING.md
  4. 0 0
      doc/PROGRESS.md
  5. 0 0
      doc/QUICK_START.md
  6. 0 0
      doc/REFACTOR_PLAN.md
  7. 259 0
      doc/architecture_design.md
  8. 12 3
      src/components.d.ts
  9. 281 0
      src/components/DeliveryFooter.vue
  10. 112 0
      src/components/LanguageSwitcher.vue
  11. 112 0
      src/components/SearchBar.vue
  12. 159 0
      src/components/ShopCard.vue
  13. 260 0
      src/components/ShopHeader.vue
  14. 238 0
      src/components/TableFooter.vue
  15. 186 0
      src/components/TableHeader.vue
  16. 228 0
      src/components/buffet/BuffetTimer.vue
  17. 74 22
      src/composables/useRole.ts
  18. 141 583
      src/locale/en.json
  19. 142 625
      src/locale/ja.json
  20. 0 36
      src/locale/uni-app.ja.json
  21. 137 575
      src/locale/zh-Hans.json
  22. 144 339
      src/locale/zh-Hant.json
  23. 115 13
      src/router/guards.ts
  24. 189 0
      src/router/routes.ts
  25. 5 0
      src/store/index.ts
  26. 55 0
      src/store/modules/admin.ts
  27. 96 47
      src/store/modules/app.ts
  28. 277 0
      src/store/modules/buffet.ts
  29. 305 0
      src/store/modules/company.ts
  30. 80 0
      src/store/modules/merchant.ts
  31. 75 0
      src/store/modules/pos.ts
  32. 243 86
      src/store/modules/user.ts
  33. 6 6
      src/utils/format.ts
  34. 130 0
      src/utils/geocoding.ts
  35. 17 3
      src/utils/image.ts
  36. 28 12
      src/utils/index.ts
  37. 31 3
      src/utils/price.ts
  38. 11 11
      src/utils/storage.ts
  39. 31 6
      src/utils/validate.ts
  40. 163 0
      src/views/admin/dashboard.vue
  41. 194 0
      src/views/admin/merchants.vue
  42. 163 0
      src/views/admin/users.vue
  43. 268 0
      src/views/buffet/menu.vue
  44. 315 0
      src/views/buffet/select.vue
  45. 304 363
      src/views/index/index.vue
  46. 61 32
      src/views/login/components/PasswordLogin.vue
  47. 57 32
      src/views/login/components/PhoneCodeLogin.vue
  48. 252 0
      src/views/merchant/buffet-plans.vue
  49. 203 0
      src/views/merchant/dashboard.vue
  50. 203 0
      src/views/merchant/orders.vue
  51. 113 0
      src/views/mine/mine.vue
  52. 375 0
      src/views/owner/dashboard.vue
  53. 238 0
      src/views/owner/reports.vue
  54. 254 0
      src/views/owner/shops.vue
  55. 276 0
      src/views/pos/order-detail.vue
  56. 243 0
      src/views/pos/orders.vue
  57. 333 0
      src/views/pos/tables.vue
  58. 130 0
      src/views/pos/welcome.vue
  59. 330 0
      src/views/scan/index.vue

+ 171 - 0
VANT_TYPE_GUIDE.md

@@ -0,0 +1,171 @@
+# Vant 4 组件类型使用指南
+
+本文档记录 Vant 4 组件的常见类型问题和正确用法,避免在开发中出现类型错误。
+
+## 1. van-tag 组件
+
+### size 属性
+❌ **错误用法**:
+```vue
+<van-tag size="mini">标签</van-tag>
+```
+
+✅ **正确用法**:
+```vue
+<van-tag size="small">标签</van-tag>
+<van-tag size="medium">标签</van-tag>
+<van-tag size="large">标签</van-tag>
+```
+
+**类型定义**:`'large' | 'medium' | 'small'`
+
+### type 属性
+✅ **可用值**:
+```vue
+<van-tag type="default">默认</van-tag>
+<van-tag type="primary">主要</van-tag>
+<van-tag type="success">成功</van-tag>
+<van-tag type="warning">警告</van-tag>
+<van-tag type="danger">危险</van-tag>
+```
+
+## 2. van-button 组件
+
+### size 属性
+✅ **可用值**:
+```vue
+<van-button size="large">大按钮</van-button>
+<van-button size="normal">普通按钮</van-button>
+<van-button size="small">小按钮</van-button>
+<van-button size="mini">迷你按钮</van-button>
+```
+
+**类型定义**:`'large' | 'normal' | 'small' | 'mini'`
+
+### type 属性
+✅ **可用值**:
+```vue
+<van-button type="default">默认按钮</van-button>
+<van-button type="primary">主要按钮</van-button>
+<van-button type="success">成功按钮</van-button>
+<van-button type="warning">警告按钮</van-button>
+<van-button type="danger">危险按钮</van-button>
+```
+
+## 3. van-icon 组件
+
+### size 属性
+✅ **可接受类型**:`string | number`
+```vue
+<van-icon name="success" size="20" />
+<van-icon name="success" size="20px" />
+<van-icon name="success" :size="20" />
+```
+
+## 4. van-field 组件
+
+### type 属性
+✅ **常用值**:
+```vue
+<van-field type="text" />
+<van-field type="tel" />
+<van-field type="digit" />
+<van-field type="number" />
+<van-field type="password" />
+```
+
+## 5. van-picker 组件
+
+### columns 属性
+✅ **正确类型**:
+```typescript
+// 简单数组
+const columns = ['选项1', '选项2', '选项3']
+
+// 对象数组
+const columns = [
+  { text: '选项1', value: '1' },
+  { text: '选项2', value: '2' }
+]
+
+// 多列
+const columns = [
+  [{ text: '选项1', value: '1' }],
+  [{ text: '选项A', value: 'a' }]
+]
+```
+
+## 6. van-stepper 组件
+
+### 数值属性
+✅ **正确用法**:
+```vue
+<van-stepper v-model="count" :min="0" :max="10" :step="1" />
+```
+
+⚠️ **注意**:min、max、step 都应该是 number 类型,不要使用字符串。
+
+## 7. 事件处理器
+
+### 标准事件
+✅ **正确写法**:
+```vue
+<!-- 使用箭头函数 -->
+<van-button @click="() => handleClick(item)">点击</van-button>
+
+<!-- 直接绑定方法 -->
+<van-button @click="handleClick">点击</van-button>
+
+<!-- 传递事件参数 -->
+<van-field @update:model-value="(val) => handleUpdate(val)" />
+```
+
+## 8. 常见类型错误及解决方案
+
+### 错误 1: Type '"mini"' is not assignable to type 'TagSize'
+**原因**:van-tag 不支持 `size="mini"`
+**解决**:改用 `size="small"`
+
+### 错误 2: Type 'string' is not assignable to type 'number'
+**原因**:某些属性需要 number 类型
+**解决**:使用 `:prop="value"` 而不是 `prop="value"`
+
+### 错误 3: Property does not exist on type
+**原因**:TypeScript 无法推断类型
+**解决**:添加类型注解或使用 `as` 断言
+
+## 9. 最佳实践
+
+1. **使用 TypeScript 的类型检查**
+   ```bash
+   npx tsc --noEmit
+   ```
+
+2. **参考官方文档**
+   访问 [Vant 4 官方文档](https://vant-ui.github.io/vant/#/zh-CN) 查看最新的 API 定义。
+
+3. **使用 IDE 的类型提示**
+   在 VSCode 中,将鼠标悬停在组件上可以看到完整的类型定义。
+
+4. **避免使用魔法字符串**
+   对于有固定选项的属性,优先使用常量或枚举:
+   ```typescript
+   const TAG_SIZES = {
+     SMALL: 'small',
+     MEDIUM: 'medium',
+     LARGE: 'large'
+   } as const
+   ```
+
+## 10. 已修复的问题
+
+### 2026-01-12
+- ✅ 修复了 `src/views/buffet/menu.vue` 中 van-tag 的 `size="mini"` 错误
+- ✅ 修复了 `src/views/buffet/select.vue` 中 van-tag 的 `size="mini"` 错误
+- ✅ 修复了 `src/views/mine/mine.vue` 中 van-tag 的 `size="mini"` 错误
+- ✅ 修复了 `src/components/TableHeader.vue` 中 van-tag 的 `size="small"` 错误
+
+---
+
+**更新时间**:2026-01-12
+**维护者**:开发团队

+ 0 - 0
DEVELOPMENT_SUMMARY.md → doc/DEVELOPMENT_SUMMARY.md


+ 164 - 0
doc/GEOCODING.md

@@ -0,0 +1,164 @@
+# 后端反向地理编码 API 规范
+
+## 接口定义
+
+### 反向地理编码接口
+
+**请求**:
+
+```
+GET /api/geocoding/reverse?lat={latitude}&lng={longitude}
+```
+
+**参数**:
+
+- `lat` (必填): 纬度,如 `35.6762`
+- `lng` (必填): 经度,如 `139.6503`
+
+**响应** (成功):
+
+```json
+{
+  "success": true,
+  "data": {
+    "address": "渋谷区",
+    "city": "東京都",
+    "district": "渋谷区",
+    "country": "日本",
+    "latitude": 35.6762,
+    "longitude": 139.6503
+  }
+}
+```
+
+**响应** (失败):
+
+```json
+{
+  "success": false,
+  "message": "Geocoding failed"
+}
+```
+
+---
+
+## Java 后端实现建议
+
+### 1. Spring Boot Controller
+
+```java
+@RestController
+@RequestMapping("/api/geocoding")
+public class GeocodingController {
+
+    @Autowired
+    private GeocodingService geocodingService;
+
+    @GetMapping("/reverse")
+    public ResponseEntity<ApiResponse<GeocodingResult>> reverseGeocode(
+        @RequestParam Double lat,
+        @RequestParam Double lng
+    ) {
+        try {
+            GeocodingResult result = geocodingService.reverseGeocode(lat, lng);
+            return ResponseEntity.ok(ApiResponse.success(result));
+        } catch (Exception e) {
+            return ResponseEntity.ok(ApiResponse.error("Geocoding failed"));
+        }
+    }
+}
+```
+
+### 2. Service 层(带缓存)
+
+```java
+@Service
+public class GeocodingService {
+
+    @Autowired
+    private RestTemplate restTemplate;
+
+    @Value("${google.maps.api.key}")
+    private String googleApiKey;
+
+    @Cacheable(value = "geocoding", key = "#lat + '_' + #lng")
+    public GeocodingResult reverseGeocode(Double lat, Double lng) {
+        String url = String.format(
+            "https://maps.googleapis.com/maps/api/geocode/json?latlng=%f,%f&key=%s&language=ja",
+            lat, lng, googleApiKey
+        );
+
+        GoogleGeocodingResponse response = restTemplate.getForObject(
+            url,
+            GoogleGeocodingResponse.class
+        );
+
+        if (response != null && "OK".equals(response.getStatus())) {
+            return parseGoogleResponse(response);
+        }
+
+        throw new RuntimeException("Geocoding failed");
+    }
+
+    private GeocodingResult parseGoogleResponse(GoogleGeocodingResponse response) {
+        // 解析 Google Maps API 响应
+        // ...
+        return result;
+    }
+}
+```
+
+### 3. 配置缓存(Redis)
+
+```java
+@Configuration
+@EnableCaching
+public class CacheConfig {
+
+    @Bean
+    public RedisCacheManager cacheManager(RedisConnectionFactory factory) {
+        RedisCacheConfiguration config = RedisCacheConfiguration.defaultCacheConfig()
+            .entryTtl(Duration.ofDays(30)); // 缓存30天
+
+        return RedisCacheManager.builder(factory)
+            .cacheDefaults(config)
+            .build();
+    }
+}
+```
+
+### 4. application.yml
+
+```yaml
+google:
+  maps:
+    api:
+      key: ${GOOGLE_MAPS_API_KEY}
+
+spring:
+  redis:
+    host: localhost
+    port: 6379
+```
+
+---
+
+## 优势
+
+1. **安全**: API Key 保存在后端环境变量中
+2. **缓存**: 相同坐标不会重复调用 Google API,节省费用
+3. **灵活**: 可以随时切换地图服务商(Google/高德/百度)
+4. **监控**: 统一记录调用日志,便于分析和优化
+
+---
+
+## 前端调用示例
+
+前端只需发送坐标到后端:
+
+```typescript
+const result = await reverseGeocode(35.6762, 139.6503);
+// 后端会返回: "渋谷区"
+```
+
+如果后端接口不可用,会自动降级到 Nominatim(免费服务)。

+ 0 - 0
PROGRESS.md → doc/PROGRESS.md


+ 0 - 0
QUICK_START.md → doc/QUICK_START.md


+ 0 - 0
REFACTOR_PLAN.md → doc/REFACTOR_PLAN.md


+ 259 - 0
doc/architecture_design.md

@@ -0,0 +1,259 @@
+# 多角色餐饮点餐系统架构设计 v3
+
+## 一、手机端用户入口架构 (C 端)
+
+系统根据用户进入的方式,将 App 划分为三种上下文模式 (Context Mode)。不同模式决定了用户的功能权限、UI 展示以及路由行为。
+
+```mermaid
+graph TD
+    Entry{用户入口}
+
+    Entry -->|打开APP/首页| Mode1[平台模式 Platform Mode]
+    Entry -->|扫描店铺码/点击分享链接| Mode2[店铺模式 Shop Mode]
+    Entry -->|扫描桌位码| Mode3[桌位模式 Table Mode]
+
+    subgraph Mode1 [平台模式 (流量分发)]
+        Home[首页 Index] -->|推荐| ShopList[商家列表]
+        Home -->|推荐| DishList[菜品列表]
+        ShopList -->|选择店铺| ShopMenu[店铺菜单]
+        DishList -->|点击菜品| ShopMenu
+    end
+
+    subgraph Mode2 [店铺模式 (外卖/预约)]
+        ShopQR[店铺码] -->|解析| ShopMenu
+        Note2[仅限: 外卖 / 自提 / 预约]
+    end
+
+    subgraph Mode3 [桌位模式 (堂食点餐)]
+        TableQR[桌位码] -->|解析| TableMenu[堂食菜单]
+        Note3[仅限: 扫码下单 / 加菜]
+    end
+
+    ShopMenu -.->|扫桌码升级| TableMenu
+```
+
+### 1.1 三种模式详解
+
+#### 1. **平台模式 (Platform Mode)**
+
+- **场景**: 用户直接打开 APP,或访问域名根路径。
+- **定位**: 流量分发、营销推广。
+- **功能**:
+  - 展示推荐商家、热门店铺。
+  - 展示推荐菜品(点击直接跳转对应店铺外卖页)。
+  - 全局搜索(搜店铺、搜菜品)。
+- **状态**: `shopId` 为空,`tableCode` 为空。
+- **路由**: `/index`
+
+#### 2. **店铺模式 (Shop Mode)**
+
+- **场景**: 扫描店铺推广码、点击店铺分享链接、从平台首页点击店铺进入。
+- **定位**: 单店的外卖与预约服务。
+- **限制**:
+  - **锁定**: 用户被锁定在该店铺内,无法"切换桌号"(因为没桌号)。
+  - **排他**: 除非用户手动返回平台首页或扫描其他店铺码,否则一直在该店铺上下文中。
+  - **功能限制**: **不可堂食下单**(没有桌号信息)。只能选择 "外卖配送"、"到店自提" 或 "预约订座"。
+- **状态**: `shopId` 有值,`tableCode` 为空。
+- **路由**: `/menu?shopId={id}`
+
+#### 3. **桌位模式 (Table Mode)**
+
+- **场景**: 扫描餐桌二维码。
+- **定位**: 店内堂食下单。
+- **限制**:
+  - **强锁定**: 绑定 `shopId` 和 `tableCode`。
+  - **禁止切换**: 无法切换店铺,无法切换桌号(除非扫描新二维码覆盖当前会话)。
+  - **功能**: 开启 "堂食下单" 功能。
+- **状态**: `shopId` 有值,`tableCode` 有值。
+- **路由**: `/menu?shopId={id}&tableCode={code}`
+
+---
+
+## 二、核心业务流程设计
+
+### 2.1 状态管理 (Store)
+
+在 `user.ts` 或 `app.ts` 中维护全局上下文:
+
+```typescript
+interface AppContext {
+  mode: "platform" | "shop" | "table";
+  shopId?: string; // 当前锁定店铺
+  tableCode?: string; // 当前锁定桌位
+  shopInfo?: Shop; // 店铺详情缓存
+}
+```
+
+### 2.2 路由规则与守卫
+
+| 路由     | 描述     | 守卫逻辑                                                                                                                                            |
+| -------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
+| `/index` | 平台首页 | 清除 currentShop/tableCode,进入 Platform Mode                                                                                                      |
+| `/scan`  | 扫码解析 | 解析参数 -> 判断模式 -> 写入 Store -> 跳转                                                                                                          |
+| `/menu`  | 菜单页   | 必须有 `shopId` (Query 或 Store)。<br>无 `shopId` -> 重定向回 `/index`。<br>有 `tableCode` -> 显示堂食 UI。<br>无 `tableCode` -> 显示外卖/预约 UI。 |
+
+### 2.3 扫码解析逻辑 (`/scan`)
+
+1.  **获取参数**: `shopId` (必填), `tableCode` (可选)。
+2.  **逻辑判断**:
+    - **有 `tableCode`**:
+      - 模式 = **Table Mode**
+      - 调用 `initGuestSession(shopId, tableCode)`
+      - 跳转 `/menu` (携带桌位信息)
+    - **无 `tableCode`**:
+      - 模式 = **Shop Mode**
+      - 设置 `currentShopId = shopId`
+      - 跳转 `/menu` (仅店铺信息)
+
+---
+
+---
+
+## 四、UI/UX 差异化设计详解
+
+### 4.1 平台模式 (Platform Mode) - "发现与导流"
+
+**核心目标**: 帮助用户快速找到感兴趣的餐厅或美食。
+
+**页面布局**:
+
+- **顶部栏**:
+  - 📍 **位置定位**: 显示当前送餐地址/位置。
+  - 🔍 **全局搜索框**: 搜索店铺名、菜品名。
+- **内容区**:
+  - **Banner 轮播**: 平台级活动、节日促销。
+  - **金刚区 (Icons)**: 菜系分类 (日料、中餐、西餐...)、功能入口 (我的收藏、领券中心)。
+  - **推荐模块**:
+    - "猜你喜欢" (基于历史)
+    - "附近热推" (基于 LBS)
+  - **店铺列表**: 垂直滚动流,展示店铺卡片 (Logo、评分、配送时间、起送价)。
+- **底部导航**: `[首页] [订单] [我的]`
+
+### 4.2 店铺模式 (Shop Mode) - "外卖与品牌"
+
+**核心目标**: 展示店铺品牌形象,提供便捷的外卖/自提服务。
+
+**页面布局**:
+
+- **店铺头部 (Header)**:
+  - **品牌重展示**: 大幅店招背景图、Logo、店铺名称。
+  - **服务信息**: 评分 ⭐4.8、配送时间 30min、公告跑马灯。
+  - **服务切换**: [外卖配送] / [到店自提] / [预约订座] Tab 切换。
+- **菜单区**:
+  - **公告栏**: 优惠券领取、满减活动提示。
+  - **布局**: 左侧分类栏 + 右侧商品流 (标准外卖布局)。
+- **底部结算栏**:
+  - 显示:购物车预览、预估配送费、起送价进度条。
+  - 按钮:`去结算` (跳转确认订单页)。
+
+### 4.3 桌位模式 (Table Mode) - "高效点餐"
+
+**核心目标**: 快速下单,减少服务员介入,明确就餐状态。
+
+**页面布局**:
+
+- **顶部栏**:
+  - **桌台信息**: 醒目展示 `[ A1 桌 ]`,旁边可有 `多人点餐` 状态提示。
+  - **人数设置**: 快捷调整就餐人数。
+- **菜单区**:
+  - **极简头部**: 压缩店铺信息,最大化菜单展示区域。
+  - **特色功能**: `呼叫服务员` (加水/催单) 悬浮球。
+  - **商品展示**: 突出 "推荐/热销",支持大图模式。
+- **底部操作栏**:
+  - 显示:当前已选金额、数量。
+  - 按钮:`选好了` -> `确认下单` (直接落单到厨房,区别于外卖的"去结算")。
+- **特殊状态**:
+  - 下单后:底部变为 "加菜模式",显示 "已下单金额" 和 "待下单金额"。
+
+---
+
+## 五、多 Agent 并行开发计划
+
+为提高开发效率,任务可拆分为三个独立的开发轨道 (Tracks),可由多个 Agent 并行执行:
+
+### Track A: 平台前端开发 (Agent A)
+
+- **目标**: 完成 `/index` 平台首页及公共组件。
+- **任务**:
+  1.  封装通用 UI 组件: `SearchBar`, `ShopCard`, `DishCard`.
+  2.  实现 `src/views/index/index.vue`:
+      - 集成 Banner 轮播。
+      - 实现分类金刚区。
+      - 实现推荐店铺流 (Mock Data)。
+
+### Track B: 店铺与外卖流程 (Agent B)
+
+- **目标**: 完善 `/menu` 的外卖模式 (Shop Mode)。
+- **任务**:
+  1.  封装 `<ShopHeader>` 组件: 展示店招、评分、配送信息。
+  2.  封装 `<DeliveryFooter>` 组件: 购物车预览、起送价逻辑、去结算。
+  3.  改造 `menu.vue`: 适配 `shopId`,集成 B 端组件。
+
+### Track C: 堂食点餐流程 (Agent C)
+
+- **目标**: 完善 `/menu` 的堂食模式 (Table Mode)。
+- **任务**:
+  1.  封装 `<TableHeader>` 组件: 桌号显示、人数切换、呼叫服务。
+  2.  封装 `<TableFooter>` 组件: 堂食下单逻辑 (直接提交订单)。
+  3.  改造 `cart.ts`: 区分堂食购物车(需提交)与外卖购物车(本地存储)。
+
+### 集成阶段 (Integrator Agent)
+
+- **目标**: 路由整合与联调。
+- **任务**:
+
+  1.  确认 `app.ts` 状态管理无误。
+  2.  验证 `scan/index.vue` 到各模式的跳转。
+  3.  全局样式统一与代码审查。
+      Admin[系统管理员 admin]
+      end
+
+  subgraph Company[公司层]
+  Owner[老板 owner]
+  end
+
+  subgraph Shop[店铺层]
+  Manager[店长 manager]
+  Staff[员工 staff]
+  end
+
+  subgraph Customer[顾客层]
+  Customer1[顾客 customer]
+  Guest[访客 guest]
+  end
+
+  Admin -->|管理| Owner
+  Owner -->|拥有多家| Shop
+  Manager -->|管理| Staff
+  Shop -->|服务| Customer1
+  Shop -->|服务| Guest
+
+```
+
+### 4.1 角色权限矩阵
+
+```
+
+功能模块 admin owner manager staff customer
+──────────────────────────────────────────────────────────────────
+[平台管理]
+查看所有公司 ✓ ✗ ✗ ✗ ✗
+创建/编辑公司 ✓ ✗ ✗ ✗ ✗
+
+[公司/多店铺管理]
+查看公司下所有店铺 ✓ ✓ ✗ ✗ ✗
+多店铺汇总报表 ✓ ✓ ✗ ✗ ✗
+
+[单店铺管理]
+店铺仪表盘 ✓ ✓ ✓ ✗ ✗
+菜单管理 ✓ ✓ ✓ ✗ ✗
+员工管理 ✓ ✓ ✓ ✗ ✗
+
+[日常运营]
+桌位管理 ✓ ✓ ✓ ✓ ✗
+订单处理 ✓ ✓ ✓ ✓ ✗
+收银结账 ✓ ✓ ✓ ✓ ✗
+
+```
+
+```

+ 12 - 3
src/components.d.ts

@@ -7,28 +7,37 @@ export {}
 
 declare module 'vue' {
   export interface GlobalComponents {
+    BuffetTimer: typeof import('./components/buffet/BuffetTimer.vue')['default']
+    DeliveryFooter: typeof import('./components/DeliveryFooter.vue')['default']
     GoodsCard: typeof import('./components/common/GoodsCard.vue')['default']
+    LanguageSwitcher: typeof import('./components/LanguageSwitcher.vue')['default']
     RouterLink: typeof import('vue-router')['RouterLink']
     RouterView: typeof import('vue-router')['RouterView']
+    SearchBar: typeof import('./components/SearchBar.vue')['default']
+    ShopCard: typeof import('./components/ShopCard.vue')['default']
+    ShopHeader: typeof import('./components/ShopHeader.vue')['default']
+    TableFooter: typeof import('./components/TableFooter.vue')['default']
+    TableHeader: typeof import('./components/TableHeader.vue')['default']
     VanActionBar: typeof import('vant/es')['ActionBar']
     VanActionBarButton: typeof import('vant/es')['ActionBarButton']
     VanActionBarIcon: typeof import('vant/es')['ActionBarIcon']
+    VanActionSheet: typeof import('vant/es')['ActionSheet']
+    VanArea: typeof import('vant/es')['Area']
     VanBackTop: typeof import('vant/es')['BackTop']
     VanBadge: typeof import('vant/es')['Badge']
     VanButton: typeof import('vant/es')['Button']
+    VanCalendar: typeof import('vant/es')['Calendar']
     VanCell: typeof import('vant/es')['Cell']
     VanCellGroup: typeof import('vant/es')['CellGroup']
     VanCheckbox: typeof import('vant/es')['Checkbox']
     VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
     VanCouponList: typeof import('vant/es')['CouponList']
+    VanDivider: typeof import('vant/es')['Divider']
     VanDropdownItem: typeof import('vant/es')['DropdownItem']
     VanDropdownMenu: typeof import('vant/es')['DropdownMenu']
     VanEmpty: typeof import('vant/es')['Empty']
     VanField: typeof import('vant/es')['Field']
     VanForm: typeof import('vant/es')['Form']
-    VanGoodsAction: typeof import('vant/es')['GoodsAction']
-    VanGoodsActionButton: typeof import('vant/es')['GoodsActionButton']
-    VanGoodsActionIcon: typeof import('vant/es')['GoodsActionIcon']
     VanGrid: typeof import('vant/es')['Grid']
     VanGridItem: typeof import('vant/es')['GridItem']
     VanIcon: typeof import('vant/es')['Icon']

+ 281 - 0
src/components/DeliveryFooter.vue

@@ -0,0 +1,281 @@
+<template>
+  <div class="delivery-footer" :class="{ expanded: showCart }">
+    <!-- 购物车抽屉 -->
+    <transition name="van-slide-up">
+      <div v-show="showCart" class="cart-drawer">
+        <div class="cart-header">
+          <span>购物车</span>
+          <van-icon name="delete-o" @click="handleClearCart" />
+        </div>
+        <div class="cart-list">
+          <div v-for="item in cartItems" :key="item.id" class="cart-item">
+            <div class="item-info">
+              <span class="item-name">{{ item.name }}</span>
+              <span class="item-price">¥{{ item.price }}</span>
+            </div>
+            <van-stepper
+              :model-value="item.quantity"
+              :min="0"
+              @change="(val) => handleQuantityChange(item, val)"
+            />
+          </div>
+        </div>
+      </div>
+    </transition>
+
+    <!-- 底部栏 -->
+    <div class="footer-bar">
+      <!-- 购物车图标 -->
+      <div class="cart-icon-wrapper" @click="toggleCart">
+        <div class="cart-icon">
+          <van-icon name="shopping-cart-o" size="24" />
+          <van-badge v-if="totalQuantity > 0" :content="totalQuantity" />
+        </div>
+      </div>
+
+      <!-- 价格信息 -->
+      <div class="price-info">
+        <div class="total-price">
+          <span class="label">合计:</span>
+          <span class="amount">¥{{ totalPrice.toFixed(2) }}</span>
+        </div>
+        <div v-if="deliveryFee > 0" class="delivery-fee">
+          配送费 ¥{{ deliveryFee }}
+        </div>
+        <div v-if="minPrice && totalPrice < minPrice" class="min-price-tip">
+          还差 ¥{{ (minPrice - totalPrice).toFixed(2) }} 起送
+        </div>
+      </div>
+
+      <!-- 结算按钮 -->
+      <van-button
+        type="primary"
+        round
+        class="checkout-btn"
+        :disabled="!canCheckout"
+        @click="handleCheckout"
+      >
+        {{ checkoutText }}
+      </van-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { showConfirmDialog } from 'vant'
+
+export interface CartItem {
+  id: string
+  name: string
+  price: number
+  quantity: number
+}
+
+interface Props {
+  cartItems?: CartItem[]
+  deliveryFee?: number
+  minPrice?: number
+}
+
+interface Emits {
+  (e: 'checkout'): void
+  (e: 'quantity-change', item: CartItem, quantity: number): void
+  (e: 'clear-cart'): void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  cartItems: () => [],
+  deliveryFee: 0,
+  minPrice: 0
+})
+
+const emit = defineEmits<Emits>()
+
+const showCart = ref(false)
+
+const totalQuantity = computed(() => {
+  return props.cartItems.reduce((sum, item) => sum + item.quantity, 0)
+})
+
+const totalPrice = computed(() => {
+  return props.cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0)
+})
+
+const canCheckout = computed(() => {
+  if (totalQuantity.value === 0) return false
+  if (props.minPrice && totalPrice.value < props.minPrice) return false
+  return true
+})
+
+const checkoutText = computed(() => {
+  if (totalQuantity.value === 0) {
+    return '去结算'
+  }
+  if (props.minPrice && totalPrice.value < props.minPrice) {
+    return `还差 ¥${(props.minPrice - totalPrice.value).toFixed(2)}`
+  }
+  return '去结算'
+})
+
+const toggleCart = () => {
+  if (totalQuantity.value > 0) {
+    showCart.value = !showCart.value
+  }
+}
+
+const handleQuantityChange = (item: CartItem, quantity: number) => {
+  emit('quantity-change', item, quantity)
+}
+
+const handleClearCart = async () => {
+  try {
+    await showConfirmDialog({
+      title: '提示',
+      message: '确定要清空购物车吗?'
+    })
+    emit('clear-cart')
+    showCart.value = false
+  } catch {
+    // 用户取消
+  }
+}
+
+const handleCheckout = () => {
+  if (canCheckout.value) {
+    emit('checkout')
+  }
+}
+</script>
+
+<style scoped>
+.delivery-footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 100;
+}
+
+.cart-drawer {
+  background: #fff;
+  border-radius: 16px 16px 0 0;
+  max-height: 50vh;
+  overflow: hidden;
+  display: flex;
+  flex-direction: column;
+}
+
+.cart-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px;
+  border-bottom: 1px solid #f0f0f0;
+  font-weight: 500;
+}
+
+.cart-list {
+  flex: 1;
+  overflow-y: auto;
+  padding: 0 16px;
+}
+
+.cart-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 0;
+  border-bottom: 1px solid #f5f5f5;
+}
+
+.cart-item:last-child {
+  border-bottom: none;
+}
+
+.item-info {
+  flex: 1;
+  display: flex;
+  justify-content: space-between;
+  margin-right: 16px;
+}
+
+.item-name {
+  font-size: 14px;
+  color: #333;
+}
+
+.item-price {
+  font-size: 14px;
+  color: #ff6b00;
+  font-weight: 500;
+}
+
+.footer-bar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 16px;
+  background: #fff;
+  box-shadow: 0 -2px 12px rgba(0,0,0,0.08);
+}
+
+.cart-icon-wrapper {
+  position: relative;
+  cursor: pointer;
+}
+
+.cart-icon {
+  width: 48px;
+  height: 48px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #fff;
+  position: relative;
+  box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
+}
+
+.price-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+}
+
+.total-price {
+  display: flex;
+  align-items: baseline;
+  gap: 4px;
+}
+
+.label {
+  font-size: 12px;
+  color: #666;
+}
+
+.amount {
+  font-size: 20px;
+  font-weight: bold;
+  color: #ff6b00;
+}
+
+.delivery-fee,
+.min-price-tip {
+  font-size: 12px;
+  color: #999;
+}
+
+.min-price-tip {
+  color: #ff976a;
+}
+
+.checkout-btn {
+  min-width: 100px;
+  height: 44px;
+  font-size: 15px;
+  font-weight: 500;
+}
+</style>

+ 112 - 0
src/components/LanguageSwitcher.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class="lang-switcher" :class="{ compact }">
+    <div class="current-lang" @click="showPicker = true">
+      <span class="lang-icon">{{ currentLangIcon }}</span>
+      <span v-if="!compact" class="lang-text">{{ currentLangText }}</span>
+      <van-icon v-if="!compact" name="arrow-down" size="12" />
+    </div>
+
+    <van-popup v-model:show="showPicker" position="bottom" round>
+      <van-picker
+        :columns="languageColumns"
+        @confirm="handleConfirm"
+        @cancel="showPicker = false"
+      >
+        <template #title>
+          {{ $t('common.selectLanguage') }}
+        </template>
+      </van-picker>
+    </van-popup>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { useI18n } from 'vue-i18n'
+import { useAppStore } from '@/store/modules/app'
+import { showToast } from 'vant'
+
+interface Props {
+  compact?: boolean
+}
+
+defineProps<Props>()
+
+const { t, locale } = useI18n()
+const appStore = useAppStore()
+const showPicker = ref(false)
+
+const languageOptions = [
+  { text: '日本語', value: 'ja', icon: '🇯🇵' },
+  { text: '简体中文', value: 'zh-Hans', icon: '🇨🇳' },
+  { text: '繁體中文', value: 'zh-Hant', icon: '🇭🇰' },
+  { text: 'English', value: 'en', icon: '🇺🇸' }
+]
+
+const languageColumns = languageOptions.map(lang => ({
+  text: `${lang.icon} ${lang.text}`,
+  value: lang.value
+}))
+
+const currentLang = computed(() => {
+  return languageOptions.find(lang => lang.value === locale.value) || languageOptions[0]
+})
+
+const currentLangIcon = computed(() => currentLang.value?.icon || '🇯🇵')
+const currentLangText = computed(() => currentLang.value?.text || '日本語')
+
+const handleConfirm = ({ selectedValues }: any) => {
+  const newLang = selectedValues[0] as string
+  if (newLang !== locale.value) {
+    appStore.setLang(newLang)
+    showToast(t('common.languageChanged'))
+    // 刷新页面以应用新语言
+    setTimeout(() => {
+      location.reload()
+    }, 500)
+  }
+  showPicker.value = false
+}
+</script>
+
+<style scoped>
+.lang-switcher {
+  display: inline-flex;
+  align-items: center;
+}
+
+.current-lang {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 6px 12px;
+  background: rgba(255, 255, 255, 0.9);
+  border-radius: 16px;
+  cursor: pointer;
+  transition: all 0.3s;
+  font-size: 14px;
+}
+
+.lang-switcher.compact .current-lang {
+  padding: 6px 10px;
+  background: transparent;
+}
+
+.current-lang:active {
+  transform: scale(0.95);
+}
+
+.lang-icon {
+  font-size: 18px;
+  line-height: 1;
+}
+
+.lang-text {
+  color: #333;
+  font-weight: 500;
+}
+
+.lang-switcher.compact .lang-text {
+  display: none;
+}
+</style>

+ 112 - 0
src/components/SearchBar.vue

@@ -0,0 +1,112 @@
+<template>
+  <div class="search-bar" :class="{ focused: isFocused }">
+    <van-icon name="search" class="search-icon" />
+    <input
+      v-model="searchText"
+      type="text"
+      :placeholder="placeholder"
+      class="search-input"
+      @focus="handleFocus"
+      @blur="handleBlur"
+      @input="handleInput"
+      @keyup.enter="handleSearch"
+    />
+    <van-icon
+      v-if="searchText"
+      name="clear"
+      class="clear-icon"
+      @click="handleClear"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+interface Props {
+  placeholder?: string
+  modelValue?: string
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: string): void
+  (e: 'search', value: string): void
+  (e: 'focus'): void
+  (e: 'blur'): void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  placeholder: '搜索店铺、菜品',
+  modelValue: ''
+})
+
+const emit = defineEmits<Emits>()
+
+const searchText = ref(props.modelValue)
+const isFocused = ref(false)
+
+const handleFocus = () => {
+  isFocused.value = true
+  emit('focus')
+}
+
+const handleBlur = () => {
+  isFocused.value = false
+  emit('blur')
+}
+
+const handleInput = () => {
+  emit('update:modelValue', searchText.value)
+}
+
+const handleSearch = () => {
+  emit('search', searchText.value)
+}
+
+const handleClear = () => {
+  searchText.value = ''
+  emit('update:modelValue', '')
+}
+</script>
+
+<style scoped>
+.search-bar {
+  display: flex;
+  align-items: center;
+  background: #f5f5f5;
+  border-radius: 20px;
+  padding: 8px 16px;
+  transition: all 0.3s;
+}
+
+.search-bar.focused {
+  background: #fff;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
+}
+
+.search-icon {
+  color: #999;
+  margin-right: 8px;
+  font-size: 18px;
+}
+
+.search-input {
+  flex: 1;
+  border: none;
+  outline: none;
+  background: transparent;
+  font-size: 14px;
+  color: #333;
+}
+
+.search-input::placeholder {
+  color: #999;
+}
+
+.clear-icon {
+  color: #999;
+  font-size: 16px;
+  cursor: pointer;
+  margin-left: 8px;
+}
+</style>

+ 159 - 0
src/components/ShopCard.vue

@@ -0,0 +1,159 @@
+<template>
+  <div class="shop-card" @click="handleClick">
+    <div class="shop-image">
+      <van-image
+        v-if="shop.logo"
+        :src="shop.logo"
+        fit="cover"
+        width="80"
+        height="80"
+        radius="8"
+      />
+      <div v-else class="shop-logo-placeholder">
+        <van-icon name="shop-o" size="32" />
+      </div>
+    </div>
+    
+    <div class="shop-info">
+      <div class="shop-name">{{ shop.name }}</div>
+      <div class="shop-tags">
+        <van-tag v-for="tag in shop.tags" :key="tag" plain size="medium">
+          {{ tag }}
+        </van-tag>
+      </div>
+      <div class="shop-meta">
+        <span class="rating">
+          <van-icon name="star" color="#ff976a" />
+          {{ shop.rating || '5.0' }}
+        </span>
+        <span class="divider">|</span>
+        <span>月售 {{ formatSales(shop.monthlySales || 0) }}</span>
+      </div>
+      <div class="shop-delivery">
+        <span class="delivery-time">{{ shop.deliveryTime || '30分钟' }}</span>
+        <span class="divider">|</span>
+        <span class="delivery-fee">配送 ¥{{ shop.deliveryFee || 0 }}</span>
+        <span v-if="shop.minPrice" class="min-price">起送 ¥{{ shop.minPrice }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+export interface Shop {
+  id: string
+  name: string
+  logo?: string
+  tags?: string[]
+  rating?: number
+  monthlySales?: number
+  deliveryTime?: string
+  deliveryFee?: number
+  minPrice?: number
+}
+
+interface Props {
+  shop: Shop
+}
+
+interface Emits {
+  (e: 'click', shop: Shop): void
+}
+
+const props = defineProps<Props>()
+const emit = defineEmits<Emits>()
+
+const formatSales = (sales: number) => {
+  if (sales >= 1000) {
+    return (sales / 1000).toFixed(1) + 'k'
+  }
+  return sales.toString()
+}
+
+const handleClick = () => {
+  emit('click', props.shop)
+}
+</script>
+
+<style scoped>
+.shop-card {
+  display: flex;
+  gap: 12px;
+  padding: 12px;
+  background: #fff;
+  border-radius: 12px;
+  cursor: pointer;
+  transition: all 0.3s;
+}
+
+.shop-card:hover {
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+}
+
+.shop-image {
+  flex-shrink: 0;
+}
+
+.shop-logo-placeholder {
+  width: 80px;
+  height: 80px;
+  background: #f5f5f5;
+  border-radius: 8px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #ccc;
+}
+
+.shop-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+}
+
+.shop-name {
+  font-size: 16px;
+  font-weight: 600;
+  color: #333;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.shop-tags {
+  display: flex;
+  gap: 4px;
+  flex-wrap: wrap;
+}
+
+.shop-meta,
+.shop-delivery {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  color: #666;
+}
+
+.rating {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+  color: #ff976a;
+  font-weight: 500;
+}
+
+.divider {
+  color: #ddd;
+}
+
+.delivery-time {
+  color: #07c160;
+}
+
+.delivery-fee,
+.min-price {
+  color: #999;
+}
+</style>

+ 260 - 0
src/components/ShopHeader.vue

@@ -0,0 +1,260 @@
+<template>
+  <div class="shop-header">
+    <!-- 店铺背景图 -->
+    <div class="shop-banner" :style="{ backgroundImage: shop.banner ? `url(${shop.banner})` : 'none' }">
+      <div class="banner-overlay"></div>
+    </div>
+
+    <!-- 店铺信息 -->
+    <div class="shop-info-card">
+      <div class="shop-basic">
+        <div class="shop-logo">
+          <van-image
+            v-if="shop.logo"
+            :src="shop.logo"
+            width="60"
+            height="60"
+            round
+            fit="cover"
+          />
+          <div v-else class="logo-placeholder">
+            <van-icon name="shop-o" size="28" />
+          </div>
+        </div>
+        <div class="shop-detail">
+          <h2 class="shop-name">{{ shop.name }}</h2>
+          <div class="shop-meta">
+            <span class="rating">
+              <van-icon name="star" color="#ff976a" />
+              {{ shop.rating || '5.0' }}
+            </span>
+            <span class="divider">|</span>
+            <span>月售 {{ formatSales(shop.monthlySales || 0) }}</span>
+            <span class="divider">|</span>
+            <span class="delivery-time">{{ shop.deliveryTime || '30分钟' }}</span>
+          </div>
+        </div>
+      </div>
+
+      <!-- 公告跑马灯 -->
+      <van-notice-bar
+        v-if="shop.notice"
+        class="shop-notice"
+        :text="shop.notice"
+        background="#fff7f0"
+        color="#ed6a0c"
+        left-icon="volume-o"
+        :scrollable="true"
+      />
+
+      <!-- 服务类型切换 -->
+      <div class="service-tabs">
+        <div
+          v-for="tab in serviceTabs"
+          :key="tab.value"
+          class="service-tab"
+          :class="{ active: currentService === tab.value }"
+          @click="handleServiceChange(tab.value)"
+        >
+          <van-icon :name="tab.icon" />
+          <span>{{ tab.label }}</span>
+        </div>
+      </div>
+
+      <!-- 优惠信息 -->
+      <div v-if="shop.promotions && shop.promotions.length" class="promotions">
+        <van-tag
+          v-for="promo in shop.promotions"
+          :key="promo.id"
+          type="danger"
+          plain
+          size="medium"
+        >
+          {{ promo.text }}
+        </van-tag>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+
+export type ServiceType = 'delivery' | 'pickup' | 'booking'
+
+export interface ShopHeaderData {
+  id: string
+  name: string
+  logo?: string
+  banner?: string
+  rating?: number
+  monthlySales?: number
+  deliveryTime?: string
+  notice?: string
+  promotions?: Array<{ id: string; text: string }>
+}
+
+interface Props {
+  shop: ShopHeaderData
+  modelValue?: ServiceType
+}
+
+interface Emits {
+  (e: 'update:modelValue', value: ServiceType): void
+  (e: 'service-change', value: ServiceType): void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  modelValue: 'delivery'
+})
+
+const emit = defineEmits<Emits>()
+
+const currentService = ref<ServiceType>(props.modelValue)
+
+const serviceTabs = [
+  { label: '外卖配送', value: 'delivery' as ServiceType, icon: 'logistics' },
+  { label: '到店自提', value: 'pickup' as ServiceType, icon: 'bag-o' },
+  { label: '预约订座', value: 'booking' as ServiceType, icon: 'clock-o' }
+]
+
+const formatSales = (sales: number) => {
+  if (sales >= 1000) {
+    return (sales / 1000).toFixed(1) + 'k'
+  }
+  return sales.toString()
+}
+
+const handleServiceChange = (service: ServiceType) => {
+  currentService.value = service
+  emit('update:modelValue', service)
+  emit('service-change', service)
+}
+</script>
+
+<style scoped>
+.shop-header {
+  background: #fff;
+  margin-bottom: 8px;
+}
+
+.shop-banner {
+  height: 150px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  background-size: cover;
+  background-position: center;
+  position: relative;
+}
+
+.banner-overlay {
+  position: absolute;
+  inset: 0;
+  background: linear-gradient(to bottom, transparent, rgba(0,0,0,0.3));
+}
+
+.shop-info-card {
+  padding: 16px;
+  margin-top: -40px;
+  position: relative;
+}
+
+.shop-basic {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 12px;
+}
+
+.shop-logo,
+.logo-placeholder {
+  width: 60px;
+  height: 60px;
+  background: #fff;
+  border-radius: 50%;
+  box-shadow: 0 2px 12px rgba(0,0,0,0.1);
+  flex-shrink: 0;
+}
+
+.logo-placeholder {
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #999;
+}
+
+.shop-detail {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  justify-content: center;
+}
+
+.shop-name {
+  margin: 0 0 6px;
+  font-size: 18px;
+  font-weight: bold;
+  color: #333;
+}
+
+.shop-meta {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  color: #666;
+}
+
+.rating {
+  display: flex;
+  align-items: center;
+  gap: 2px;
+  color: #ff976a;
+  font-weight: 500;
+}
+
+.divider {
+  color: #ddd;
+}
+
+.delivery-time {
+  color: #07c160;
+}
+
+.shop-notice {
+  margin: 12px 0;
+  border-radius: 8px;
+}
+
+.service-tabs {
+  display: flex;
+  gap: 12px;
+  margin: 12px 0;
+}
+
+.service-tab {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 6px;
+  padding: 12px;
+  background: #f5f5f5;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: all 0.3s;
+  font-size: 13px;
+  color: #666;
+}
+
+.service-tab.active {
+  background: #e8f4ff;
+  color: #1989fa;
+  font-weight: 500;
+}
+
+.promotions {
+  display: flex;
+  gap: 8px;
+  flex-wrap: wrap;
+  margin-top: 12px;
+}
+</style>

+ 238 - 0
src/components/TableFooter.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="table-footer">
+    <!-- 底部栏 -->
+    <div class="footer-bar">
+      <!-- 已选信息 -->
+      <div class="order-info">
+        <div class="total-display">
+          <span class="label">已选</span>
+          <span class="quantity">{{ totalQuantity }}道</span>
+          <span class="divider">|</span>
+          <span class="amount">¥{{ totalPrice.toFixed(2) }}</span>
+        </div>
+        <div v-if="hasOrdered" class="ordered-info">
+          已下单: ¥{{ orderedAmount.toFixed(2) }}
+        </div>
+      </div>
+
+      <!-- 操作按钮 -->
+      <div class="action-buttons">
+        <van-button
+          v-if="!hasOrdered"
+          type="primary"
+          round
+          size="large"
+          class="order-btn"
+          :disabled="totalQuantity === 0"
+          @click="handleSubmitOrder"
+        >
+          {{ totalQuantity > 0 ? `选好了 (${totalQuantity})` : '选择菜品' }}
+        </van-button>
+        
+        <template v-else>
+          <van-button
+            v-if="totalQuantity > 0"
+            type="warning"
+            round
+            size="large"
+            class="add-dish-btn"
+            @click="handleAddDish"
+          >
+            加菜 ({{ totalQuantity }})
+          </van-button>
+          <van-button
+            type="default"
+            round
+            size="large"
+            @click="handleViewOrders"
+          >
+            查看已点
+          </van-button>
+        </template>
+      </div>
+    </div>
+
+    <!-- 确认下单弹窗 -->
+    <van-dialog
+      v-model:show="showConfirmDialog"
+      title="确认下单"
+      show-cancel-button
+      @confirm="confirmOrder"
+    >
+      <div class="order-confirm-content">
+        <div class="confirm-item">
+          <span>菜品数量</span>
+          <span>{{ totalQuantity }} 道</span>
+        </div>
+        <div class="confirm-item">
+          <span>合计金额</span>
+          <span class="highlight">¥{{ totalPrice.toFixed(2) }}</span>
+        </div>
+        <p class="confirm-tip">确认后将立即通知厨房准备</p>
+      </div>
+    </van-dialog>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { showToast } from 'vant'
+
+export interface TableCartItem {
+  id: string
+  name: string
+  price: number
+  quantity: number
+}
+
+interface Props {
+  cartItems?: TableCartItem[]
+  hasOrdered?: boolean
+  orderedAmount?: number
+}
+
+interface Emits {
+  (e: 'submit-order'): void
+  (e: 'add-dish'): void
+  (e: 'view-orders'): void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  cartItems: () => [],
+  hasOrdered: false,
+  orderedAmount: 0
+})
+
+const emit = defineEmits<Emits>()
+
+const showConfirmDialog = ref(false)
+
+const totalQuantity = computed(() => {
+  return props.cartItems.reduce((sum, item) => sum + item.quantity, 0)
+})
+
+const totalPrice = computed(() => {
+  return props.cartItems.reduce((sum, item) => sum + item.price * item.quantity, 0)
+})
+
+const handleSubmitOrder = () => {
+  if (totalQuantity.value === 0) {
+    showToast('请先选择菜品')
+    return
+  }
+  showConfirmDialog.value = true
+}
+
+const confirmOrder = () => {
+  emit('submit-order')
+  showConfirmDialog.value = false
+}
+
+const handleAddDish = () => {
+  emit('add-dish')
+}
+
+const handleViewOrders = () => {
+  emit('view-orders')
+}
+</script>
+
+<style scoped>
+.table-footer {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  z-index: 100;
+}
+
+.footer-bar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px 16px;
+  background: #fff;
+  box-shadow: 0 -2px 12px rgba(0,0,0,0.08);
+}
+
+.order-info {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.total-display {
+  display: flex;
+  align-items: baseline;
+  gap: 6px;
+}
+
+.label {
+  font-size: 12px;
+  color: #666;
+}
+
+.quantity {
+  font-size: 16px;
+  font-weight: bold;
+  color: #333;
+}
+
+.divider {
+  color: #ddd;
+}
+
+.amount {
+  font-size: 20px;
+  font-weight: bold;
+  color: #ff6b00;
+}
+
+.ordered-info {
+  font-size: 12px;
+  color: #07c160;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 8px;
+}
+
+.order-btn,
+.add-dish-btn {
+  min-width: 120px;
+  height: 44px;
+  font-size: 15px;
+  font-weight: 500;
+}
+
+.order-confirm-content {
+  padding: 20px;
+}
+
+.confirm-item {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 12px 0;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.confirm-item:last-child {
+  border-bottom: none;
+}
+
+.highlight {
+  font-size: 18px;
+  font-weight: bold;
+  color: #ff6b00;
+}
+
+.confirm-tip {
+  margin-top: 12px;
+  font-size: 12px;
+  color: #999;
+  text-align: center;
+}
+</style>

+ 186 - 0
src/components/TableHeader.vue

@@ -0,0 +1,186 @@
+<template>
+  <div class="table-header">
+    <!-- 桌位信息栏 -->
+    <div class="table-info-bar">
+      <div class="table-badge">
+        <van-icon name="location-o" />
+        <span class="table-code">{{ tableCode }} 桌</span>
+      </div>
+      
+      <div class="dining-info">
+        <div class="guest-count" @click="showGuestPicker = true">
+          <van-icon name="friends-o" size="16" />
+          <span>{{ guestCount }}位</span>
+          <van-icon name="arrow-down" size="10" />
+        </div>
+      </div>
+
+      <div class="service-btn" @click="handleCallService">
+        <van-icon name="service-o" />
+        <span>呼叫服务</span>
+      </div>
+    </div>
+
+    <!-- 简化店铺信息 -->
+    <div class="shop-simple">
+      <span class="shop-name">{{ shopName }}</span>
+      <van-tag v-if="sessionStatus" :type="sessionStatus === 'dining' ? 'success' : 'default'">
+        {{ sessionStatus === 'dining' ? '就餐中' : '空闲' }}
+      </van-tag>
+    </div>
+
+    <!-- 已下单提醒 -->
+    <div v-if="hasOrdered" class="order-status">
+      <van-icon name="success" color="#07c160" />
+      <span>已下单 {{ orderedCount }} 道菜,可继续加菜</span>
+    </div>
+
+    <!-- 就餐人数选择器 -->
+    <van-popup v-model:show="showGuestPicker" position="bottom" round>
+      <van-picker
+        :columns="guestColumns"
+        @confirm="handleGuestConfirm"
+        @cancel="showGuestPicker = false"
+      />
+    </van-popup>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { showToast } from 'vant'
+
+interface Props {
+  tableCode: string
+  shopName?: string
+  guestCount?: number
+  sessionStatus?: 'idle' | 'dining'
+  hasOrdered?: boolean
+  orderedCount?: number
+}
+
+interface Emits {
+  (e: 'update:guestCount', count: number): void
+  (e: 'call-service'): void
+}
+
+const props = withDefaults(defineProps<Props>(), {
+  shopName: '店铺名称',
+  guestCount: 1,
+  sessionStatus: 'dining',
+  hasOrdered: false,
+  orderedCount: 0
+})
+
+const emit = defineEmits<Emits>()
+
+const showGuestPicker = ref(false)
+
+const guestColumns = Array.from({ length: 20 }, (_, i) => ({
+  text: `${i + 1}位`,
+  value: i + 1
+}))
+
+const handleGuestConfirm = ({ selectedValues }: any) => {
+  const count = selectedValues[0] as number
+  emit('update:guestCount', count)
+  showGuestPicker.value = false
+  showToast(`已设置 ${count} 位就餐`)
+}
+
+const handleCallService = () => {
+  emit('call-service')
+  showToast('已呼叫服务员')
+}
+</script>
+
+<style scoped>
+.table-header {
+  background: #fff;
+  padding: 12px 16px;
+  margin-bottom: 8px;
+}
+
+.table-info-bar {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  margin-bottom: 12px;
+}
+
+.table-badge {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 8px 16px;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  border-radius: 20px;
+  font-weight: 500;
+  font-size: 15px;
+}
+
+.table-code {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.dining-info {
+  flex: 1;
+}
+
+.guest-count {
+  display: inline-flex;
+  align-items: center;
+  gap: 4px;
+  padding: 6px 12px;
+  background: #f5f5f5;
+  border-radius: 16px;
+  font-size: 14px;
+  cursor: pointer;
+}
+
+.service-btn {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  padding: 8px 12px;
+  background: #fff;
+  border: 1px solid #1989fa;
+  color: #1989fa;
+  border-radius: 20px;
+  font-size: 13px;
+  cursor: pointer;
+  transition: all 0.3s;
+}
+
+.service-btn:active {
+  background: #e8f4ff;
+}
+
+.shop-simple {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding-bottom: 12px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.shop-name {
+  flex: 1;
+  font-size: 14px;
+  color: #666;
+}
+
+.order-status {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-top: 12px;
+  padding: 10px;
+  background: #f0fdf4;
+  border-radius: 8px;
+  font-size: 13px;
+  color: #07c160;
+}
+</style>

+ 228 - 0
src/components/buffet/BuffetTimer.vue

@@ -0,0 +1,228 @@
+<template>
+  <div v-if="show && session" class="buffet-timer" :class="{ expiring: isExpiring }">
+    <div class="timer-content" @click="toggleExpanded">
+      <div class="timer-main">
+        <van-icon name="clock-o" />
+        <span class="time">{{ remainingTime }}</span>
+        <span class="label">{{ $t('buffet.remaining') }}</span>
+      </div>
+      <van-icon :name="expanded ? 'arrow-up' : 'arrow-down'" class="toggle-icon" />
+    </div>
+
+    <transition name="slide">
+      <div v-if="expanded" class="timer-details">
+        <div class="detail-item">
+          <span class="label">{{ $t('buffet.plan') }}:</span>
+          <span class="value">{{ session.planName }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="label">{{ $t('buffet.table') }}:</span>
+          <span class="value">{{ session.tableName }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="label">{{ $t('buffet.orderCount') }}:</span>
+          <span class="value">
+            {{ session.orderCount }}
+            <span v-if="session.maxOrders">/ {{ session.maxOrders }}</span>
+          </span>
+        </div>
+        <div class="detail-item">
+          <span class="label">{{ $t('buffet.startTime') }}:</span>
+          <span class="value">{{ formatTime(session.startTime) }}</span>
+        </div>
+        <div class="detail-item">
+          <span class="label">{{ $t('buffet.endTime') }}:</span>
+          <span class="value">{{ formatTime(session.endTime) }}</span>
+        </div>
+
+        <div class="timer-actions">
+          <van-button size="small" @click="handlePause" v-if="session.status === 'active'">
+            {{ $t('buffet.pause') }}
+          </van-button>
+          <van-button size="small" type="primary" @click="handleResume" v-if="session.status === 'paused'">
+            {{ $t('buffet.resume') }}
+          </van-button>
+          <van-button size="small" plain type="danger" @click="handleComplete">
+            {{ $t('buffet.endEarly') }}
+          </van-button>
+        </div>
+      </div>
+    </transition>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, watch } from 'vue'
+import { useBuffetStore } from '@/store/modules/buffet'
+import { showNotify, showConfirmDialog } from 'vant'
+import { useI18n } from 'vue-i18n'
+
+const { t } = useI18n()
+const buffetStore = useBuffetStore()
+
+const expanded = ref(false)
+
+const show = computed(() => buffetStore.showTimer)
+const session = computed(() => buffetStore.currentSession)
+const remainingTime = computed(() => buffetStore.remainingTimeFormatted)
+const isExpiring = computed(() => buffetStore.isExpiringSoon)
+
+const toggleExpanded = () => {
+  expanded.value = !expanded.value
+}
+
+const formatTime = (timestamp: number) => {
+  const date = new Date(timestamp)
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  return `${hours}:${minutes}`
+}
+
+const handlePause = () => {
+  buffetStore.pauseSession()
+  showNotify({ type: 'warning', message: t('buffet.paused') })
+}
+
+const handleResume = () => {
+  buffetStore.resumeSession()
+  showNotify({ type: 'success', message: t('buffet.resumed') })
+}
+
+const handleComplete = async () => {
+  try {
+    await showConfirmDialog({
+      title: t('buffet.confirmEnd'),
+      message: t('buffet.confirmEndMessage')
+    })
+    buffetStore.completeSession()
+    showNotify({ type: 'success', message: t('buffet.ended') })
+  } catch {
+    // 用户取消
+  }
+}
+
+// 监听剩余时间,显示提醒
+watch(() => session.value?.remainingTime, (newTime) => {
+  if (!newTime) return
+
+  const minutes = Math.floor(newTime / 60)
+
+  // 最后10分钟提醒
+  if (minutes === 10 && newTime % 60 === 0) {
+    showNotify({
+      type: 'warning',
+      message: t('buffet.reminder10min'),
+      duration: 5000
+    })
+  }
+
+  // 最后5分钟提醒
+  if (minutes === 5 && newTime % 60 === 0) {
+    showNotify({
+      type: 'danger',
+      message: t('buffet.reminder5min'),
+      duration: 5000
+    })
+  }
+
+  // 时间到
+  if (newTime === 0) {
+    showNotify({
+      type: 'danger',
+      message: t('buffet.timeUp'),
+      duration: 0
+    })
+  }
+})
+</script>
+
+<style scoped>
+.buffet-timer {
+  position: fixed;
+  top: 46px;
+  left: 0;
+  right: 0;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  z-index: 999;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+}
+
+.buffet-timer.expiring {
+  background: linear-gradient(135deg, #ee0a24 0%, #ff6034 100%);
+  animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+  0%, 100% { opacity: 1; }
+  50% { opacity: 0.85; }
+}
+
+.timer-content {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 8px 16px;
+  cursor: pointer;
+}
+
+.timer-main {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.time {
+  font-size: 20px;
+  font-weight: bold;
+  font-family: 'Courier New', monospace;
+}
+
+.label {
+  font-size: 12px;
+  opacity: 0.9;
+}
+
+.toggle-icon {
+  transition: transform 0.3s;
+}
+
+.timer-details {
+  border-top: 1px solid rgba(255, 255, 255, 0.2);
+  padding: 12px 16px;
+}
+
+.detail-item {
+  display: flex;
+  justify-content: space-between;
+  font-size: 13px;
+  margin-bottom: 8px;
+}
+
+.detail-item .label {
+  opacity: 0.8;
+}
+
+.detail-item .value {
+  font-weight: 500;
+}
+
+.timer-actions {
+  display: flex;
+  gap: 8px;
+  margin-top: 12px;
+}
+
+.slide-enter-active,
+.slide-leave-active {
+  transition: all 0.3s ease;
+  max-height: 300px;
+  overflow: hidden;
+}
+
+.slide-enter-from,
+.slide-leave-to {
+  max-height: 0;
+  opacity: 0;
+}
+</style>

+ 74 - 22
src/composables/useRole.ts

@@ -1,5 +1,5 @@
 import { computed } from 'vue'
-import { useUserStore } from '@/store/modules/user'
+import { useUserStore, type UserRole } from '@/store/modules/user'
 import { showDialog, showToast } from 'vant'
 import { useI18n } from 'vue-i18n'
 
@@ -17,19 +17,33 @@ export function useRole() {
   const availableRoles = computed(() => userStore.availableRoles)
 
   // 角色判断
-  const isUser = computed(() => userStore.isUser)
-  const isMerchant = computed(() => userStore.isMerchantRole)
-  const isWaiter = computed(() => userStore.isWaiter)
+  const isCustomer = computed(() => userStore.isCustomer)
+  const isGuest = computed(() => userStore.isGuestRole)
+  const isStaff = computed(() => userStore.isStaff)
+  const isManager = computed(() => userStore.isManager)
+  const isOwner = computed(() => userStore.isOwner)
   const isAdmin = computed(() => userStore.isAdmin)
+  const isShopRole = computed(() => userStore.isShopRole)
+
+  // 店铺信息
+  const currentShop = computed(() => userStore.currentShop)
+  const currentShopId = computed(() => userStore.currentShopId)
+
+  // 桌位会话
+  const tableSession = computed(() => userStore.tableSession)
+  const hasTableSession = computed(() => userStore.tableSession !== null)
 
   // 检查是否拥有某个角色
-  const hasRole = (role: string) => userStore.hasRole(role)
+  const hasRole = (role: UserRole) => userStore.hasRole(role)
 
   // 检查是否拥有任一角色
-  const hasAnyRole = (roles: string[]) => userStore.hasAnyRole(roles)
+  const hasAnyRole = (roles: UserRole[]) => userStore.hasAnyRole(roles)
+
+  // 检查权限
+  const can = (permission: string) => userStore.can(permission)
 
   // 切换角色
-  const switchRole = async (newRole: 'user' | 'merchant' | 'waiter' | 'admin') => {
+  const switchRole = async (newRole: UserRole) => {
     if (!hasRole(newRole)) {
       showToast(t('role.noPermission'))
       return false
@@ -57,39 +71,77 @@ export function useRole() {
   }
 
   // 获取角色显示名称
-  const getRoleLabel = (role: string) => {
-    return t(`role.${role}`)
+  const getRoleLabel = (role: UserRole) => {
+    const fallbackLabels: Record<UserRole, string> = {
+      customer: '顾客',
+      guest: '访客',
+      staff: '员工',
+      manager: '店长',
+      owner: '老板',
+      admin: '管理员'
+    }
+    try {
+      return t(`role.${role}`)
+    } catch {
+      return fallbackLabels[role] || role
+    }
   }
 
   // 获取角色图标
-  const getRoleIcon = (role: string) => {
-    const icons: Record<string, string> = {
-      user: 'user-o',
-      merchant: 'shop-o',
-      waiter: 'manager-o',
+  const getRoleIcon = (role: UserRole) => {
+    const icons: Record<UserRole, string> = {
+      customer: 'user-o',
+      guest: 'scan',
+      staff: 'manager-o',
+      manager: 'medal-o',
+      owner: 'shop-o',
       admin: 'shield-o'
     }
     return icons[role] || 'user-o'
   }
 
-  // 检查权限(简化版)
-  const can = (permission: string) => {
-    if (userStore.permissions.includes('*')) return true
-    return userStore.permissions.includes(permission)
+  // 获取角色颜色
+  const getRoleColor = (role: UserRole) => {
+    const colors: Record<UserRole, string> = {
+      customer: '#1989fa',
+      guest: '#969799',
+      staff: '#07c160',
+      manager: '#ff976a',
+      owner: '#ee0a24',
+      admin: '#7232dd'
+    }
+    return colors[role] || '#1989fa'
   }
 
   return {
+    // 角色状态
     currentRole,
     availableRoles,
-    isUser,
-    isMerchant,
-    isWaiter,
+    
+    // 角色判断
+    isCustomer,
+    isGuest,
+    isStaff,
+    isManager,
+    isOwner,
     isAdmin,
+    isShopRole,
+    
+    // 店铺
+    currentShop,
+    currentShopId,
+    
+    // 桌位
+    tableSession,
+    hasTableSession,
+    
+    // 方法
     hasRole,
     hasAnyRole,
+    can,
     switchRole,
     getRoleLabel,
     getRoleIcon,
-    can
+    getRoleColor
   }
 }

+ 141 - 583
src/locale/en.json

@@ -1,255 +1,97 @@
 {
-  "language": "Language",
-  "locale": {
-    "auto": "System",
-    "language": "Select Language",
-    "zhHans": "简体中文",
-    "en": "English",
-    "ja": "日本語",
-    "zhHant": "繁体中文",
-    "zh-Hans": "简体中文",
-    "zh-Hant": "繁体中文"
-  },
   "common": {
-    "tips": "Tips",
+    "confirm": "Confirm",
+    "cancel": "Cancel",
     "success": "Success",
     "error": "Error",
-    "cancel": "Cancel",
-    "confirm": "Confirm",
-    "featureInDevelopment": "Feature in development, please stay tuned",
+    "loading": "Loading",
+    "pleaseLogin": "Please login first",
+    "selectLanguage": "Select Language",
+    "languageChanged": "Language changed",
     "viewMore": "View More",
     "environment": "Environment",
-    "pleaseLogin": "Please login first"
+    "featureInDevelopment": "Feature in development"
   },
-  "welcome": {
-    "message": "Welcome",
-    "setting": "Settings",
-    "startOrdering": "Start Ordering",
-    "setShop": "Set Shop",
-    "initSetting": "Shop Settings",
-    "selectShopFirst": "Please select shop first",
-    "setTableNumber": "Set Table Number",
-    "enterTableNumber": "Enter table number",
-    "setShopAndTableNumber": "Please set shop and table information",
-    "save": "Save",
-    "shopId": "Shop ID",
-    "deskId": "Table ID",
-    "deskNumber": "Table Number",
-    "deskPeople": "Number of People",
-    "submit": "Submit",
-    "enterShopId": "Enter Shop ID",
-    "enterDeskId": "Enter Table ID",
-    "enterDeskNumber": "Enter Table Number",
-    "enterDeskPeople": "Enter Number of People",
-    "shopIdRequired": "Shop ID is required",
-    "deskIdRequired": "Table ID is required",
-    "deskNumberRequired": "Table Number is required",
-    "deskPeopleRequired": "Number of People is required",
-    "shopNotExist": "Shop does not exist"
+  "search": {
+    "placeholder": "Search shops, dishes"
+  },
+  "index": {
+    "home": "Home",
+    "nearbyHot": "Popular Nearby",
+    "categories": {
+      "japanese": "Japanese",
+      "chinese": "Chinese",
+      "western": "Western",
+      "fastFood": "Fast Food",
+      "dessert": "Dessert",
+      "drinks": "Drinks",
+      "favorites": "Favorites",
+      "coupons": "Coupons"
+    }
   },
-  "menu.title": "Order",
   "menu": {
+    "title": "Menu",
     "currentTable": "Current Table",
-    "checkout": "Checkout",
-    "title": "Order",
     "people": "People",
-    "peopleUnit": "People",
-    "tableNumber": "Table Number",
     "closed": "Closed",
     "distance": "Distance",
-    "deliveryDistance": "Delivery Range",
-    "noDelivery": "No Delivery",
     "dineIn": "Dine In",
     "takeOut": "Take Out",
     "selectSpec": "Select Options",
     "soldOut": "Sold Out",
-    "shortOf": "Short of",
-    "startDelivery": "Minimum for Delivery",
-    "stock": "Stock",
+    "checkout": "Checkout",
     "addToCart": "Add to Cart",
-    "clear": "Clear",
-    "locateNearestStore": "Find Nearest Store",
-    "details": "Details",
-    "sold": "Sold",
-    "productDetails": "Product Details",
-    "minOrderAmount": "Need {amount} more to order",
-    "continueShopping": "Continue Shopping"
+    "clear": "Clear"
   },
-  "locale.auto": "System",
-  "locale.language": "Select Language",
-  "index.component": "Component",
-  "index.api": "API",
-  "index.schema": "Schema",
-  "index.detail": "Detail",
-  "index.language": "Language",
-  "index.language-info": "Language Info",
-  "index.system-language": "System Language",
-  "index.application-language": "Application Language",
-  "index.language-change-confirm": "Applying this setting will restart the app",
-  "index.hello": "Hello",
-  "index.guest": "Guest",
-  "index.system-name": "FastEat Ordering System",
-  "index.respected-user": "Dear User",
-  "index.authorize-tip": "Please authorize to get better service",
-  "index.normal-member": "Regular Member",
-  "index.login-register": "Login/Register",
-  "index.balance": "Balance",
-  "index.points": "Points",
-  "index.coupon": "Coupons",
-  "index.self-pickup": "Self Pickup",
-  "index.skip-queue": "Skip Queue",
-  "index.delivery": "Delivery",
-  "index.delivery-tip": "Food Delivery",
-  "index.reservation": "Reservation",
-  "index.reservation-tip": "Reserve in advance",
-  "index.scan-order": "Scan to Order",
-  "index.scan-tip": "Scan QR code to order",
-  "index.view-stores": "View Stores",
-  "index.find-more-stores": "Find More Stores",
-  "index.explore": "Explore",
-  "index.points-mall": "Points Mall",
-  "index.points-mall-tip": "Exchange for coupons and gifts",
-  "index.browse": "Browse",
-  "index.activities": "Activities",
-  "index.systemName": "FastEat Ordering System",
-  "index.respectedUser": "Dear User",
-  "index.authorizeTip": "Please authorize to get better service",
-  "index.normalMember": "Regular Member",
-  "index.loginRegister": "Login/Register",
-  "index.selfPickup": "Self Pickup",
-  "index.skipQueue": "Skip Queue",
-  "index.deliveryTip": "Food Delivery",
-  "index.reservationTip": "Reserve in advance",
-  "index.scanOrder": "Scan to Order",
-  "index.scanTip": "Scan QR code to order",
-  "index.viewStores": "View Stores",
-  "index.findMoreStores": "Find More Stores",
-  "index.pointsMall": "Points Mall",
-  "index.pointsMallTip": "Exchange for coupons and gifts",
-  "index.todayRecommend": "Today's Recommend",
-  "index.hotSales": "Hot Sales",
-  "api.message": "Message",
-  "schema.name": "Name",
-  "schema.add": "Add",
-  "schema.add-success": "Added Successfully",
-  "index.home": "Home",
-  "order.title": "Order History",
-  "mine.title": "My Profile",
-  "content.title": "Content",
-  "news.title": "News Detail",
-  "pay.title": "Payment",
-  "remark.title": "Remarks",
-  "packages.title": "Available Coupons",
-  "login.title": "Login",
-  "logout.title": "Logout",
-  "address.title": "Delivery Address",
-  "addAddress.title": "Add Address",
-  "orders.title": "Order History",
-  "orderDetail.title": "Order Detail",
-  "coupons.title": "Coupons List",
-  "userinfo.title": "User Information",
-  "shop.title": "Shop",
-  "bill.title": "Bill",
-  "balance.title": "Member Recharge",
-  "refund.title": "Refund",
-  "scoreProductList.title": "Points Mall",
-  "scoreProductDetail.title": "Product Detail",
-  "scoreProductOrder.title": "Order List",
-  "scoreProductOrderDetail.title": "Order Detail",
-  "scoreProductConfirm.title": "Confirm",
-  "scan.title": "QR Code Order",
-  "vipDetail.title": "VIP Card Detail",
-  "vipList.title": "VIP Card List",
-  "menuDetail.title": "Detail",
-  "merchantCenter.title": "Merchant Center",
-  "merchantOrderList.title": "Order List",
-  "merchantCash.title": "Withdrawal",
-  "merchantAddCard.title": "Add Bank Account",
-  "merchantWallet.title": "My Wallet",
-  "merchantOrderDetail.title": "Order Detail",
-  "merchantMy.title": "My Page",
-  "merchantShopInfo.title": "Shop Information",
-  "merchantCashAccount.title": "Withdrawal Account",
-  "merchantWithdrawal.title": "Withdrawal History",
-  "merchantBell.title": "Table",
-  "due.title": "Reservation",
-  "dueDetail.title": "Reservation Details",
-  "pad.title": "Tablet Ordering",
-  "pad.menu": "Tablet Menu",
-  "welcome.tableNumber": "Table Number",
-  "welcome.setting": "Settings",
-  "welcome.setShop": "Set Shop",
-  "welcome.initSetting": "Shop Settings",
-  "welcome.selectShopFirst": "Please select shop first",
-  "welcome.setTableNumber": "Set Table Number",
-  "welcome.enterTableNumber": "Enter table number",
-  "welcome.setShopAndTableNumber": "Please set shop and table information",
-  "welcome.save": "Save",
   "cart": {
     "title": "Cart",
     "ordered": "Ordered",
     "items": "Items",
     "clear": "Clear",
-    "table": "Table",
     "checkout": "Checkout",
-    "prompt": "Confirm",
     "confirmClear": "Clear cart?",
-    "emptyCartMessage": "Please select items first",
-    "storeClosedMessage": "Store is closed",
-    "outOfDeliveryRangeMessage": "Address is out of delivery range",
-    "loading": "Loading",
-    "backToShopping": "Continue Shopping",
-    "withoutTax": "Price without tax",
-    "withTax": "Price with tax",
+    "emptyCart": "Please select items first",
     "total": "Total",
-    "amountToPay": "Amount to Pay",
-    "checkoutButton": "Checkout",
-    "orderedItems": "Ordered Items",
-    "tableNumber": "Table Number",
-    "userAvatar": "User Avatar",
+    "amountToPay": "Amount to Pay"
+  },
+  "order": {
+    "title": "Orders",
+    "detail": "Order Details",
+    "orderNumber": "Order Number",
     "orderTime": "Order Time",
-    "userNickname": "User Nickname",
-    "productImage": "Product Image",
-    "productTitle": "Product Title",
-    "productSpec": "Product Specification",
-    "productPrice": "Price",
-    "productQuantity": "Quantity",
-    "deliveryFee": "Delivery Fee",
-    "discountAmount": "Discount Amount",
-    "deductionAmount": "Deduction Amount",
-    "actualAmount": "Actual Amount",
     "paymentMethod": "Payment Method",
-    "balancePayment": "Balance Payment",
-    "wechatPay": "WeChat Pay",
-    "alipay": "Alipay",
-    "remarks": "Remarks",
-    "confirmOrder": "Confirm Order",
-    "pay": "Pay"
+    "actualAmount": "Actual Amount",
+    "ordered": "Ordered",
+    "preparing": "Preparing",
+    "delivering": "Delivering",
+    "completed": "Completed",
+    "cancelled": "Cancelled"
   },
   "mine": {
     "title": "My Profile",
-    "user-id": "User ID",
-    "login-benefits": "Login for Benefits",
+    "login-benefits": "Login for more benefits",
     "auth-login": "Login",
-    "not-vip": "Not VIP",
+    "user-id": "User ID",
+    "not-vip": "Non-VIP Member",
     "view-details": "View Details",
     "activate-now": "Activate Now",
     "coupon": "Coupons",
     "points": "Points",
     "balance": "Balance",
-    "history-consumption": "History",
+    "history-consumption": "Spending",
     "my-orders": "My Orders",
     "my-coupons": "My Coupons",
-    "switchIdentity": "Switch Identity",
+    "switchIdentity": "Switch Role",
+    "personalCenter": "Personal Center",
+    "my-services": "My Services",
     "order": {
       "all": "All Orders",
       "unpaid": "Unpaid",
-      "in-progress": "In Progress",
+      "in-progress": "Processing",
       "completed": "Completed",
       "refund": "Refund"
     },
     "coupons": {
-      "available": "Available",
       "received": "Received",
       "used": "Used",
       "expired": "Expired",
@@ -257,408 +99,124 @@
     },
     "services": {
       "address": "Address",
-      "customer-service": "Customer Service",
-      "feedback": "Feedback",
-      "about-us": "About Us"
-    },
-    "personal-center": "Personal Center",
-    "personalCenter": "Personal Center",
-    "my-services": "My Services",
-    "myServices": "My Services"
+      "customerService": "Customer Service",
+      "feedback": "Feedback"
+    }
+  },
+  "login": {
+    "title": "Login",
+    "welcome": "Welcome",
+    "phoneLogin": "Phone Verification",
+    "passwordLogin": "Password Login",
+    "orLoginWith": "Or login with",
+    "quickLogin": "Quick Login",
+    "areaCode": "Area Code",
+    "phone": "Phone",
+    "password": "Password",
+    "captcha": "Code",
+    "enterPhone": "Enter phone number",
+    "enterPassword": "Enter password",
+    "enterCaptcha": "Enter verification code",
+    "getCaptcha": "Get Code",
+    "loginNow": "Login Now",
+    "invalidPhone": "Invalid phone number format",
+    "checkAgreement": "Please check the agreement",
+    "autoCreateAccount": "Unregistered phone numbers will automatically create an account after verification",
+    "selectAreaCode": "Select Country Code",
+    "forgotPassword": "Forgot password?",
+    "noAccount": "Don't have an account?",
+    "registerNow": "Register now",
+    "captchaSent": "Verification code sent",
+    "sendFailed": "Failed to send",
+    "currentEnvironment": "Environment",
+    "agreementPrefix": "I have read and agree to",
+    "userAgreement": "User Agreement",
+    "and": "and",
+    "privacyPolicy": "Privacy Policy",
+    "success": "Login successful"
   },
   "merchant": {
-    "my": {
-      "title": "My Page",
+    "tabbar": {
+      "home": "Home",
+      "order": "Orders",
+      "table": "Tables",
+      "my": "My"
+    },
+    "index": {
+      "merchantCenter": "Merchant Center",
       "operating": "Operating",
       "closed": "Closed",
-      "businessHours": "Business Hours",
-      "address": "Address",
-      "phone": "Phone",
-      "shopInfo": "Shop Information",
-      "withdrawalAccount": "Withdrawal Account",
-      "withdrawalDetails": "Withdrawal History",
-      "tabbar": {
-        "home": "Home",
-        "order": "Order",
-        "table": "Table",
-        "my": "My",
-        "back": "Back"
-      }
+      "todayData": "Today's Data",
+      "todayOrders": "Today's Orders",
+      "todayRevenue": "Today's Revenue",
+      "pendingOrders": "Pending Orders",
+      "pendingMeals": "Pending Meals"
     },
     "order": {
       "title": "Order List",
-      "search": {
-        "placeholder": "Enter order number/phone number",
-        "button": "Search Order"
-      },
-      "tabs": {
-        "takein": "Takeout",
-        "takeout": "Delivery",
-        "desk": "Dine-in",
-        "due": "Reservation"
-      },
       "status": {
         "all": "All",
         "pending": "Pending",
         "preparing": "Preparing",
-        "readyToDeliver": "Ready to Deliver",
         "completed": "Completed",
-        "cancelled": "Cancelled",
-        "refunding": "Refunding",
-        "pendingConfirmation": "Pending Confirmation",
-        "confirmed": "Confirmed",
-        "unpaid": "Unpaid",
-        "waiting": "Waiting",
-        "refunded": "Refunded"
-      },
-      "dueStatus": {
-        "booking": "Booking",
-        "cancelled": "Cancelled",
-        "completed": "Completed"
-      },
-      "orderType": {
-        "takein": "Takeout Order",
-        "takeout": "Delivery Order",
-        "desk": "Dine-in Order",
-        "due": "Reservation Order"
-      },
-      "orderInfo": {
-        "orderTime": "Order Time",
-        "orderNumber": "Order Number",
-        "amount": "Order Amount",
-        "refundReason": "Refund Reason",
-        "productDetails": "Product Details",
-        "bookingInfo": "Booking Information",
-        "tableNumber": "Table Number"
+        "cancelled": "Cancelled"
       },
       "actions": {
         "accept": "Accept",
         "serve": "Serve",
-        "prepare": "Prepare",
-        "deliver": "Deliver",
-        "complete": "Complete",
-        "refund": "Refund",
-        "cancelBooking": "Cancel Reservation",
-        "print": "Print Receipt",
-        "receive": "Confirm Receipt",
-        "confirmRefund": "Confirm Refund?",
-        "confirmPrint": "Confirm Print?",
-        "confirmReceive": "Confirm Receipt?",
-        "confirm": "Confirm Accept?",
-        "serveFood": "Confirm Serve?",
-        "deliverOrder": "Confirm Deliver?",
-        "completeOrder": "Confirm Complete?",
-        "refundOrder": "Confirm Refund?"
-      },
-      "confirmations": {
-        "acceptOrder": "Are you sure you want to accept this order?",
-        "serveFood": "Are you sure the food is ready to serve?",
-        "deliverOrder": "Are you sure you want to deliver this order?",
-        "completeOrder": "Are you sure you want to complete this order?",
-        "refundOrder": "Are you sure you want to refund this order?"
-      }
-    },
-    "index": {
-      "orderCategories": "Order Categories",
-      "merchantCenter": "Merchant Center",
-      "myBalance": "My Balance ($)",
-      "balanceDetails": "Balance Details",
-      "withdrawNow": "Withdraw Now",
-      "operating": "Operating",
-      "closed": "Closed",
-      "todayData": "Today's Data",
-      "realTimeUpdate": "Real-time Update",
-      "todayOrders": "Today's Orders",
-      "todayRevenue": "Today's Revenue",
-      "todayTakeout": "Today's Takeout",
-      "todayDineIn": "Today's Dine-in",
-      "pendingOrders": "Pending Orders",
-      "pendingMeals": "Pending Meals",
-      "pendingDelivery": "Pending Delivery",
-      "completed": "Completed",
-      "cancelled": "Cancelled"
-    },
-    "addBankCard": "Add Bank Card",
-    "name": "Name",
-    "enterRealName": "Enter your real name",
-    "pleaseEnterRealName": "Please enter your real name",
-    "cardNumber": "Card Number",
-    "enterCardNumber": "Enter bank card number",
-    "pleaseEnterValidCardNumber": "Please enter a valid card number",
-    "bankName": "Bank Name",
-    "enterBankName": "Enter bank name",
-    "pleaseEnterBankName": "Please enter bank name",
-    "phoneNumber": "Phone Number",
-    "enterBankPhone": "Enter phone number registered with bank",
-    "pleaseEnterValidPhone": "Please enter a valid phone number",
-    "bell": {
-      "title": "Tables",
-      "idle": "Idle",
-      "dining": "Dining",
-      "noDiningData": "No dining data available"
-    },
-    "tabbar": {
-      "home": "Home",
-      "order": "Orders",
-      "table": "Tables",
-      "mine": "My",
-      "back": "Back"
-    },
-    "cash": {
-      "title": "Withdrawal",
-      "withdrawTo": "Withdraw to",
-      "pleaseSelect": "Please select",
-      "withdrawalAmount": "Withdrawal Amount",
-      "enterAmount": "Enter amount",
-      "withdrawAll": "Withdraw All",
-      "availableAmount": "Available Amount",
-      "confirmWithdrawal": "Confirm Withdrawal",
-      "withdrawalTip": "Note: After submitting a withdrawal request, it will take approximately 10 business days to process.",
-      "selectWithdrawalAccount": "Please select withdrawal account",
-      "add": "Add",
-      "noWithdrawableAmount": "No withdrawable amount"
-    },
-    "cashAccount": {
-      "title": "Withdrawal Account",
-      "bankCard": "Bank Card",
-      "unbind": "Unbind",
-      "addAccount": "Add Account",
-      "confirmUnbind": "Are you sure you want to unbind?",
-      "unbindTips": "Tips"
-    },
-    "orderDetail": {
-      "title": "Order Details",
-      "userDeleted": "User has been deleted",
-      "tableNumber": "Table Number",
-      "pickupNumber": "Pickup Number",
-      "deliveryMethod": "Delivery Method",
-      "selfPickup": "Self Pickup",
-      "takeout": "Takeout",
-      "dineIn": "Dine-in",
-      "reservation": "Reservation",
-      "orderTime": "Order Time",
-      "orderNumber": "Order Number",
-      "productDetails": "Product Details",
-      "additionalOrder": "Additional Order #{count}",
-      "firstOrder": "First Order",
-      "reservedTable": "Reserved Table",
-      "remarks": "Remarks",
-      "none": "None",
-      "coupon": "Coupon",
-      "total": "Total",
-      "discounted": "Discounted",
-      "reservationTime": "Reservation Time",
-      "actions": {
-        "accept": "Accept",
-        "serve": "Serve",
-        "deliver": "Deliver",
-        "complete": "Complete",
-        "cancelReservation": "Cancel Reservation",
-        "confirmReservation": "Confirm Reservation"
-      },
-      "confirmations": {
-        "acceptOrder": "Are you sure you want to accept this order?",
-        "serveFood": "Are you sure the food is ready to serve?",
-        "deliverOrder": "Are you sure you want to deliver this order?",
-        "completeOrder": "Are you sure you want to complete this order?",
-        "cancelReservation": "Are you sure you want to cancel this reservation?",
-        "confirmReservation": "Are you sure you want to confirm this reservation?"
-      },
-      "reservationPerson": "Reservation Person",
-      "arrivalTime": "Arrival Time",
-      "arrivalTimeTip": "Default is 10 minutes before reservation time",
-      "arrivalTimeError": "Arrival time cannot be later than reservation time",
-      "arrivalTimeEarlyError": "Arrival time cannot be more than 1 hour before reservation time",
-      "reservedTableNumber": "Reserved Table Number",
-      "reservationPhone": "Reservation Phone",
-      "paymentMethod": "Payment Method"
-    },
-    "shopInfo": {
-      "title": "Shop Information",
-      "shopName": "Shop Name",
-      "shopPhone": "Shop Phone",
-      "mapAddress": "Map Address",
-      "detailAddress": "Detailed Address",
-      "businessStartTime": "Business Start Time",
-      "businessEndTime": "Business End Time",
-      "save": "Save",
-      "placeholders": {
-        "input": "Please input",
-        "select": "Please select",
-        "selectAddress": "Please select address"
-      }
-    },
-    "wallet": {
-      "title": "My Wallet",
-      "withdrawableBalance": "Withdrawable Balance",
-      "currency": "$",
-      "withdraw": "Withdraw",
-      "transactionDetails": "Transaction Details",
-      "income": "Income",
-      "expense": "Expense"
-    },
-    "withdrawal": {
-      "title": "Withdrawal History",
-      "bankCardWithdrawal": "Bank Card Withdrawal",
-      "withdrawalTime": "Withdrawal Time",
-      "currency": "$",
-      "status": {
-        "pending": "Pending Review",
-        "processing": "Processing",
-        "rejected": "Rejected",
-        "completed": "Completed"
+        "complete": "Complete"
       }
     }
   },
-  "order": {
-    "detail": "Order Details",
-    "currentTable": "Current Table",
-    "people": "People",
-    "paymentMethod": "Payment Method",
-    "orderAmount": "Order Amount",
-    "tax": "Tax",
-    "deliveryFee": "Delivery Fee",
-    "discountAmount": "Discount Amount",
-    "deductionAmount": "Deduction Amount",
-    "actualAmount": "Actual Amount",
-    "orderTime": "Order Time",
-    "orderNumber": "Order Number",
-    "serviceType": "Service Type",
-    "selfPickup": "Self Pickup",
-    "delivery": "Delivery",
-    "pickupTime": "Pickup Time",
-    "immediatePickup": "Immediate Pickup",
-    "completionTime": "Completion Time",
-    "remarks": "Remarks",
-    "none": "None",
-    "confirmReceived": "Confirm Received",
-    "applyRefund": "Refund",
-    "ordered": "Ordered",
-    "preparing": "Preparing",
-    "delivering": "Delivering",
-    "pleaseEat": "Please Eat",
-    "delivered": "Delivered",
-    "pleasePickup": "Please Pickup",
-    "ordersAhead": "Orders Ahead",
-    "waitingToPrepare": "Waiting to Prepare",
-    "balancePayment": "Balance Payment",
-    "balance": "Balance",
-    "insufficientBalance": "Insufficient Balance",
-    "wechatPay": "WeChat Pay",
-    "payNow": "Pay Now",
-    "booking": "Booking",
-    "cancelled": "Cancelled",
-    "completed": "Completed",
-    "addOrder": "Add Order",
-    "confirmOfflinePayment": "Confirm Offline Payment",
-    "pay": "Pay",
-    "toFinish": "Finish",
-    "cancelBooking": "Cancel Booking",
-    "bookingToDineIn": "Booking to Dine In",
-    "finishOrder": "Finish Order",
-    "orderFinished": "Order Completed",
-    "addFood": "Add Food",
-    "confirmSettlement": "Confirm Settlement",
-    "continueOrdering": "Continue Ordering",
-    "settlementConfirmation": "Do you want to settle the bill? If yes, please proceed to the front desk. If not, you can continue ordering.",
-    "thankYouMessage": "Thank you for dining with us. We look forward to your next visit.",
-    "autoRedirectTip": "Would you like to continue ordering? Will automatically redirect to the menu page in {seconds} seconds",
-    "stayOnPage": "Stay on this page"
+  "owner": {
+    "dashboard": "Company Overview",
+    "shops": "Shop Management",
+    "todayData": "Today's Data",
+    "totalRevenue": "Total Revenue",
+    "totalOrders": "Total Orders",
+    "operating": "Operating",
+    "closed": "Closed",
+    "enterShop": "Enter Shop"
   },
-  "login": {
-    "title": "Login",
-    "welcome": "Welcome",
-    "padMode": "Pad Mode",
-    "usernamePlaceholder": "Enter username",
-    "usernameTip": "Please enter your account",
-    "passwordPlaceholder": "Enter password",
-    "passwordTip": "Password must be at least 6 characters",
-    "submit": "Login",
-    "usernameRequired": "Username is required",
-    "passwordRequired": "Password is required",
-    "passwordLength": "Password must be at least 6 characters",
-    "agreementRequired": "Please agree to the terms",
-    "success": "Login successful",
-    "failed": "Login failed",
-    "agreementPrefix": "I have read and agree to",
-    "agreementAnd": "and",
-    "userAgreement": "User Agreement",
-    "userAgreementText": "User Agreement",
-    "privacyPolicy": "Privacy Policy",
-    "privacyPolicyText": "Privacy Policy",
-    "enterPhone": "Enter phone number",
-    "autoCreateAccount": "Unregistered phone numbers will automatically create an account after verification",
-    "enterCaptcha": "Enter verification code",
-    "getCaptcha": "Get Code",
-    "loginNow": "Login Now",
-    "quickLogin": "Quick Login with Phone",
-    "and": "and",
-    "checkAgreement": "Please check the agreement",
-    "invalidPhone": "Invalid phone number format",
-    "selectAreaCode": "Select Country Code",
-    "currentEnvironment": "Current Environment",
-    "phoneLogin": "Phone Verification",
-    "passwordLogin": "Password Login",
-    "emailLogin": "Email Login",
-    "orLoginWith": "Or login with",
-    "areaCode": "Area Code",
-    "phone": "Phone",
-    "phoneNumber": "Phone Number",
-    "username": "Username",
-    "email": "Email",
-    "password": "Password",
-    "captcha": "Code",
-    "verificationCode": "Verification Code",
-    "enterUsername": "Enter username",
-    "enterEmail": "Enter email address",
-    "enterPassword": "Enter password",
-    "invalidEmail": "Invalid email format",
-    "forgotPassword": "Forgot password?",
-    "noAccount": "Don't have an account?",
-    "registerNow": "Register now",
-    "captchaSent": "Verification code sent",
-    "sendFailed": "Failed to send"
+  "admin": {
+    "dashboard": "Admin Dashboard",
+    "users": "User Management",
+    "merchants": "Merchant Management",
+    "searchUser": "Search username/phone",
+    "all": "All",
+    "normalUser": "Normal User",
+    "merchant": "Merchant"
+  },
+  "pos": {
+    "welcome": "POS System",
+    "tables": "Table Management",
+    "orders": "Order Management",
+    "available": "Available",
+    "occupied": "Occupied",
+    "openTable": "Open",
+    "checkout": "Checkout",
+    "acceptOrder": "Accept",
+    "completeOrder": "Complete"
+  },
+  "buffet": {
+    "title": "All-You-Can-Eat",
+    "selectPlan": "Select Buffet Plan",
+    "remaining": "Time Left",
+    "pause": "Pause",
+    "resume": "Resume",
+    "endEarly": "End Early",
+    "timeUp": "Time's up! Thank you",
+    "cannotOrder": "Cannot order now",
+    "confirmOrder": "Confirm Order",
+    "orderSuccess": "Order success!"
   },
   "role": {
-    "user": "Customer",
-    "merchant": "Merchant",
-    "waiter": "Waiter",
+    "customer": "Customer",
+    "staff": "Staff",
+    "manager": "Manager",
+    "owner": "Owner",
     "admin": "Administrator",
     "switchTitle": "Switch Role",
-    "switchConfirm": "Are you sure you want to switch to {role} role?",
-    "switchSuccess": "Switched to {role}",
-    "noPermission": "You do not have permission for this role",
     "currentRole": "Current Role"
-  },
-  "meLogin": {
-    "title": "Merchant Login"
-  },
-  "logout": {
-    "title": "Logout"
-  },
-  "due": {
-    "title": "Reservation"
-  },
-  "index": {
-    "respected-user": "Dear User",
-    "authorize-tip": "Please authorize to get better service"
-  },
-  "dueDetail": {
-    "title": "Reservation Details",
-    "reservation": "Reservation Information",
-    "arrivalTime": "Arrival Time",
-    "arrivalTimeTip": "Default is 10 minutes before reservation time",
-    "arrivalTimeError": "Arrival time cannot be later than reservation time",
-    "arrivalTimeEarlyError": "Arrival time cannot be more than 1 hour before reservation time",
-    "name": "Name",
-    "enterRealName": "Please enter your real name",
-    "phoneNumber": "Phone Number",
-    "enterPhone": "Please enter phone number",
-    "invalidPhoneFormat": "Please enter a valid 10-11 digit phone number",
-    "captcha": "Verification Code",
-    "enterCaptcha": "Please enter verification code",
-    "getCaptcha": "Get Code",
-    "submit": "Submit",
-    "confirmReservation": "Please confirm reservation time"
   }
 }

+ 142 - 625
src/locale/ja.json

@@ -1,705 +1,222 @@
 {
-  "language": "言語",
-  "locale": {
-    "auto": "システム",
-    "language": "言語を選択",
-    "zhHans": "简体中文",
-    "en": "English",
-    "ja": "日本語",
-    "zhHant": "繁体中文",
-    "zh-Hans": "简体中文",
-    "zh-Hant": "繁体中文"
-  },
   "common": {
-    "tips": "ヒント",
+    "confirm": "確認",
+    "cancel": "キャンセル",
     "success": "操作成功",
     "error": "操作失敗",
-    "cancel": "キャンセル",
-    "confirm": "確認",
-    "featureInDevelopment": "機能開発中、お楽しみに",
+    "loading": "読み込み中",
+    "pleaseLogin": "ログインしてください",
+    "selectLanguage": "言語を選択",
+    "languageChanged": "言語を変更しました",
     "viewMore": "もっと見る",
     "environment": "環境",
-    "pleaseLogin": "ログインしてください"
+    "featureInDevelopment": "開発中"
+  },
+  "search": {
+    "placeholder": "店舗・料理を検索"
   },
-  "welcome": {
-    "message": "ようこそ",
-    "setting": "設定",
-    "startOrdering": "注文開始",
-    "setShop": "店舗設定",
-    "initSetting": "店舗情報設定",
-    "selectShopFirst": "先に店舗を選択してください",
-    "setTableNumber": "テーブル番号設定",
-    "enterTableNumber": "テーブル番号を入力",
-    "setShopAndTableNumber": "店舗とテーブル情報を設定してください",
-    "save": "保存",
-    "shopId": "店舗ID",
-    "deskId": "テーブルID",
-    "deskNumber": "テーブル番号",
-    "deskPeople": "人数",
-    "submit": "送信",
-    "enterShopId": "店舗IDを入力",
-    "enterDeskId": "テーブルIDを入力",
-    "enterDeskNumber": "テーブル番号を入力",
-    "enterDeskPeople": "人数を入力",
-    "shopIdRequired": "店舗IDが必要です",
-    "deskIdRequired": "テーブルIDが必要です",
-    "deskNumberRequired": "テーブル番号が必要です",
-    "deskPeopleRequired": "人数が必要です",
-    "shopNotExist": "店舗が存在しません"
+  "index": {
+    "home": "ホーム",
+    "nearbyHot": "近くの人気店",
+    "categories": {
+      "japanese": "和食",
+      "chinese": "中華",
+      "western": "洋食",
+      "fastFood": "ファストフード",
+      "dessert": "デザート",
+      "drinks": "ドリンク",
+      "favorites": "お気に入り",
+      "coupons": "クーポン"
+    }
   },
-  "menu.title": "注文",
   "menu": {
-    "currentTable": "現在のテーブル",
-    "checkout": "会計へ",
     "title": "注文",
+    "currentTable": "現在のテーブル",
     "people": "人数",
-    "peopleUnit": "人",
-    "tableNumber": "テーブル番号",
     "closed": "営業時間外",
-    "distance": "現在地からの距離",
-    "deliveryDistance": "配達距離",
-    "noDelivery": "配達範囲外",
-    "dineIn": "店舗受取",
-    "takeOut": "デリバリー",
-    "selectSpec": "仕様を選択",
+    "distance": "現在地から",
+    "dineIn": "店内",
+    "takeOut": "持ち帰り",
+    "selectSpec": "オプション選択",
     "soldOut": "売り切れ",
-    "shortOf": "あと",
-    "startDelivery": "円で配達可能",
-    "stock": "在庫",
+    "checkout": "会計へ",
     "addToCart": "カートに追加",
-    "clear": "クリア",
-    "locateNearestStore": "最寄りの店舗を探す",
-    "details": "詳細",
-    "sold": "販売済",
-    "productDetails": "商品詳細",
-    "minOrderAmount": "あと{amount}円で注文可能",
-    "continueShopping": "買い物を続ける",
-    "historyOrder": "注文履歴",
-    "finishOrdering": "お会計",
-    "viewOrderFailed": "注文履歴を表示できません",
-    "finishOrderFailed": "操作を完了できません"
-  },
-  "locale.auto": "システム",
-  "locale.language": "言語を選択してください",
-  "index.component": "コンポーネント",
-  "index.api": "API",
-  "index.schema": "スキーマ",
-  "index.detail": "詳細",
-  "index.language": "言語",
-  "index.language-info": "言語情報",
-  "index.system-language": "システム言語",
-  "index.application-language": "アプリケーション言語",
-  "index.language-change-confirm": "この設定を適用するとアプリが再起動します",
-  "index.hello": "こんにちは",
-  "index.guest": "ゲスト",
-  "index.system-name": "FastEat注文·デリバリーシステム",
-  "index.respected-user": "尊敬するユーザー",
-  "index.authorize-tip": "より良いサービスを提供するため、ログインを許可してください",
-  "index.normal-member": "一般会員",
-  "index.login-register": "ログイン/登録",
-  "index.balance": "残高",
-  "index.points": "ポイント",
-  "index.coupon": "クーポン",
-  "index.self-pickup": "店舗受取",
-  "index.skip-queue": "注文して行列をスキップ",
-  "index.delivery": "デリバリー",
-  "index.delivery-tip": "美味しい料理をご自宅へ",
-  "index.reservation": "予約注文",
-  "index.reservation-tip": "事前予約して、到着後すぐに食事",
-  "index.scan-order": "QRコード注文",
-  "index.scan-tip": "カメラでスキャンして美味しい料理を楽しむ",
-  "index.view-stores": "店舗を見る",
-  "index.find-more-stores": "もっと多くの店舗を発見",
-  "index.explore": "探す",
-  "index.points-mall": "ポイントモール",
-  "index.points-mall-tip": "ポイントモールに入って、クーポンとグッズに交換",
-  "index.browse": "閲覧",
-  "index.activities": "最新活動",
-  "index.systemName": "FastEat注文·デリバリーシステム",
-  "index.respectedUser": "尊敬するユーザー",
-  "index.authorizeTip": "より良いサービスを提供するため、ログインを許可してください",
-  "index.normalMember": "一般会員",
-  "index.loginRegister": "ログイン/登録",
-  "index.selfPickup": "店舗受取",
-  "index.skipQueue": "注文して行列をスキップ",
-  "index.deliveryTip": "美味しい料理をご自宅へ",
-  "index.reservationTip": "事前予約して、到着後すぐに食事",
-  "index.scanOrder": "QRコード注文",
-  "index.scanTip": "カメラでスキャンして美味しい料理を楽しむ",
-  "index.viewStores": "店舗を見る",
-  "index.findMoreStores": "もっと多くの店舗を発見",
-  "index.pointsMall": "ポイントモール",
-  "index.pointsMallTip": "ポイントモールに入って、クーポンとグッズに交換",
-  "index.todayRecommend": "本日のおすすめ",
-  "index.hotSales": "人気商品",
-  "api.message": "メッセージ",
-  "schema.name": "名前",
-  "schema.add": "追加",
-  "schema.add-success": "追加成功",
-  "index.home": "ホーム",
-  "order.title": "注文履歴",
-  "mine.title": "マイページ",
-  "content.title": "コンテンツ",
-  "news.title": "ニュース詳細",
-  "pay": {
-    "title": "支払い",
-    "scanTitle": "注文",
-    "delivery": "デリバリー",
-    "selfPickup": "店頭受取",
-    "confirmAddress": "お届け先住所を選択",
-    "pickupTime": "受け取り時間",
-    "immediatePickup": "すぐに受け取り",
-    "contactPhone": "電話番号",
-    "enterPhone": "電話番号を入力してください",
-    "autoFill": "自動入力",
-    "estimatedDeliveryTime": "お届け予定時間",
-    "coupon": "クーポン",
-    "noAvailableCoupons": "利用可能なクーポンなし",
-    "full": "満",
-    "reduce": "引",
-    "availableCoupons": "利用可能なクーポン",
-    "sheets": "枚",
-    "memberDiscount": "会員割引",
-    "noDiscount": "割引なし",
-    "enjoy": "享受",
-    "discount": "割引",
-    "total": "合計",
-    "deliveryFee": "配送料",
-    "couponDiscount": "クーポン割引",
-    "actualAmount": "実支払額",
-    "paymentMethod": "支払方法",
-    "balancePayment": "残高支払い",
-    "balance": "残高",
-    "insufficientBalance": "残高不足",
-    "wechatPay": "WeChat支払い",
-    "alipay": "Alipay",
-    "storePayment": "店舗支払い",
-    "remarks": "備考",
-    "clickToAddRemarks": "備考を入力する",
-    "confirmOrder": "注文を確定",
-    "pay": "支払う",
-    "confirmAddressTitle": "お届け先住所の確認",
-    "changeAddress": "住所を変更",
-    "confirmAndPay": "確認して支払う",
-    "submitOrder": "注文を確定",
-    "loading": "読み込み中",
-    "selectDeliveryAddress": "お届け先住所を選択してください",
-    "minOrderAmount": "最低注文金額は¥{amount}です",
-    "tips": "お知らせ",
-    "subscribeMessageTip": "より良いサービスを提供するため、注文完了時にメッセージを送信させていただきます",
-    "agree": "同意",
-    "disagree": "拒否",
-    "defaultSpec": "デフォルト",
-    "alipayBrowserTip": "通常のブラウザでAlipay支払いを行ってください"
+    "clear": "クリア"
   },
-  "remark.title": "備考",
-  "packages.title": "利用可能なクーポン",
-  "login.title": "ログイン",
-  "logout.title": "ログアウト",
-  "address.title": "お届け先住所",
-  "addAddress.title": "住所を追加",
-  "orders.title": "注文履歴",
-  "orderDetail.title": "注文詳細",
-  "coupons.title": "クーポン一覧",
-  "userinfo.title": "ユーザー情報",
-  "shop.title": "店舗",
-  "bill.title": "明細",
-  "balance.title": "会員チャージ",
-  "refund.title": "返金",
-  "scoreProductList.title": "ポイントモール",
-  "scoreProductDetail.title": "商品詳細",
-  "scoreProductOrder.title": "注文一覧",
-  "scoreProductOrderDetail.title": "注文詳細",
-  "scoreProductConfirm.title": "確認",
-  "scan.title": "QRコード注文",
-  "vipDetail.title": "会員カード詳細",
-  "vipList.title": "会員カード一覧",
-  "menuDetail.title": "詳細",
-  "merchantCenter.title": "加盟店センター",
-  "merchantOrderList.title": "注文一覧",
-  "merchantCash.title": "出金",
-  "merchantAddCard.title": "銀行口座追加",
-  "merchantWallet.title": "マイウォレット",
-  "merchantOrderDetail.title": "注文詳細",
-  "merchantMy.title": "マイページ",
-  "merchantShopInfo.title": "店舗情報",
-  "merchantCashAccount.title": "出金口座",
-  "merchantWithdrawal.title": "出金履歴",
-  "merchantBell.title": "テーブル",
-  "due.title": "予約",
-  "dueDetail.title": "予約詳細",
-  "pad.title": "タブレット注文",
-  "pad.menu": "タブレットメニュー",
-  "welcome.tableNumber": "テーブル番号",
-  "welcome.setting": "設定",
-  "welcome.setShop": "店舗設定",
-  "welcome.initSetting": "店舗情報設定",
-  "welcome.selectShopFirst": "テーブル番号を設定する前に店舗を選択してください",
-  "welcome.setTableNumber": "テーブル番号設定",
-  "welcome.enterTableNumber": "登録済みのテーブル番号を入力してください",
-  "welcome.setShopAndTableNumber": "店舗とテーブル番号の情報を設定してください",
-  "welcome.save": "保存",
   "cart": {
     "title": "カート",
     "ordered": "注文済",
     "items": "点",
     "clear": "クリア",
-    "table": "テーブル",
     "checkout": "注文確定",
-    "prompt": "確認",
     "confirmClear": "カートを空にしますか",
-    "emptyCartMessage": "先に商品を選んでください",
-    "storeClosedMessage": "営業時間外です",
-    "outOfDeliveryRangeMessage": "選択された住所は配達範囲外です",
-    "loading": "読み込み中",
-    "backToShopping": "買い物を続ける",
-    "withoutTax": "税抜価格",
-    "withTax": "税込価格",
+    "emptyCart": "先に商品を選んでください",
     "total": "合計",
-    "amountToPay": "支払額",
-    "checkoutButton": "注文へ",
-    "orderedItems": "注文済み商品",
-    "tableNumber": "テーブル番号",
-    "userAvatar": "ユーザーアバター",
+    "amountToPay": "支払額"
+  },
+  "order": {
+    "title": "注文履歴",
+    "detail": "注文詳細",
+    "orderNumber": "注文番号",
     "orderTime": "注文時間",
-    "userNickname": "ユーザーネーム",
-    "productImage": "商品画像",
-    "productTitle": "商品名",
-    "productSpec": "商品仕様",
-    "productPrice": "価格",
-    "productQuantity": "数量",
-    "deliveryFee": "配達料",
-    "discountAmount": "割引額",
-    "deductionAmount": "値引き額",
-    "actualAmount": "実支払額",
     "paymentMethod": "支払方法",
-    "balancePayment": "残高支払い",
-    "wechatPay": "WeChat支払い",
-    "alipay": "Alipay",
-    "remarks": "備考",
-    "confirmOrder": "注文を確定",
-    "pay": "支払う"
+    "actualAmount": "実支払額",
+    "ordered": "注文済み",
+    "preparing": "調理中",
+    "delivering": "配送中",
+    "completed": "完了",
+    "cancelled": "キャンセル"
   },
   "mine": {
     "title": "マイページ",
-    "user-id": "ユーザーID",
     "login-benefits": "ログインして特典を受ける",
     "auth-login": "ログイン",
-    "not-vip": "VIPではありません",
-    "view-details": "詳細を見る",
+    "user-id": "ユーザーID",
+    "not-vip": "非VIP",
+    "view-details": "詳細を表示",
     "activate-now": "今すぐ有効化",
     "coupon": "クーポン",
     "points": "ポイント",
     "balance": "残高",
-    "history-consumption": "利用履歴",
+    "history-consumption": "累計消費",
     "my-orders": "マイ注文",
     "my-coupons": "マイクーポン",
     "switchIdentity": "身分切り替え",
+    "personalCenter": "個人センター",
+    "my-services": "マイサービス",
     "order": {
       "all": "全ての注文",
       "unpaid": "未払い",
       "in-progress": "処理中",
       "completed": "完了",
-      "refund": "返金"
+      "refund": "払い戻し"
     },
     "coupons": {
-      "available": "利用可能",
-      "received": "受け取り済み",
+      "received": "獲得済み",
       "used": "使用済み",
       "expired": "期限切れ",
       "center": "クーポンセンター"
     },
     "services": {
       "address": "住所",
-      "customer-service": "カスタマーサービス",
-      "feedback": "フィードバック",
-      "about-us": "会社概要"
-    },
-    "personal-center": "個人センター",
-    "personalCenter": "個人センター",
-    "my-services": "マイサービス",
-    "myServices": "マイサービス"
+      "customerService": "カスタマーサービス",
+      "feedback": "フィードバック"
+    }
+  },
+  "login": {
+    "title": "ログイン",
+    "welcome": "ようこそ",
+    "phoneLogin": "電話番号認証",
+    "passwordLogin": "パスワードログイン",
+    "orLoginWith": "または",
+    "quickLogin": "クイックログイン",
+    "areaCode": "国番号",
+    "phone": "電話番号",
+    "password": "パスワード",
+    "captcha": "認証コード",
+    "enterPhone": "電話番号を入力してください",
+    "enterPassword": "パスワードを入力してください",
+    "enterCaptcha": "認証コードを入力してください",
+    "getCaptcha": "認証コードを取得",
+    "loginNow": "今すぐログイン",
+    "invalidPhone": "電話番号が正しくありません",
+    "checkAgreement": "規約に同意してください",
+    "autoCreateAccount": "未登録の電話番号は認証後に自動でアカウントが作成されます",
+    "selectAreaCode": "国番号を選択",
+    "forgotPassword": "パスワードをお忘れですか?",
+    "noAccount": "アカウントをお持ちでない方",
+    "registerNow": "今すぐ登録",
+    "captchaSent": "認証コードが送信されました",
+    "sendFailed": "送信に失敗しました",
+    "currentEnvironment": "現在の環境",
+    "agreementPrefix": "私は",
+    "userAgreement": "利用規約",
+    "and": "および",
+    "privacyPolicy": "プライバシーポリシー",
+    "success": "ログイン成功"
   },
   "merchant": {
-    "my": {
-      "title": "マイページ",
-      "operating": "営業中",
-      "closed": "休業中",
-      "businessHours": "営業時間",
-      "address": "住所",
-      "phone": "電話番号",
-      "shopInfo": "店舗情報",
-      "withdrawalAccount": "出金口座",
-      "withdrawalDetails": "出金履歴",
-      "tabbar": {
-        "home": "ホーム",
-        "order": "注文",
-        "table": "テーブル",
-        "my": "マイ",
-        "back": "戻る"
-      }
-    },
-    "order": {
-      "title": "注文一覧",
-      "search": {
-        "placeholder": "注文番号/電話番号を入力",
-        "button": "注文を検索"
-      },
-      "tabs": {
-        "takein": "店内飲食",
-        "takeout": "テイクアウト",
-        "desk": "テーブル",
-        "due": "予約"
-      },
-      "status": {
-        "all": "すべて",
-        "pending": "受付待ち",
-        "preparing": "調理待ち",
-        "readyToDeliver": "配達待ち",
-        "completed": "完了",
-        "cancelled": "キャンセル",
-        "refunding": "返金中",
-        "pendingConfirmation": "確認待ち",
-        "confirmed": "確認済み",
-        "unpaid": "未払い",
-        "waiting": "待ち",
-        "refunded": "返金済み"
-      },
-      "dueStatus": {
-        "booking": "予約中",
-        "cancelled": "キャンセル済",
-        "completed": "完了"
-      },
-      "orderType": {
-        "takein": "店内飲食",
-        "takeout": "テイクアウト",
-        "desk": "テーブル",
-        "due": "予約"
-      },
-      "orderInfo": {
-        "orderTime": "注文時間",
-        "orderNumber": "注文番号",
-        "amount": "金額",
-        "refundReason": "返金理由",
-        "productDetails": "商品詳細",
-        "bookingInfo": "予約情報",
-        "tableNumber": "テーブル番号"
-      },
-      "actions": {
-        "cancelBooking": "予約をキャンセル",
-        "print": "伝票出力",
-        "receive": "受取確認",
-        "refund": "返金承認",
-        "confirmRefund": "返金を確認しますか?",
-        "confirmPrint": "伝票を出力しますか?",
-        "confirmReceive": "受け取りを確認しますか?",
-        "accept": "受付",
-        "serve": "提供",
-        "prepare": "調理",
-        "deliver": "配達",
-        "complete": "完了"
-      },
-      "confirmations": {
-        "acceptOrder": "注文を受け付けますか?",
-        "serveFood": "料理を提供しますか?",
-        "deliverOrder": "配達しますか?",
-        "completeOrder": "完了にしますか?",
-        "refundOrder": "返金しますか?"
-      }
-    },
-    "addBankCard": "銀行カードを追加",
-    "name": "名前",
-    "enterRealName": "本名を入力してください",
-    "pleaseEnterRealName": "本名を入力してください",
-    "cardNumber": "カード番号",
-    "enterCardNumber": "銀行カード番号を入力してください",
-    "pleaseEnterValidCardNumber": "有効なカード番号を入力してください",
-    "bankName": "銀行名",
-    "enterBankName": "銀行名を入力してください",
-    "pleaseEnterBankName": "銀行名を入力してください",
-    "phoneNumber": "電話番号",
-    "enterBankPhone": "銀行に登録されている電話番号を入力してください",
-    "pleaseEnterValidPhone": "有効な電話番号を入力してください",
-    "bell": {
-      "title": "テーブル",
-      "idle": "空き中",
-      "dining": "食事中",
-      "noDiningData": "食事中のデータがありません"
-    },
     "tabbar": {
       "home": "ホーム",
       "order": "注文",
       "table": "テーブル",
-      "mine": "マイページ",
-      "back": "戻る"
-    },
-    "cash": {
-      "title": "出金",
-      "withdrawTo": "出金先",
-      "pleaseSelect": "選択してください",
-      "withdrawalAmount": "出金額",
-      "enterAmount": "金額を入力してください",
-      "withdrawAll": "全額出金",
-      "availableAmount": "出金可能額",
-      "confirmWithdrawal": "出金確認",
-      "withdrawalTip": "ご注意:出金申請後、約10営業日以内に入金されます。",
-      "selectWithdrawalAccount": "出金口座を選択してください",
-      "add": "追加",
-      "noWithdrawableAmount": "出金可能な金額がありません"
-    },
-    "cashAccount": {
-      "title": "出金口座",
-      "bankCard": "銀行カード",
-      "unbind": "解除",
-      "addAccount": "口座を追加",
-      "confirmUnbind": "解除してもよろしいですか?",
-      "unbindTips": "ヒント"
+      "my": "マイ"
     },
     "index": {
-      "orderCategories": "注文分類",
       "merchantCenter": "店舗センター",
-      "myBalance": "残高(円)",
-      "balanceDetails": "残高詳細",
-      "withdrawNow": "今すぐ出金",
       "operating": "営業中",
       "closed": "休業中",
       "todayData": "本日のデータ",
-      "realTimeUpdate": "リアルタイム更新",
       "todayOrders": "本日の注文",
       "todayRevenue": "本日の売上",
-      "todayTakeout": "本日のテイクアウト",
-      "todayDineIn": "本日の店内飲食",
       "pendingOrders": "受付待ち",
-      "pendingMeals": "調理待ち",
-      "pendingDelivery": "配達待ち",
-      "completed": "完了",
-      "cancelled": "キャンセル"
+      "pendingMeals": "調理待ち"
     },
-    "orderDetail": {
-      "title": "注文詳細",
-      "userDeleted": "ユーザーは削除されました",
-      "tableNumber": "テーブル番号",
-      "pickupNumber": "受取番号",
-      "deliveryMethod": "配達方法",
-      "selfPickup": "店頭受取",
-      "takeout": "テイクアウト",
-      "dineIn": "店内飲食",
-      "reservation": "予約",
-      "orderTime": "注文時間",
-      "orderNumber": "注文番号",
-      "productDetails": "商品詳細",
-      "additionalOrder": "{count}回目の追加注文",
-      "firstOrder": "最初の注文",
-      "reservedTable": "予約テーブル",
-      "remarks": "備考",
-      "none": "なし",
-      "coupon": "クーポン",
-      "total": "合計",
-      "discounted": "割引額",
-      "reservationTime": "予約時間",
+    "order": {
+      "title": "注文一覧",
+      "status": {
+        "all": "すべて",
+        "pending": "受付待ち",
+        "preparing": "調理待ち",
+        "completed": "完了",
+        "cancelled": "キャンセル"
+      },
       "actions": {
         "accept": "受付",
         "serve": "提供",
-        "deliver": "配達",
-        "complete": "完了",
-        "cancelReservation": "予約キャンセル",
-        "confirmReservation": "予約確認"
-      },
-      "confirmations": {
-        "acceptOrder": "注文を受け付けますか?",
-        "serveFood": "料理を提供しますか?",
-        "deliverOrder": "配達しますか?",
-        "completeOrder": "完了にしますか?",
-        "cancelReservation": "予約をキャンセルしますか?",
-        "confirmReservation": "予約を確認しますか?"
-      },
-      "reservationPerson": "予約者",
-      "arrivalTime": "到着時間",
-      "arrivalTimeTip": "予約時間の10分前がデフォルト設定です",
-      "arrivalTimeError": "到着時間は予約時間より後にできません",
-      "arrivalTimeEarlyError": "到着時間は予約時間の1時間以上前にできません",
-      "reservedTableNumber": "予約テーブル番号",
-      "reservationPhone": "連絡先電話番号",
-      "paymentMethod": "支払方法"
-    },
-    "shopInfo": {
-      "title": "店舗情報",
-      "shopName": "店舗名",
-      "shopPhone": "店舗電話番号",
-      "mapAddress": "地図上の住所",
-      "detailAddress": "詳細住所",
-      "businessStartTime": "営業開始時間",
-      "businessEndTime": "営業終了時間",
-      "save": "保存",
-      "placeholders": {
-        "input": "入力してください",
-        "select": "選択してください",
-        "selectAddress": "住所を選択してください"
-      }
-    },
-    "wallet": {
-      "title": "マイウォレット",
-      "withdrawableBalance": "出金可能残高",
-      "currency": "円",
-      "withdraw": "出金する",
-      "transactionDetails": "取引履歴",
-      "income": "入金",
-      "expense": "出金"
-    },
-    "withdrawal": {
-      "title": "出金履歴",
-      "bankCardWithdrawal": "銀行カード出金",
-      "withdrawalTime": "出金時間",
-      "currency": "円",
-      "status": {
-        "pending": "審査待ち",
-        "processing": "処理中",
-        "rejected": "拒否されました",
-        "completed": "完了"
+        "complete": "完了"
       }
     }
   },
-  "order": {
-    "detail": "注文詳細",
-    "currentTable": "現在のテーブル",
-    "people": "人数",
-    "paymentMethod": "支払方法",
-    "orderAmount": "注文金額",
-    "tax": "税金",
-    "deliveryFee": "配送料",
-    "discountAmount": "割引額",
-    "deductionAmount": "値引き額",
-    "actualAmount": "実際の支払額",
-    "orderTime": "注文時間",
-    "orderNumber": "注文番号",
-    "serviceType": "サービスタイプ",
-    "selfPickup": "店頭受け取り",
-    "delivery": "デリバリー",
-    "pickupTime": "受け取り時間",
-    "immediatePickup": "すぐに受け取り",
-    "completionTime": "完成時間",
-    "remarks": "備考",
-    "none": "なし",
-    "confirmReceived": "受け取り確認",
-    "applyRefund": "返金申請",
-    "ordered": "注文済み",
-    "preparing": "調理中",
-    "delivering": "配送中",
-    "pleaseEat": "お召し上がりください",
-    "delivered": "配送完了",
-    "pleasePickup": "受け取りをお願いします",
-    "ordersAhead": "あなたの前に",
-    "waitingToPrepare": "件の注文待ち",
-    "balancePayment": "残高支払い",
-    "balance": "残高",
-    "insufficientBalance": "残高不足",
-    "wechatPay": "WeChat支払い",
-    "payNow": "今すぐ支払う",
-    "booking": "予約中",
-    "cancelled": "キャンセル済み",
-    "completed": "完了",
-    "addOrder": "追加注文",
-    "confirmOfflinePayment": "オフライン支払いの確認",
-    "pay": "会計",
-    "finishOrder": "お会計",
-    "toFinish": "注文を完了",
-    "orderFinished": "お会計",
-    "addFood": "追加注文",
-    "confirmSettlement": "決済確認",
-    "continueOrdering": "注文を続ける",
-    "settlementConfirmation": "決済を確認しますか?確認する場合はフロントへお越しください。まだ決済しない場合は、注文を続けることができます。",
-    "thankYouMessage": "ご利用ありがとうございました。またのご来店をお待ちしております。",
-    "autoRedirectTip": "注文を続けますか?{seconds}秒後に自動的にメニューページに移動します",
-    "stayOnPage": "このページに留まる"
+  "owner": {
+    "dashboard": "会社概要",
+    "shops": "店舗管理",
+    "todayData": "本日のデータ",
+    "totalRevenue": "総売上高",
+    "totalOrders": "総注文",
+    "operating": "営業中",
+    "closed": "休業中",
+    "enterShop": "店舗に入る"
   },
-  "login": {
-    "title": "ログイン",
-    "welcome": "ようこそ",
-    "padMode": "タブレットモード",
-    "usernamePlaceholder": "ユーザー名を入力",
-    "usernameTip": "アカウントを入力してください",
-    "passwordPlaceholder": "パスワードを入力",
-    "passwordTip": "パスワードは6文字以上必要です",
-    "submit": "ログイン",
-    "usernameRequired": "ユーザー名を入力してください",
-    "passwordRequired": "パスワードを入力してください",
-    "passwordLength": "パスワードは6文字以上必要です",
-    "agreementRequired": "利用規約に同意してください",
-    "success": "ログイン成功",
-    "failed": "ログイン失敗",
-    "agreementPrefix": "以下の規約に同意します",
-    "agreementAnd": "および",
-    "userAgreement": "利用規約",
-    "userAgreementText": "「利用規約」",
-    "privacyPolicy": "プライバシーポリシー",
-    "privacyPolicyText": "「プライバシーポリシー」",
-    "enterPhone": "電話番号を入力してください",
-    "autoCreateAccount": "未登録の電話番号は認証後に自動でアカウントが作成されます",
-    "enterCaptcha": "認証コードを入力してください",
-    "getCaptcha": "認証コードを取得",
-    "loginNow": "今すぐログイン",
-    "quickLogin": "電話番号で簡単ログイン",
-    "and": "および",
-    "checkAgreement": "規約に同意してください",
-    "invalidPhone": "電話番号が正しくありません",
-    "captchaSent": "認証コードが送信されました",
-    "sendFailed": "送信に失敗しました",
-    "selectAreaCode": "国番号を選択",
-    "agreement": "利用規約に同意する",
-    "currentEnvironment": "現在の環境",
-    "phoneLogin": "電話番号認証",
-    "passwordLogin": "パスワードログイン",
-    "emailLogin": "メールアドレスログイン",
-    "orLoginWith": "または以下の方法でログイン",
-    "areaCode": "国番号",
-    "phone": "電話番号",
-    "phoneNumber": "電話番号",
-    "username": "ユーザー名",
-    "email": "メールアドレス",
-    "password": "パスワード",
-    "captcha": "認証コード",
-    "verificationCode": "認証コード",
-    "enterUsername": "ユーザー名を入力してください",
-    "enterEmail": "メールアドレスを入力してください",
-    "enterPassword": "パスワードを入力してください",
-    "invalidEmail": "メールアドレスの形式が正しくありません",
-    "forgotPassword": "パスワードをお忘れですか?",
-    "noAccount": "アカウントをお持ちでない方",
-    "registerNow": "今すぐ登録"
+  "admin": {
+    "dashboard": "管理画面",
+    "users": "ユーザー管理",
+    "merchants": "加盟店管理",
+    "searchUser": "ユーザー名/電話番号を検索",
+    "all": "すべて",
+    "normalUser": "一般ユーザー",
+    "merchant": "加盟店"
+  },
+  "pos": {
+    "welcome": "POSシステム",
+    "tables": "テーブル管理",
+    "orders": "注文管理",
+    "available": "空席",
+    "occupied": "使用中",
+    "openTable": "開席",
+    "checkout": "会計",
+    "acceptOrder": "受注",
+    "completeOrder": "完了"
+  },
+  "buffet": {
+    "title": "食べ放題モード",
+    "selectPlan": "食べ放題プランを選択",
+    "remaining": "残り時間",
+    "pause": "一時停止",
+    "resume": "再開",
+    "endEarly": "早期終了",
+    "timeUp": "食べ放題時間終了!ご来店ありがとうございました",
+    "cannotOrder": "現在注文できません",
+    "confirmOrder": "注文確認",
+    "orderSuccess": "注文成功!"
   },
   "role": {
-    "user": "一般ユーザー",
-    "merchant": "店舗オーナー",
-    "waiter": "店員",
+    "customer": "お客様",
+    "staff": "スタッフ",
+    "manager": "店長",
+    "owner": "オーナー",
     "admin": "管理者",
     "switchTitle": "身分切り替え",
-    "switchConfirm": "{role}に切り替えますか?",
-    "switchSuccess": "{role}に切り替えました",
-    "noPermission": "このロールの権限がありません",
     "currentRole": "現在の身分"
-  },
-  "meLogin": {
-    "title": "店舗ログイン"
-  },
-  "logout": {
-    "title": "ログアウト"
-  },
-  "dueDetail": {
-    "title": "予約詳細",
-    "reservation": "予約情報",
-    "arrivalTime": "到着時間",
-    "arrivalTimeTip": "予約時間の10分前がデフォルト設定です",
-    "arrivalTimeError": "到着時間は予約時間より後にできません",
-    "arrivalTimeEarlyError": "到着時間は予約時間の1時間以上前にできません",
-    "name": "お名前",
-    "enterRealName": "本名を入力してください",
-    "phoneNumber": "電話番号",
-    "enterPhone": "電話番号を入力してください",
-    "invalidPhoneFormat": "10-11桁の有効な電話番号を入力してください",
-    "captcha": "認証コード",
-    "enterCaptcha": "認証コードを入力してください",
-    "getCaptcha": "認証コードを取得",
-    "submit": "送信",
-    "confirmReservation": "予約時間を確認してください"
   }
 }

+ 0 - 36
src/locale/uni-app.ja.json

@@ -1,36 +0,0 @@
-{
-  "common": {
-    "uni.app.quit": "もう一度押すと、アプリケーションが終了します",
-    "uni.async.error": "サーバーへの接続がタイムアウトしました。画面をクリックして再試行してください",
-    "uni.showActionSheet.cancel": "キャンセル",
-    "uni.showToast.unpaired": "使用するには、showToastとhideToastをペアにする必要があることに注意してください",
-    "uni.showLoading.unpaired": "使用するには、showLoadingとhideLoadingをペアにする必要があることに注意してください",
-    "uni.showModal.cancel": "キャンセル",
-    "uni.showModal.confirm": "OK",
-    "uni.chooseImage.cancel": "キャンセル",
-    "uni.chooseImage.sourceType.album": "アルバムから選択",
-    "uni.chooseImage.sourceType.camera": "カメラ",
-    "uni.chooseVideo.cancel": "キャンセル",
-    "uni.chooseVideo.sourceType.album": "アルバムから選択",
-    "uni.chooseVideo.sourceType.camera": "カメラ",
-    "uni.previewImage.cancel": "キャンセル",
-    "uni.previewImage.button.save": "画像を保存",
-    "uni.previewImage.save.success": "画像をアルバムに正常に保存します",
-    "uni.previewImage.save.fail": "画像をアルバムに保存できませんでした",
-    "uni.setClipboardData.success": "コンテンツがコピーされました",
-    "uni.scanCode.title": "スキャンコード",
-    "uni.scanCode.album": "アルバム",
-    "uni.scanCode.fail": "認識に失敗しました",
-    "uni.scanCode.flash.on": "タッチして点灯",
-    "uni.scanCode.flash.off": "タップして閉じる",
-    "uni.startSoterAuthentication.authContent": "指紋認識...",
-    "uni.picker.done": "完了",
-    "uni.picker.cancel": "キャンセル",
-    "uni.video.danmu": "「弾幕」",
-    "uni.video.volume": "ボリューム",
-    "uni.button.feedback.title": "質問のフィードバック",
-    "uni.button.feedback.send": "送信"
-  },
-  "ios": {},
-  "android": {}
-}

+ 137 - 575
src/locale/zh-Hans.json

@@ -1,188 +1,78 @@
 {
-  "language": "语言",
-  "locale": {
-    "auto": "系统",
-    "language": "选择语言",
-    "en": "English",
-    "ja": "日本語",
-    "zh-Hans": "简体中文",
-    "zh-Hant": "繁体中文"
-  },
   "common": {
-    "tips": "温馨提示",
+    "confirm": "确定",
+    "cancel": "取消",
     "success": "操作成功",
     "error": "操作失败",
-    "cancel": "取消",
-    "confirm": "确定",
-    "featureInDevelopment": "功能正在开发中,敬请期待",
+    "loading": "加载中",
+    "pleaseLogin": "请先登录",
+    "selectLanguage": "选择语言",
+    "languageChanged": "语言已切换",
     "viewMore": "更多",
     "environment": "环境",
-    "pleaseLogin": "请先登录"
+    "featureInDevelopment": "功能开发中"
+  },
+  "search": {
+    "placeholder": "搜索店铺、菜品"
   },
   "index": {
-    "component": "组件",
-    "respected-user": "尊敬的客户",
-    "authorize-tip": "请授权以获取更好的服务",
-    "api": "API",
-    "schema": "Schema",
-    "detail": "详情",
-    "language": "语言",
-    "languageInfo": "语言信息",
-    "systemLanguage": "系统语言",
-    "applicationLanguage": "应用语言",
-    "languageChangeConfirm": "应用此设置将重启App",
-    "hello": "您好",
-    "guest": "游客",
-    "systemName": "FastEat点餐外卖系统",
-    "respectedUser": "尊敬的用户",
-    "authorizeTip": "为给您提供更好的服务请授权登录",
-    "normalMember": "普通会员",
-    "loginRegister": "登录/注册",
-    "balance": "余额",
-    "points": "积分",
-    "coupon": "券",
-    "selfPickup": "自取",
-    "skipQueue": "下单免排队",
-    "delivery": "外卖",
-    "deliveryTip": "美食送到家",
-    "reservation": "提前预约",
-    "reservationTip": "提前预约,到店直接就餐",
-    "scanOrder": "扫码点餐",
-    "scanTip": "打开相机扫一扫即可享用美食哦",
-    "viewStores": "查看门店",
-    "findMoreStores": "等你发现更多线下门店",
-    "explore": "找一找",
-    "pointsMall": "积分商城",
-    "pointsMallTip": "进入积分商城兑换奈雪券及周边好礼",
-    "browse": "逛一逛",
-    "activities": "活动抢先知",
     "home": "首页",
-    "merchantCenter": "商户中心",
-    "myBalance": "我的余额(元)",
-    "balanceDetails": "余额明细",
-    "withdrawNow": "立即提现",
-    "operating": "营业中",
-    "closed": "停业中",
-    "todayData": "今日数据",
-    "realTimeUpdate": "实时更新",
-    "todayOrders": "今日订单",
-    "todayRevenue": "今日营业额",
-    "todayTakeout": "今日外卖",
-    "todayDineIn": "今日堂食",
-    "pendingOrders": "待接单",
-    "pendingMeals": "待出餐",
-    "pendingDelivery": "待配送",
-    "completed": "已完成",
-    "cancelled": "已取消",
-    "todayRecommend": "今日推荐",
-    "hotSales": "热销商品"
-  },
-  "index.home": "首页",
-  "api": {
-    "message": "提示"
-  },
-  "schema": {
-    "name": "姓名",
-    "add": "新增",
-    "addSuccess": "新增成功"
-  },
-  "welcome": {
-    "message": "欢迎光临",
-    "setting": "设置",
-    "startOrdering": "开始点餐",
-    "setShop": "设置店铺",
-    "initSetting": "店铺信息设定",
-    "selectShopFirst": "设置桌号前请先选择店铺",
-    "setTableNumber": "设置桌号",
-    "enterTableNumber": "请输入已录入桌号",
-    "setShopAndTableNumber": "请先设置店铺和桌号信息",
-    "save": "保存",
-    "shopId": "店铺ID",
-    "deskId": "桌台ID",
-    "deskNumber": "桌台号",
-    "deskPeople": "就餐人数",
-    "submit": "提交",
-    "enterShopId": "请输入店铺ID",
-    "enterDeskId": "请输入桌台ID",
-    "enterDeskNumber": "请输入桌台号",
-    "enterDeskPeople": "请输入就餐人数",
-    "shopIdRequired": "请输入店铺ID",
-    "deskIdRequired": "请输入桌台ID",
-    "deskNumberRequired": "请输入桌台号",
-    "deskPeopleRequired": "请输入就餐人数",
-    "shopNotExist": "店铺不存在请重新输入",
-    "tableNumber": "桌号"
+    "nearbyHot": "附近热推",
+    "categories": {
+      "japanese": "日料",
+      "chinese": "中餐",
+      "western": "西餐",
+      "fastFood": "快餐",
+      "dessert": "甜品",
+      "drinks": "饮品",
+      "favorites": "我的收藏",
+      "coupons": "领券中心"
+    }
   },
-  "menu.title": "点餐",
-  "order.title": "订单",
-  "mine.title": "我的",
   "menu": {
     "title": "点餐",
     "currentTable": "当前桌号",
     "people": "人数",
-    "peopleUnit": "人",
-    "tableNumber": "桌号",
     "closed": "已歇业",
     "distance": "距离您",
-    "deliveryDistance": "配送距离",
-    "noDelivery": "本店不支持外卖",
     "dineIn": "自取",
     "takeOut": "外卖",
     "selectSpec": "选规格",
     "soldOut": "已售罄",
-    "shortOf": "差",
-    "startDelivery": "元起送",
     "checkout": "去结算",
-    "stock": "库存",
     "addToCart": "加入购物车",
-    "clear": "清空",
-    "locateNearestStore": "定位最近的门店",
-    "details": "详情",
-    "sold": "已售",
-    "productDetails": "商品详情",
-    "minOrderAmount": "差{amount}元起送",
-    "continueShopping": "继续购物",
-    "historyOrder": "历史订单",
-    "finishOrdering": "结束点餐",
-    "viewOrderFailed": "无法查看历史订单",
-    "finishOrderFailed": "无法完成操作"
+    "clear": "清空"
   },
   "cart": {
     "title": "购物车",
     "ordered": "已点",
     "items": "份",
     "clear": "清空",
-    "table": "桌位",
     "checkout": "确定下单",
-    "prompt": "提示",
     "confirmClear": "确定清空购物车么",
-    "emptyCartMessage": "请先去点餐哦",
-    "storeClosedMessage": "不在店铺营业时间内",
-    "outOfDeliveryRangeMessage": "选中的地址不在配送范围",
-    "loading": "加载中",
-    "backToShopping": "返回继续购物",
-    "withoutTax": "税前价格",
-    "withTax": "税后价格",
+    "emptyCart": "请先去点餐哦",
     "total": "合计",
-    "amountToPay": "应付",
-    "checkoutButton": "去下单",
-    "orderedItems": "已下单",
-    "tableNumber": "桌位",
-    "userAvatar": "用户头像",
+    "amountToPay": "应付"
+  },
+  "order": {
+    "title": "订单",
+    "detail": "订单详情",
+    "orderNumber": "订单号",
     "orderTime": "下单时间",
-    "userNickname": "用户昵称",
-    "productImage": "商品图片",
-    "productTitle": "商品名称",
-    "productSpec": "商品规格",
-    "productPrice": "价格",
-    "productQuantity": "数量"
+    "paymentMethod": "支付方式",
+    "actualAmount": "实付金额",
+    "ordered": "已下单",
+    "preparing": "制作中",
+    "delivering": "配送中",
+    "completed": "已完成",
+    "cancelled": "已取消"
   },
   "mine": {
     "title": "我的",
-    "user-id": "用户ID",
     "login-benefits": "登录获取更多权益",
     "auth-login": "授权登录",
-    "not-vip": "非会员",
+    "user-id": "用户ID",
+    "not-vip": "非VIP用户",
     "view-details": "查看详情",
     "activate-now": "立即开通",
     "coupon": "优惠券",
@@ -192,6 +82,8 @@
     "my-orders": "我的订单",
     "my-coupons": "我的优惠券",
     "switchIdentity": "切换身份",
+    "personalCenter": "个人中心",
+    "my-services": "我的服务",
     "order": {
       "all": "全部订单",
       "unpaid": "待付款",
@@ -200,461 +92,131 @@
       "refund": "退款/售后"
     },
     "coupons": {
-      "available": "可用",
+      "received": "已领取",
       "used": "已使用",
       "expired": "已过期",
-      "center": "优惠券中心",
-      "received": "已领取"
+      "center": "领券中心"
     },
     "services": {
       "address": "收货地址",
-      "customer-service": "联系客服",
-      "feedback": "意见反馈",
-      "about-us": "关于我们"
-    },
-    "personal-center": "个人中心",
-    "personalCenter": "个人中心",
-    "my-services": "我的服务",
-    "myServices": "我的服务"
+      "customerService": "联系客服",
+      "feedback": "意见反馈"
+    }
+  },
+  "login": {
+    "title": "登录",
+    "welcome": "欢迎登录",
+    "phoneLogin": "手机验证码",
+    "passwordLogin": "密码登录",
+    "orLoginWith": "或使用以下方式登录",
+    "quickLogin": "一键登录",
+    "areaCode": "区号",
+    "phone": "手机号",
+    "password": "密码",
+    "captcha": "验证码",
+    "enterPhone": "请输入手机号",
+    "enterPassword": "请输入密码",
+    "enterCaptcha": "请输入验证码",
+    "getCaptcha": "获取验证码",
+    "loginNow": "立即登录",
+    "invalidPhone": "手机号码格式不对",
+    "checkAgreement": "请勾选下面协议",
+    "autoCreateAccount": "未注册的手机号验证后自动创建账号",
+    "selectAreaCode": "选择区号",
+    "forgotPassword": "忘记密码?",
+    "noAccount": "还没有账号?",
+    "registerNow": "立即注册",
+    "captchaSent": "验证码已发送",
+    "sendFailed": "发送失败",
+    "currentEnvironment": "当前环境",
+    "agreementPrefix": "我已阅读并同意",
+    "userAgreement": "用户协议",
+    "and": "与",
+    "privacyPolicy": "隐私政策",
+    "success": "登录成功"
   },
   "merchant": {
-    "my": {
-      "title": "我的",
+    "tabbar": {
+      "home": "首页",
+      "order": "订单",
+      "table": "桌台",
+      "my": "我的"
+    },
+    "index": {
+      "merchantCenter": "商户中心",
       "operating": "营业中",
       "closed": "停业中",
-      "businessHours": "营业时间",
-      "address": "地址",
-      "phone": "电话",
-      "shopInfo": "店铺信息",
-      "withdrawalAccount": "提现账户",
-      "withdrawalDetails": "提现明细",
-      "tabbar": {
-        "home": "首页",
-        "order": "订单",
-        "table": "桌台",
-        "my": "我的",
-        "back": "返回"
-      }
+      "todayData": "今日数据",
+      "todayOrders": "今日订单",
+      "todayRevenue": "今日营业额",
+      "pendingOrders": "待接单",
+      "pendingMeals": "待出餐"
     },
     "order": {
       "title": "订单列表",
-      "search": {
-        "placeholder": "请输入订单号/手机号",
-        "button": "查询订单"
-      },
-      "tabs": {
-        "takein": "自取",
-        "takeout": "外卖",
-        "desk": "堂食",
-        "due": "预约"
-      },
       "status": {
         "all": "全部",
         "pending": "待接单",
         "preparing": "待出餐",
-        "readyToDeliver": "待配送",
         "completed": "已完成",
-        "cancelled": "已取消",
-        "refunding": "退款中",
-        "pendingConfirmation": "待确认",
-        "confirmed": "已确认",
-        "unpaid": "待支付",
-        "waiting": "等待中",
-        "refunded": "已退款"
-      },
-      "dueStatus": {
-        "booking": "预约中",
-        "cancelled": "已取消",
-        "completed": "已完成"
-      },
-      "orderType": {
-        "takein": "堂食",
-        "takeout": "外卖",
-        "desk": "桌台",
-        "due": "预约"
-      },
-      "orderInfo": {
-        "orderTime": "下单时间",
-        "orderNumber": "订单号",
-        "amount": "金额",
-        "refundReason": "退款原因",
-        "productDetails": "商品详情",
-        "bookingInfo": "预约信息",
-        "tableNumber": "桌号"
+        "cancelled": "已取消"
       },
       "actions": {
         "accept": "接单",
         "serve": "出餐",
-        "prepare": "备餐",
-        "deliver": "配送",
-        "complete": "完成",
-        "refund": "退款",
-        "cancelBooking": "取消预约",
-        "print": "出单",
-        "receive": "后台收货",
-        "confirmRefund": "确定要退款?",
-        "confirmPrint": "确定要出单吗?",
-        "confirmReceive": "确定要收货吗?",
-        "confirm": "确定要接单吗?",
-        "serveFood": "确定要出餐吗?",
-        "deliverOrder": "确定要配送吗?",
-        "completeOrder": "确定要完成吗?",
-        "refundOrder": "确定要退款吗?"
-      },
-      "confirmations": {
-        "acceptOrder": "确定要接单吗?",
-        "serveFood": "确定要出餐吗?",
-        "deliverOrder": "确定要配送吗?",
-        "completeOrder": "确定要完成吗?",
-        "refundOrder": "确定要退款吗?"
-      }
-    },
-    "index": {
-      "orderCategories": "订单分类",
-      "merchantCenter": "商户中心",
-      "myBalance": "我的余额(元)",
-      "balanceDetails": "余额明细",
-      "withdrawNow": "立即提现",
-      "operating": "营业中",
-      "closed": "停业中",
-      "todayData": "今日数据",
-      "realTimeUpdate": "实时更新",
-      "todayOrders": "今日订单",
-      "todayRevenue": "今日营业额",
-      "todayTakeout": "今日外卖",
-      "todayDineIn": "今日堂食",
-      "pendingOrders": "待接单",
-      "pendingMeals": "待出餐",
-      "pendingDelivery": "待配送",
-      "completed": "已完成",
-      "cancelled": "已取消"
-    },
-    "addBankCard": "添加银行卡",
-    "name": "姓名",
-    "enterRealName": "请输入真实姓名",
-    "pleaseEnterRealName": "请输入真实姓名",
-    "cardNumber": "卡号",
-    "enterCardNumber": "请输入银行卡卡号",
-    "pleaseEnterValidCardNumber": "请输入正确卡号",
-    "bankName": "银行名称",
-    "enterBankName": "请输入银行名称",
-    "pleaseEnterBankName": "请输入银行卡名称",
-    "phoneNumber": "手机号",
-    "enterBankPhone": "请输入银行预留手机号",
-    "pleaseEnterValidPhone": "请输入正确手机号",
-    "bell": {
-      "title": "桌台",
-      "idle": "空闲中",
-      "dining": "就餐中",
-      "noDiningData": "暂无就餐中数据"
-    },
-    "cash": {
-      "title": "提现",
-      "withdrawTo": "提现至",
-      "pleaseSelect": "请选择",
-      "withdrawalAmount": "提现金额",
-      "enterAmount": "请输入金额",
-      "withdrawAll": "全部提现",
-      "availableAmount": "可提现金额",
-      "confirmWithdrawal": "确认提现",
-      "withdrawalTip": "温馨提示: 提现申请发起后,预计在10个工作日内到账。",
-      "selectWithdrawalAccount": "请选择提现账号",
-      "add": "添加",
-      "noWithdrawableAmount": "没有可提现金额"
-    },
-    "cashAccount": {
-      "title": "提现账户",
-      "bankCard": "银行卡",
-      "unbind": "解绑",
-      "addAccount": "添加账号",
-      "confirmUnbind": "确定要解绑?",
-      "unbindTips": "提示"
-    },
-    "tabbar": {
-      "home": "首页",
-      "order": "订单",
-      "table": "桌台",
-      "my": "我的",
-      "back": "返回"
-    },
-    "orderDetail": {
-      "title": "订单详情",
-      "userDeleted": "此用户已注销",
-      "tableNumber": "桌号",
-      "pickupNumber": "取餐号",
-      "deliveryMethod": "配送方式",
-      "selfPickup": "自取",
-      "takeout": "外卖",
-      "dineIn": "堂食",
-      "reservation": "预约",
-      "orderTime": "下单时间",
-      "orderNumber": "订单编号",
-      "productDetails": "商品明细",
-      "additionalOrder": "第{count}次加菜",
-      "firstOrder": "首次点菜",
-      "reservedTable": "预约餐桌",
-      "remarks": "备注",
-      "none": "无",
-      "coupon": "抵用券",
-      "total": "共计",
-      "discounted": "已优惠",
-      "reservationTime": "预约时间",
-      "actions": {
-        "accept": "接单",
-        "serve": "出餐",
-        "deliver": "配送",
-        "complete": "完成",
-        "cancelReservation": "取消预约",
-        "confirmReservation": "确认预约"
-      },
-      "confirmations": {
-        "acceptOrder": "确定要接单吗?",
-        "serveFood": "确定要出餐吗?",
-        "deliverOrder": "确定要配送吗?",
-        "completeOrder": "确定要完成吗?",
-        "cancelReservation": "确定要取消预约吗?",
-        "confirmReservation": "确定要确认预约吗?"
-      },
-      "reservationPerson": "预约人",
-      "arrivalTime": "到达时间",
-      "arrivalTimeTip": "默认为预约时间前10分钟",
-      "arrivalTimeError": "到达时间不能晚于预约时间",
-      "arrivalTimeEarlyError": "提前到达时间不能超过1小时",
-      "reservedTableNumber": "预约桌号",
-      "reservationPhone": "预留电话",
-      "paymentMethod": "支付方式"
-    },
-    "shopInfo": {
-      "title": "店铺资料",
-      "shopName": "店铺名称",
-      "shopPhone": "店铺电话",
-      "mapAddress": "地图地址",
-      "detailAddress": "详细地址",
-      "businessStartTime": "营业开始时间",
-      "businessEndTime": "营业结束时间",
-      "save": "保存",
-      "placeholders": {
-        "input": "请输入",
-        "select": "请选择",
-        "selectAddress": "请选择地址"
-      }
-    },
-    "wallet": {
-      "title": "我的钱包",
-      "withdrawableBalance": "可提现余额",
-      "currency": "元",
-      "withdraw": "去提现",
-      "transactionDetails": "收支明细",
-      "income": "收入",
-      "expense": "支出"
-    },
-    "withdrawal": {
-      "title": "提现明细",
-      "bankCardWithdrawal": "银行卡提现",
-      "withdrawalTime": "提现时间",
-      "currency": "元",
-      "status": {
-        "pending": "未审核",
-        "processing": "待到账",
-        "rejected": "审核拒绝",
-        "completed": "已到账"
+        "complete": "完成"
       }
     }
   },
-  "login": {
-    "title": "登录",
-    "welcome": "欢迎登录",
-    "padMode": "平板模式",
-    "usernamePlaceholder": "请输入用户名",
-    "usernameTip": "请输入您的账号",
-    "passwordPlaceholder": "请输入密码",
-    "passwordTip": "密码长度至少6位",
-    "submit": "立即登录",
-    "usernameRequired": "请输入用户名",
-    "passwordRequired": "请输入密码",
-    "passwordLength": "密码长度至少6位",
-    "agreementRequired": "请勾选用户协议",
-    "success": "登录成功",
-    "failed": "登录失败",
-    "agreementPrefix": "我已经阅读并遵守",
-    "agreementAnd": "与",
-    "userAgreement": "用户协议",
-    "userAgreementText": "《用户协议》",
-    "privacyPolicy": "隐私政策",
-    "privacyPolicyText": "《隐私政策》",
-    "enterPhone": "请输入手机号",
-    "autoCreateAccount": "未注册的手机号验证后自动创建账号",
-    "enterCaptcha": "请输入验证码",
-    "getCaptcha": "获取验证码",
-    "loginNow": "立即登录",
-    "quickLogin": "手机号快捷登录",
-    "and": "与",
-    "checkAgreement": "请勾选下面协议",
-    "invalidPhone": "手机号码格式不对",
-    "selectAreaCode": "选择区号",
-    "currentEnvironment": "当前环境",
-    "phoneLogin": "手机验证码",
-    "passwordLogin": "密码登录",
-    "emailLogin": "邮箱登录",
-    "orLoginWith": "或使用以下方式登录",
-    "areaCode": "区号",
-    "phone": "手机号",
-    "phoneNumber": "手机号",
-    "username": "用户名",
-    "email": "邮箱",
-    "password": "密码",
-    "captcha": "验证码",
-    "verificationCode": "验证码",
-    "enterUsername": "请输入用户名",
-    "enterEmail": "请输入邮箱地址",
-    "enterPassword": "请输入密码",
-    "invalidEmail": "邮箱格式不正确",
-    "forgotPassword": "忘记密码?",
-    "noAccount": "还没有账号?",
-    "registerNow": "立即注册",
-    "captchaSent": "验证码已发送",
-    "sendFailed": "发送失败"
+  "owner": {
+    "dashboard": "公司总览",
+    "shops": "店铺管理",
+    "todayData": "今日数据",
+    "totalRevenue": "总营业额",
+    "totalOrders": "总订单",
+    "operating": "营业中",
+    "closed": "休息中",
+    "enterShop": "进入店铺"
+  },
+  "admin": {
+    "dashboard": "管理后台",
+    "users": "用户管理",
+    "merchants": "商家管理",
+    "searchUser": "搜索用户名/手机号",
+    "all": "全部",
+    "normalUser": "普通用户",
+    "merchant": "商家"
+  },
+  "pos": {
+    "welcome": "POS系统",
+    "tables": "桌位管理",
+    "orders": "订单管理",
+    "available": "空闲",
+    "occupied": "就餐中",
+    "openTable": "开台",
+    "checkout": "结账",
+    "acceptOrder": "接单",
+    "completeOrder": "完成"
+  },
+  "buffet": {
+    "title": "放题模式",
+    "selectPlan": "选择放题方案",
+    "remaining": "剩余时间",
+    "pause": "暂停",
+    "resume": "继续",
+    "endEarly": "提前结束",
+    "timeUp": "放题时间已到!感谢光临",
+    "cannotOrder": "当前无法点餐",
+    "confirmOrder": "确认点餐",
+    "orderSuccess": "点餐成功!"
   },
   "role": {
-    "user": "普通用户",
-    "merchant": "商家",
-    "waiter": "服务员",
+    "customer": "顾客",
+    "staff": "员工",
+    "manager": "店长",
+    "owner": "老板",
     "admin": "管理员",
     "switchTitle": "切换身份",
-    "switchConfirm": "确定切换到{role}身份吗?",
-    "switchSuccess": "已切换至{role}",
-    "noPermission": "您没有此角色权限",
     "currentRole": "当前身份"
-  },
-  "meLogin": {
-    "title": "商家登陆"
-  },
-  "logout": {
-    "title": "退出登录"
-  },
-  "pad": {
-    "title": "pad点餐",
-    "menu": "pad菜单"
-  },
-  "order": {
-    "detail": "订单详情",
-    "currentTable": "当前桌号",
-    "people": "就餐人数",
-    "paymentMethod": "支付方式",
-    "orderAmount": "订单金额",
-    "tax": "税金",
-    "deliveryFee": "配送费",
-    "discountAmount": "优惠金额",
-    "deductionAmount": "折扣金额",
-    "actualAmount": "实付金额",
-    "orderTime": "下单时间",
-    "orderNumber": "订单号",
-    "serviceType": "享用方式",
-    "selfPickup": "自取",
-    "delivery": "外卖",
-    "pickupTime": "取餐时间",
-    "immediatePickup": "立即取餐",
-    "completionTime": "制作完成时间",
-    "remarks": "备注",
-    "none": "无",
-    "confirmReceived": "确认收到餐",
-    "applyRefund": "退款",
-    "ordered": "已下单",
-    "preparing": "制作中",
-    "delivering": "配送中",
-    "pleaseEat": "请就餐",
-    "delivered": "已送达",
-    "pleasePickup": "请取餐",
-    "ordersAhead": "您前面还有",
-    "waitingToPrepare": "单待制作",
-    "balancePayment": "余额支付",
-    "balance": "余额",
-    "insufficientBalance": "余额不足",
-    "wechatPay": "微信支付",
-    "payNow": "立即支付",
-    "booking": "预约中",
-    "cancelled": "已取消",
-    "completed": "已完成",
-    "addOrder": "去加餐",
-    "confirmOfflinePayment": "线下确认收款",
-    "pay": "去买单",
-    "finishOrder": "完成订单",
-    "orderFinished": "订单已完成",
-    "addFood": "加餐",
-    "confirmSettlement": "确定结算",
-    "continueOrdering": "继续下单",
-    "settlementConfirmation": "是否确认结算?如果确定结算请您去前台结算,如果未结算,请您继续下单。",
-    "thankYouMessage": "感谢您的用餐,期待您的下次光临",
-    "autoRedirectTip": "是否继续点餐?{seconds}秒后将自动跳转到点餐页面",
-    "stayOnPage": "留在此页"
-  },
-  "dueDetail": {
-    "title": "预约详情",
-    "reservation": "预约信息",
-    "arrivalTime": "到达时间",
-    "arrivalTimeTip": "默认为预约时间前10分钟",
-    "arrivalTimeError": "到达时间不能晚于预约时间",
-    "arrivalTimeEarlyError": "提前到达时间不能超过1小时",
-    "name": "姓名",
-    "enterRealName": "请输入真实姓名",
-    "phoneNumber": "手机号",
-    "enterPhone": "请输入手机号",
-    "invalidPhoneFormat": "请输入10-11位有效手机号",
-    "captcha": "验证码",
-    "enterCaptcha": "请输入验证码",
-    "getCaptcha": "获取验证码",
-    "submit": "提交",
-    "confirmReservation": "请确认预约时间"
-  },
-  "pay": {
-    "title": "支付",
-    "scanTitle": "下单",
-    "delivery": "外卖配送",
-    "selfPickup": "点餐自取",
-    "confirmAddress": "选择收货地址",
-    "pickupTime": "取餐时间",
-    "immediatePickup": "立即取餐",
-    "contactPhone": "联系电话",
-    "enterPhone": "请输入手机号码",
-    "autoFill": "自动填写",
-    "estimatedDeliveryTime": "预计送达时间",
-    "coupon": "优惠券",
-    "noAvailableCoupons": "暂无可用",
-    "full": "满",
-    "reduce": "减",
-    "availableCoupons": "可用优惠券",
-    "sheets": "张",
-    "memberDiscount": "会员折扣",
-    "noDiscount": "没有折扣",
-    "enjoy": "享受",
-    "discount": "折优惠",
-    "total": "总计",
-    "deliveryFee": "配送费",
-    "couponDiscount": "优惠券折扣",
-    "actualAmount": "实付",
-    "paymentMethod": "支付方式",
-    "balancePayment": "余额支付",
-    "balance": "余额",
-    "insufficientBalance": "余额不足",
-    "wechatPay": "微信支付",
-    "alipay": "支付宝",
-    "storePayment": "到店支付",
-    "remarks": "备注",
-    "clickToAddRemarks": "点击填写备注",
-    "confirmOrder": "提交订单",
-    "pay": "付款",
-    "confirmAddressTitle": "请再次确认下单地址",
-    "changeAddress": "修改地址",
-    "confirmAndPay": "确认并付款",
-    "submitOrder": "提交订单",
-    "loading": "加载中",
-    "selectDeliveryAddress": "请选择收货地址",
-    "minOrderAmount": "本店外卖起送价为¥{amount}",
-    "tips": "温馨提示",
-    "subscribeMessageTip": "为更好的促进您与商家的交流,小程序需要在您成交时向您发送消息",
-    "agree": "同意",
-    "disagree": "拒绝",
-    "defaultSpec": "默认",
-    "alipayBrowserTip": "请使用普通浏览器打开进行支付宝支付"
   }
 }

+ 144 - 339
src/locale/zh-Hant.json

@@ -1,167 +1,78 @@
 {
-  "language": "語言",
-  "locale": {
-    "auto": "系統",
-    "language": "請選擇語言",
-    "zhHans": "简体中文",
-    "en": "English",
-    "ja": "日本語",
-    "zhHant": "繁體中文"
-  },
   "common": {
-    "tips": "溫馨提示",
+    "confirm": "確定",
+    "cancel": "取消",
     "success": "操作成功",
     "error": "操作失敗",
-    "cancel": "取消",
-    "confirm": "確定",
-    "featureInDevelopment": "功能正在開發中,敬請期待",
+    "loading": "載入中",
+    "pleaseLogin": "請先登入",
+    "selectLanguage": "選擇語言",
+    "languageChanged": "語言已切換",
     "viewMore": "更多",
     "environment": "環境",
-    "pleaseLogin": "請先登錄"
+    "featureInDevelopment": "功能開發中"
+  },
+  "search": {
+    "placeholder": "搜尋店舖、菜品"
   },
   "index": {
-    "component": "組件",
-    "api": "API",
-    "schema": "Schema",
-    "detail": "詳情",
-    "language": "語言",
-    "languageInfo": "語言信息",
-    "systemLanguage": "系統語言",
-    "applicationLanguage": "應用語言",
-    "languageChangeConfirm": "應用此設置將重啟App",
-    "hello": "您好",
-    "guest": "遊客",
-    "systemName": "FastEat點餐外賣系統",
-    "respectedUser": "尊敬的用戶",
-    "authorizeTip": "為給您提供更好的服務請授權登錄",
-    "normalMember": "普通會員",
-    "loginRegister": "登錄/註冊",
-    "balance": "餘額",
-    "points": "積分",
-    "coupon": "券",
-    "selfPickup": "自取",
-    "skipQueue": "下單免排隊",
-    "delivery": "外賣",
-    "deliveryTip": "美食送到家",
-    "reservation": "提前預約",
-    "reservationTip": "提前預約,到店直接就餐",
-    "scanOrder": "掃碼點餐",
-    "scanTip": "微信打開掃一掃即可享用美食哦",
-    "viewStores": "查看門店",
-    "findMoreStores": "等你發現更多線下門店",
-    "explore": "找一找",
-    "pointsMall": "積分商城",
-    "pointsMallTip": "進入積分商城兌換奈雪券及周邊好禮",
-    "browse": "逛一逛",
-    "activities": "活動搶先知",
     "home": "首頁",
-    "todayRecommend": "今日推薦",
-    "hotSales": "熱銷商品"
-  },
-  "api": {
-    "message": "提示"
-  },
-  "schema": {
-    "name": "姓名",
-    "add": "新增",
-    "addSuccess": "新增成功"
-  },
-  "welcome": {
-    "message": "歡迎光臨",
-    "setting": "設置",
-    "startOrdering": "開始點餐",
-    "setShop": "設置店鋪",
-    "initSetting": "店鋪信息設定",
-    "selectShopFirst": "設置桌號前請先選擇店鋪",
-    "setTableNumber": "設置桌號",
-    "enterTableNumber": "請輸入已錄入桌號",
-    "setShopAndTableNumber": "請先設置店鋪和桌號信息",
-    "save": "保存",
-    "shopId": "店鋪ID",
-    "deskId": "桌台ID",
-    "deskNumber": "桌台號",
-    "deskPeople": "就餐人數",
-    "submit": "提交",
-    "enterShopId": "請輸入店鋪ID",
-    "enterDeskId": "請輸入桌台ID",
-    "enterDeskNumber": "請輸入桌台號",
-    "enterDeskPeople": "請輸入就餐人數",
-    "shopIdRequired": "請輸入店鋪ID",
-    "deskIdRequired": "請輸入桌台ID",
-    "deskNumberRequired": "請輸入桌台號",
-    "deskPeopleRequired": "請輸入就餐人數",
-    "shopNotExist": "店鋪不存在請重新輸入",
-    "tableNumber": "桌號"
+    "nearbyHot": "附近熱推",
+    "categories": {
+      "japanese": "日料",
+      "chinese": "中餐",
+      "western": "西餐",
+      "fastFood": "快餐",
+      "dessert": "甜品",
+      "drinks": "飲品",
+      "favorites": "我的收藏",
+      "coupons": "領券中心"
+    }
   },
   "menu": {
     "title": "點餐",
     "currentTable": "當前桌號",
     "people": "人數",
-    "peopleUnit": "人",
-    "tableNumber": "桌位號",
     "closed": "已歇業",
     "distance": "距離您",
-    "deliveryDistance": "配送距離",
-    "noDelivery": "本店不支持外賣",
     "dineIn": "自取",
     "takeOut": "外賣",
     "selectSpec": "選規格",
     "soldOut": "已售罄",
-    "shortOf": "差",
-    "startDelivery": "元起送",
     "checkout": "去結算",
-    "stock": "庫存",
     "addToCart": "加入購物車",
-    "clear": "清空",
-    "locateNearestStore": "定位最近的門店"
+    "clear": "清空"
   },
   "cart": {
     "title": "購物車",
     "ordered": "已點",
     "items": "份",
     "clear": "清空",
-    "table": "桌位",
     "checkout": "確定下單",
-    "prompt": "提示",
     "confirmClear": "確定清空購物車麼",
-    "emptyCartMessage": "請先去點餐哦",
-    "storeClosedMessage": "不在店鋪營業時間內",
-    "outOfDeliveryRangeMessage": "選中的地址不在配送範圍",
-    "loading": "加載中",
-    "backToShopping": "返回繼續購物",
-    "withoutTax": "稅前價格",
-    "withTax": "稅後價格",
+    "emptyCart": "請先去點餐哦",
     "total": "合計",
-    "amountToPay": "應付",
-    "checkoutButton": "去下單",
-    "orderedItems": "已下單",
-    "tableNumber": "桌位",
-    "userAvatar": "用戶頭像",
+    "amountToPay": "應付"
+  },
+  "order": {
+    "title": "訂單",
+    "detail": "訂單詳情",
+    "orderNumber": "訂單號",
     "orderTime": "下單時間",
-    "userNickname": "用戶暱稱",
-    "productImage": "商品圖片",
-    "productTitle": "商品名稱",
-    "productSpec": "商品規格",
-    "productPrice": "價格",
-    "productQuantity": "數量",
-    "deliveryFee": "配送費",
-    "discountAmount": "優惠金額",
-    "deductionAmount": "折扣金額",
-    "actualAmount": "實付金額",
     "paymentMethod": "支付方式",
-    "balancePayment": "餘額支付",
-    "wechatPay": "微信支付",
-    "alipay": "支付寶",
-    "remarks": "備註",
-    "confirmOrder": "確認訂單",
-    "pay": "付款"
+    "actualAmount": "實付金額",
+    "ordered": "已下單",
+    "preparing": "製作中",
+    "delivering": "配送中",
+    "completed": "已完成",
+    "cancelled": "已取消"
   },
   "mine": {
     "title": "我的",
+    "login-benefits": "登入獲取更多權益",
+    "auth-login": "授權登入",
     "user-id": "用戶ID",
-    "login-benefits": "登錄獲取更多權益",
-    "auth-login": "授權登錄",
-    "not-vip": "非會員",
+    "not-vip": "非VIP用戶",
     "view-details": "查看詳情",
     "activate-now": "立即開通",
     "coupon": "優惠券",
@@ -170,248 +81,142 @@
     "history-consumption": "歷史消費",
     "my-orders": "我的訂單",
     "my-coupons": "我的優惠券",
+    "switchIdentity": "切換身份",
+    "personalCenter": "個人中心",
     "my-services": "我的服務",
-    "orders": {
+    "order": {
       "all": "全部訂單",
       "unpaid": "待付款",
-      "processing": "進行中",
+      "in-progress": "進行中",
       "completed": "已完成",
       "refund": "退款/售後"
     },
     "coupons": {
-      "available": "可用",
+      "received": "已領取",
       "used": "已使用",
-      "expired": "已過期"
+      "expired": "已過期",
+      "center": "領券中心"
     },
     "services": {
       "address": "收貨地址",
-      "customer-service": "聯繫客服",
-      "feedback": "意見反饋",
-      "about-us": "關於我們"
+      "customerService": "聯繫客服",
+      "feedback": "意見反饋"
     }
   },
+  "login": {
+    "title": "登入",
+    "welcome": "歡迎登入",
+    "phoneLogin": "手機驗證碼",
+    "passwordLogin": "密碼登入",
+    "orLoginWith": "或使用以下方式登入",
+    "quickLogin": "一鍵登入",
+    "areaCode": "區號",
+    "phone": "手機號",
+    "password": "密碼",
+    "captcha": "驗證碼",
+    "enterPhone": "請輸入手機號",
+    "enterPassword": "請輸入密碼",
+    "enterCaptcha": "請輸入驗證碼",
+    "getCaptcha": "獲取驗證碼",
+    "loginNow": "立即登入",
+    "invalidPhone": "手機號碼格式不對",
+    "checkAgreement": "請勾選下面協議",
+    "autoCreateAccount": "未註冊的手機號驗證後自動創建賬號",
+    "selectAreaCode": "選擇區號",
+    "forgotPassword": "忘記密碼?",
+    "noAccount": "還沒有賬號?",
+    "registerNow": "立即註冊",
+    "captchaSent": "驗證碼已發送",
+    "sendFailed": "發送失敗",
+    "currentEnvironment": "當前環境",
+    "agreementPrefix": "我已閱讀並同意",
+    "userAgreement": "用戶協議",
+    "and": "與",
+    "privacyPolicy": "隱私政策",
+    "success": "登入成功"
+  },
   "merchant": {
-    "my": {
-      "title": "我的",
+    "tabbar": {
+      "home": "首頁",
+      "order": "訂單",
+      "table": "桌台",
+      "my": "我的"
+    },
+    "index": {
+      "merchantCenter": "商戶中心",
       "operating": "營業中",
-      "closed": "歇業中",
-      "businessHours": "營業時間",
-      "address": "地址",
-      "phone": "電話",
-      "shopInfo": "店鋪資料",
-      "withdrawalAccount": "提現賬號",
-      "withdrawalDetails": "提現明細",
-      "tabbar": {
-        "home": "首頁",
-        "order": "訂單",
-        "table": "桌台",
-        "my": "我的",
-        "back": "返回"
-      }
+      "closed": "停業中",
+      "todayData": "今日數據",
+      "todayOrders": "今日訂單",
+      "todayRevenue": "今日營業額",
+      "pendingOrders": "待接單",
+      "pendingMeals": "待出餐"
     },
     "order": {
       "title": "訂單列表",
-      "search": {
-        "placeholder": "請輸入訂單號/手機號",
-        "button": "查詢訂單"
-      },
-      "tabs": {
-        "takein": "自取",
-        "takeout": "外賣",
-        "desk": "堂食",
-        "due": "預約"
-      },
       "status": {
         "all": "全部",
-        "pending": "待單",
-        "waiting": "待收貨",
+        "pending": "待接單",
+        "preparing": "待出餐",
         "completed": "已完成",
-        "refunding": "待退款",
-        "unpaid": "待支付",
-        "refunded": "已退款"
-      },
-      "dueStatus": {
-        "booking": "預約中",
-        "cancelled": "已取消",
-        "completed": "已完成"
-      },
-      "orderType": {
-        "takein": "自取訂單",
-        "takeout": "外賣訂單",
-        "desk": "堂食訂單",
-        "due": "預約訂單"
-      },
-      "orderInfo": {
-        "orderTime": "下單時間",
-        "orderNumber": "訂單編號",
-        "amount": "訂單金額",
-        "refundReason": "退款原因",
-        "productDetails": "商品明細",
-        "bookingInfo": "預約信息",
-        "tableNumber": "桌號"
+        "cancelled": "已取消"
       },
-      "actions": {
-        "cancelBooking": "取消預約",
-        "print": "出單",
-        "receive": "後台收貨",
-        "refund": "同意退款",
-        "confirmRefund": "確定要退款?"
-      },
-      "detail": "訂單詳情",
-      "currentTable": "當前桌號",
-      "people": "就餐人數",
-      "paymentMethod": "支付方式",
-      "orderAmount": "訂單金額",
-      "tax": "稅金",
-      "deliveryFee": "配送費",
-      "discountAmount": "優惠金額",
-      "deductionAmount": "折扣金額",
-      "actualAmount": "實付金額",
-      "orderTime": "下單時間",
-      "orderNumber": "訂單號",
-      "serviceType": "享用方式",
-      "selfPickup": "自取",
-      "delivery": "外賣",
-      "pickupTime": "取餐時間",
-      "immediatePickup": "立即取餐",
-      "completionTime": "製作完成時間",
-      "remarks": "備註",
-      "none": "無",
-      "confirmReceived": "確認收到餐",
-      "applyRefund": "退款",
-      "ordered": "已下單",
-      "preparing": "製作中",
-      "delivering": "配送中",
-      "pleaseEat": "請就餐",
-      "delivered": "已送達",
-      "pleasePickup": "請取餐",
-      "ordersAhead": "您前面還有",
-      "waitingToPrepare": "單待製作",
-      "balancePayment": "餘額支付",
-      "insufficientBalance": "餘額不足",
-      "wechatPay": "微信支付",
-      "payNow": "立即支付",
-      "booking": "預約中",
-      "cancelled": "已取消",
-      "completed": "已完成",
-      "addOrder": "去加餐",
-      "confirmOfflinePayment": "線下確認收款",
-      "pay": "去買單",
-      "finishOrder": "完成訂單",
-      "orderFinished": "訂單已完成",
-      "addFood": "加餐",
-      "confirmSettlement": "確定結算",
-      "continueOrdering": "繼續下單",
-      "settlementConfirmation": "是否確認結算?如果確定結算請您去前台結算,如果未結算,請您繼續下單。",
-      "thankYouMessage": "感謝您的用餐,期待您的下次光臨",
-      "autoRedirectTip": "是否繼續點餐?{seconds}秒後將自動跳轉到點餐頁面",
-      "stayOnPage": "留在此頁"
-    },
-    "orderDetail": {
-      "title": "訂單詳情",
-      "userDeleted": "此用戶已註銷",
-      "tableNumber": "桌號",
-      "pickupNumber": "取餐號",
-      "deliveryMethod": "配送方式",
-      "selfPickup": "自取",
-      "takeout": "外賣",
-      "dineIn": "堂食",
-      "reservation": "預約",
-      "orderTime": "下單時間",
-      "orderNumber": "訂單編號",
-      "productDetails": "商品明細",
-      "additionalOrder": "第{count}次加菜",
-      "firstOrder": "首次點菜",
-      "reservedTable": "預約餐桌",
-      "remarks": "備註",
-      "none": "無",
-      "coupon": "抵用券",
-      "total": "共計",
-      "discounted": "已優惠",
-      "reservationTime": "預約時間",
       "actions": {
         "accept": "接單",
         "serve": "出餐",
-        "deliver": "配送",
-        "complete": "完成",
-        "cancelReservation": "取消預約",
-        "confirmReservation": "確認預約"
-      },
-      "confirmations": {
-        "acceptOrder": "確定要接單嗎?",
-        "serveFood": "確定要出餐嗎?",
-        "deliverOrder": "確定要配送嗎?",
-        "completeOrder": "確定要完成嗎?",
-        "cancelReservation": "確定要取消預約嗎?",
-        "confirmReservation": "確定要確認預約嗎?"
-      },
-      "reservationPerson": "預約人",
-      "arrivalTime": "到達時間",
-      "arrivalTimeTip": "默認為預約時間前10分鐘",
-      "arrivalTimeError": "到達時間不能晚於預約時間",
-      "arrivalTimeEarlyError": "提前到達時間不能超過1小時",
-      "reservedTableNumber": "預約桌號",
-      "reservationPhone": "預留電話",
-      "paymentMethod": "支付方式"
+        "complete": "完成"
+      }
     }
   },
-  "login": {
-    "title": "登錄",
-    "welcome": "歡迎",
-    "padMode": "平板模式",
-    "usernamePlaceholder": "請輸入用戶名",
-    "usernameTip": "請輸入您的賬號",
-    "passwordPlaceholder": "請輸入密碼",
-    "passwordTip": "密碼長度至少6位",
-    "submit": "立即登錄",
-    "usernameRequired": "請輸入用戶名",
-    "passwordRequired": "請輸入密碼",
-    "passwordLength": "密碼長度至少6位",
-    "agreementRequired": "請勾選用戶協議",
-    "success": "登錄成功",
-    "failed": "登錄失敗",
-    "agreementPrefix": "我已經閱讀並遵守",
-    "agreementAnd": "與",
-    "userAgreement": "用戶協議",
-    "userAgreementText": "《用戶協議》",
-    "privacyPolicy": "隱私政策",
-    "privacyPolicyText": "《隱私政策》",
-    "enterPhone": "請輸入手機號",
-    "autoCreateAccount": "未註冊的手機號驗證後自動創建賬號",
-    "enterCaptcha": "請輸入驗證碼",
-    "getCaptcha": "獲取驗證碼",
-    "loginNow": "立即登錄",
-    "quickLogin": "手機號快捷登錄",
-    "and": "與",
-    "checkAgreement": "請勾選下面協議",
-    "invalidPhone": "手機號碼格式不對"
+  "owner": {
+    "dashboard": "公司總覽",
+    "shops": "店鋪管理",
+    "todayData": "今日數據",
+    "totalRevenue": "總營業額",
+    "totalOrders": "總訂單",
+    "operating": "營業中",
+    "closed": "休息中",
+    "enterShop": "進入店鋪"
   },
-  "meLogin": {
-    "title": "商家登陸"
+  "admin": {
+    "dashboard": "管理後台",
+    "users": "用戶管理",
+    "merchants": "商家管理",
+    "searchUser": "搜尋用戶名/手機號",
+    "all": "全部",
+    "normalUser": "普通用戶",
+    "merchant": "商家"
   },
-  "logout": {
-    "title": "退出登錄"
+  "pos": {
+    "welcome": "POS系統",
+    "tables": "桌位管理",
+    "orders": "訂單管理",
+    "available": "空閒",
+    "occupied": "就餐中",
+    "openTable": "開台",
+    "checkout": "結賬",
+    "acceptOrder": "接單",
+    "completeOrder": "完成"
   },
-  "pad": {
-    "title": "pad點餐",
-    "menu": "pad菜單"
+  "buffet": {
+    "title": "放題模式",
+    "selectPlan": "選擇放題方案",
+    "remaining": "剩餘時間",
+    "pause": "暫停",
+    "resume": "繼續",
+    "endEarly": "提前結束",
+    "timeUp": "放題時間已到!感謝光臨",
+    "cannotOrder": "當前無法點餐",
+    "confirmOrder": "確認點餐",
+    "orderSuccess": "點餐成功!"
   },
-  "dueDetail": {
-    "title": "預約詳情",
-    "reservation": "預約信息",
-    "arrivalTime": "到達時間",
-    "arrivalTimeTip": "默認為預約時間前10分鐘",
-    "arrivalTimeError": "到達時間不能晚於預約時間",
-    "arrivalTimeEarlyError": "提前到達時間不能超過1小時",
-    "name": "姓名",
-    "enterRealName": "請輸入真實姓名",
-    "phoneNumber": "手機號",
-    "enterPhone": "請輸入手機號",
-    "invalidPhoneFormat": "請輸入10-11位有效手機號",
-    "captcha": "驗證碼",
-    "enterCaptcha": "請輸入驗證碼",
-    "getCaptcha": "獲取驗證碼",
-    "submit": "提交",
-    "confirmReservation": "請確認預約時間"
+  "role": {
+    "customer": "顧客",
+    "staff": "員工",
+    "manager": "店長",
+    "owner": "老板",
+    "admin": "管理員",
+    "switchTitle": "切換身份",
+    "currentRole": "當前身份"
   }
 }

+ 115 - 13
src/router/guards.ts

@@ -2,33 +2,135 @@
  * 路由守卫
  */
 
-import { useUserStore } from '@/store'
+import type { Router } from 'vue-router'
+import { useUserStore, type UserRole } from '@/store/modules/user'
+import { useAppStore } from '@/store/modules/app'
 import { showToast } from 'vant'
+import i18n from '@/locale'
+
+const { t } = i18n.global
 
 /**
  * 前置守卫
  */
-export function setupRouterGuards(router) {
-  router.beforeEach((to, from, next) => {
+export function setupRouterGuards(router: Router) {
+  router.beforeEach(async (to, from, next) => {
     const userStore = useUserStore()
+    const appStore = useAppStore()
+
+    // 0. 模式守卫
+    if (to.path === '/index') {
+      // 进入首页,切换为平台模式
+      if (!appStore.isPlatformMode) {
+        appStore.enterPlatformMode()
+      }
+    } else if (to.path === '/menu') {
+      // 进入菜单页,检查是否有店铺上下文
+      const queryShopId = to.query.shopId as string
+      
+      // 如果URL有shopId,优先使用(处理直接访问)
+      if (queryShopId && (!appStore.currentShop || appStore.currentShop.id !== queryShopId)) {
+        // TODO: 这里应该从API加载店铺信息
+        // 暂时只设置简单的上下文
+        appStore.enterShopMode({ id: queryShopId, name: '加载中...', status: 'operating', companyId: '', address: '' })
+      } 
+      // 如果没有context且没有query参数,重定向回首页
+      else if (!appStore.currentShop) {
+        showToast('请先选择店铺')
+        next('/index')
+        return
+      }
+    }
+
+    // 1. 检查是否需要登录
+    // guest 用户允许访问菜单和购物车,但不能访问需要登录的页面
+    if (to.meta.requiresAuth) {
+      // 检查是否登录或有有效会话
+      if (!userStore.isLogin && !userStore.isGuest) {
+        
+        // 特殊处理:如果是Shop Mode下的购物车/结算,提示登录
+        if (appStore.isShopMode && ['/cart', '/payment'].some(p => to.path.startsWith(p))) {
+           // 允许访问,但在页面内处理登录逻辑
+           // 或者跳转登录
+           showToast(t('common.pleaseLogin'))
+           next({
+             path: '/login',
+             query: { redirect: to.fullPath }
+           })
+           return
+        }
+
+        showToast(t('common.pleaseLogin'))
+        next({
+          path: '/login',
+          query: { redirect: to.fullPath }
+        })
+        return
+      }
+
+      // guest 用户尝试访问需要登录的页面
+      if (userStore.isGuest && to.meta.requiresAuth) {
+        // 允许 guest 访问部分页面(订单创建相关)
+        const guestAllowedPaths = ['/cart', '/payment', '/order/detail']
+        if (!guestAllowedPaths.some(p => to.path.startsWith(p))) {
+          showToast(t('common.pleaseLogin'))
+          next({
+            path: '/login',
+            query: { redirect: to.fullPath }
+          })
+          return
+        }
+      }
+    }
+
+    // 2. 检查角色要求
+    if (to.meta.requiresRole) {
+      const requiredRoles = Array.isArray(to.meta.requiresRole)
+        ? to.meta.requiresRole as UserRole[]
+        : [to.meta.requiresRole] as UserRole[]
+
+      const hasRole = requiredRoles.some(role =>
+        userStore.availableRoles.includes(role)
+      )
+
+      if (!hasRole) {
+        showToast(t('role.noPermission'))
+        next(false)
+        return
+      }
+
+      // 3. 自动切换角色(如果当前角色不匹配)
+      if (!requiredRoles.includes(userStore.currentRole)) {
+        // 如果当前角色不匹配,但用户拥有所需角色,自动切换
+        const targetRole = requiredRoles.find(role =>
+          userStore.availableRoles.includes(role)
+        )
+        if (targetRole) {
+          userStore.setCurrentRole(targetRole)
+        }
+      }
+    }
 
-    // 检查是否需要登录
-    if (to.meta.requiresAuth && !userStore.isLogin) {
-      showToast('请先登录')
-      next({
-        path: '/login',
-        query: { redirect: to.fullPath }
-      })
-      return
+    // 4. 检查权限(可选)
+    if (to.meta.requiresPermission) {
+      const permission = to.meta.requiresPermission as string
+      if (!userStore.can(permission)) {
+        showToast(t('role.noPermission'))
+        next(false)
+        return
+      }
     }
 
     next()
   })
 
   router.afterEach((to) => {
-    // 设置页面标题
+    // 设置页面标题(支持国际化)
     if (to.meta.title) {
-      document.title = to.meta.title
+      const title = typeof to.meta.title === 'string'
+        ? t(to.meta.title)
+        : to.meta.title
+      document.title = typeof title === 'string' ? title : 'FastEat'
     }
   })
 }

+ 189 - 0
src/router/routes.ts

@@ -16,6 +16,17 @@ export default [
       keepAlive: true
     }
   },
+
+  // 扫码入口(无需登录)
+  {
+    path: '/scan',
+    name: 'Scan',
+    component: () => import('@/views/scan/index.vue'),
+    meta: {
+      title: '扫码点餐'
+    }
+  },
+
   {
     path: '/menu',
     name: 'Menu',
@@ -101,5 +112,183 @@ export default [
       title: 'payment.title',
       requiresAuth: true
     }
+  },
+
+  // 老板端路由 (owner)
+  {
+    path: '/owner/dashboard',
+    name: 'OwnerDashboard',
+    component: () => import('@/views/owner/dashboard.vue'),
+    meta: {
+      title: '公司总览',
+      requiresAuth: true,
+      requiresRole: 'owner'
+    }
+  },
+  {
+    path: '/owner/shops',
+    name: 'OwnerShops',
+    component: () => import('@/views/owner/shops.vue'),
+    meta: {
+      title: '店铺管理',
+      requiresAuth: true,
+      requiresRole: 'owner'
+    }
+  },
+  {
+    path: '/owner/reports',
+    name: 'OwnerReports',
+    component: () => import('@/views/owner/reports.vue'),
+    meta: {
+      title: '综合报表',
+      requiresAuth: true,
+      requiresRole: 'owner'
+    }
+  },
+
+  // 店铺管理路由 (owner/manager)
+  {
+    path: '/shop/dashboard',
+    name: 'ShopDashboard',
+    component: () => import('@/views/merchant/dashboard.vue'),
+    meta: {
+      title: '店铺总览',
+      requiresAuth: true,
+      requiresRole: ['owner', 'manager']
+    }
+  },
+  {
+    path: '/shop/:id/dashboard',
+    name: 'ShopDetailDashboard',
+    component: () => import('@/views/merchant/dashboard.vue'),
+    meta: {
+      title: '店铺详情',
+      requiresAuth: true,
+      requiresRole: ['owner', 'manager']
+    }
+  },
+
+  // 商家路由(兼容)
+  {
+    path: '/merchant/dashboard',
+    name: 'MerchantDashboard',
+    component: () => import('@/views/merchant/dashboard.vue'),
+    meta: {
+      title: '商家仪表盘',
+      requiresAuth: true,
+      requiresRole: ['owner', 'manager']
+    }
+  },
+  {
+    path: '/merchant/orders',
+    name: 'MerchantOrders',
+    component: () => import('@/views/merchant/orders.vue'),
+    meta: {
+      title: '订单管理',
+      requiresAuth: true,
+      requiresRole: ['owner', 'manager']
+    }
+  },
+  {
+    path: '/merchant/buffet-plans',
+    name: 'MerchantBuffetPlans',
+    component: () => import('@/views/merchant/buffet-plans.vue'),
+    meta: {
+      title: '放题方案管理',
+      requiresAuth: true,
+      requiresRole: ['owner', 'manager']
+    }
+  },
+
+  // POS/员工端路由
+  {
+    path: '/pos/welcome',
+    name: 'PosWelcome',
+    component: () => import('@/views/pos/welcome.vue'),
+    meta: {
+      title: 'POS系统',
+      requiresAuth: true,
+      requiresRole: ['staff', 'manager', 'owner', 'admin']
+    }
+  },
+  {
+    path: '/pos/tables',
+    name: 'PosTables',
+    component: () => import('@/views/pos/tables.vue'),
+    meta: {
+      title: '桌位管理',
+      requiresAuth: true,
+      requiresRole: ['staff', 'manager', 'owner', 'admin']
+    }
+  },
+  {
+    path: '/pos/orders',
+    name: 'PosOrders',
+    component: () => import('@/views/pos/orders.vue'),
+    meta: {
+      title: '订单管理',
+      requiresAuth: true,
+      requiresRole: ['staff', 'manager', 'owner', 'admin']
+    }
+  },
+  {
+    path: '/pos/orders/:id',
+    name: 'PosOrderDetail',
+    component: () => import('@/views/pos/order-detail.vue'),
+    meta: {
+      title: '订单详情',
+      requiresAuth: true,
+      requiresRole: ['staff', 'manager', 'owner', 'admin']
+    }
+  },
+
+  // 放题路由
+  {
+    path: '/buffet/select',
+    name: 'BuffetSelect',
+    component: () => import('@/views/buffet/select.vue'),
+    meta: {
+      title: '选择放题方案'
+    }
+  },
+  {
+    path: '/buffet/menu',
+    name: 'BuffetMenu',
+    component: () => import('@/views/buffet/menu.vue'),
+    meta: {
+      title: '放题点餐'
+    }
+  },
+
+  // 管理员路由
+  {
+    path: '/admin/dashboard',
+    name: 'AdminDashboard',
+    component: () => import('@/views/admin/dashboard.vue'),
+    meta: {
+      title: '管理后台',
+      requiresAuth: true,
+      requiresRole: 'admin'
+    }
+  },
+  {
+    path: '/admin/users',
+    name: 'AdminUsers',
+    component: () => import('@/views/admin/users.vue'),
+    meta: {
+      title: '用户管理',
+      requiresAuth: true,
+      requiresRole: 'admin'
+    }
+  },
+  {
+    path: '/admin/merchants',
+    name: 'AdminMerchants',
+    component: () => import('@/views/admin/merchants.vue'),
+    meta: {
+      title: '商家管理',
+      requiresAuth: true,
+      requiresRole: 'admin'
+    }
   }
 ]

+ 5 - 0
src/store/index.ts

@@ -11,3 +11,8 @@ export { useAppStore } from './modules/app'
 export { useUserStore } from './modules/user'
 export { useCartStore } from './modules/cart'
 export { useOrderStore } from './modules/order'
+export { useMerchantStore } from './modules/merchant'
+export { usePosStore } from './modules/pos'
+export { useAdminStore } from './modules/admin'
+export { useCompanyStore } from './modules/company'
+export { useBuffetStore } from './modules/buffet'

+ 55 - 0
src/store/modules/admin.ts

@@ -0,0 +1,55 @@
+import { defineStore } from 'pinia'
+
+export const useAdminStore = defineStore('admin', {
+  state: () => ({
+    stats: {
+      totalUsers: 0,
+      totalMerchants: 0,
+      todayOrders: 0,
+      totalRevenue: 0
+    },
+    users: [] as any[],
+    merchants: [] as any[]
+  }),
+
+  actions: {
+    async loadDashboardStats() {
+      // Mock数据
+      this.stats = {
+        totalUsers: 15280,
+        totalMerchants: 328,
+        todayOrders: 1568,
+        totalRevenue: 2856000
+      }
+    },
+
+    async loadUsers(filters: any, page: number) {
+      // Mock数据
+      this.users = [
+        {
+          id: '1',
+          nickname: '测试用户1',
+          phone: '138****8888',
+          status: 'normal',
+          createTime: '2024-01-01'
+        }
+      ]
+      return { list: this.users, total: 1 }
+    },
+
+    async loadMerchants(filters: any, page: number) {
+      // Mock数据
+      this.merchants = [
+        {
+          id: '1',
+          name: '测试餐厅',
+          phone: '138****8888',
+          status: 'normal',
+          auditStatus: 'approved',
+          createTime: '2024-01-01'
+        }
+      ]
+      return { list: this.merchants, total: 1 }
+    }
+  }
+})

+ 96 - 47
src/store/modules/app.ts

@@ -2,26 +2,52 @@ import { defineStore } from 'pinia'
 import { storage } from '@/utils/storage'
 import { setDayjsLocale } from '@/utils/format'
 import { setLanguage } from '@/locale'
+import type { Shop } from './company'
+
+export type AppMode = 'platform' | 'shop' | 'table'
+
+export interface TableInfo {
+  id: string
+  code: string
+  name: string
+}
 
 export const useAppStore = defineStore('app', {
   state: () => ({
-    lang: storage.get('language', 'ja'),
-    store: {},
-    storeInfo: {},
-    desk: {},
+    lang: storage.get<string>('language', 'ja') as string,
+    
+    // 当前应用模式
+    mode: 'platform' as AppMode,
+
+    // 当前上下文信息
+    currentShop: null as Shop | null,
+    currentTable: null as TableInfo | null,
+    
+    // 兼容旧字段 (后续逐渐废弃)
+    store: {} as Record<string, any>,
+    storeInfo: {} as Record<string, any>,
+    desk: {} as Record<string, any>,
     isScan: false,
-    location: {},
-    orderType: 'takein' // takein, takeout, delivery
+    location: {} as { latitude?: number; longitude?: number },
+    orderType: 'takein' as 'takein' | 'takeout' | 'delivery'
   }),
 
   getters: {
-    // 是否扫码进入
-    isScanned: (state) => state.isScan,
-    // 当前店铺
-    currentStore: (state) => state.store,
-    // 订单类型文本
+    // 模式判断
+    isPlatformMode: (state) => state.mode === 'platform',
+    isShopMode: (state) => state.mode === 'shop',
+    isTableMode: (state) => state.mode === 'table',
+
+    // 获取当前ID
+    shopId: (state) => state.currentShop?.id,
+    tableCode: (state) => state.currentTable?.code,
+
+    // 兼容 getters
+    isScanned: (state) => state.isScan || state.mode === 'table',
+    currentStore: (state) => state.currentShop || state.store,
+    
     orderTypeText: (state) => {
-      const map = {
+      const map: Record<string, string> = {
         takein: '堂食',
         takeout: '外带',
         delivery: '配送'
@@ -31,10 +57,7 @@ export const useAppStore = defineStore('app', {
   },
 
   actions: {
-    /**
-     * 设置语言
-     */
-    setLang(lang) {
+    setLang(lang: string) {
       this.lang = lang
       storage.set('language', lang)
       setLanguage(lang)
@@ -42,57 +65,83 @@ export const useAppStore = defineStore('app', {
     },
 
     /**
-     * 设置店铺信息
+     * 进入平台模式
      */
-    setStore(store) {
-      this.store = store
+    enterPlatformMode() {
+      this.mode = 'platform'
+      this.currentShop = null
+      this.currentTable = null
+      // 兼容
+      this.store = {}
+      this.desk = {}
+      this.isScan = false
+      this.orderType = 'delivery' // 默认外卖/列表
     },
 
     /**
-     * 设置店铺详细信息
+     * 进入店铺模式 (外卖/预约)
      */
-    setStoreInfo(storeInfo) {
-      this.storeInfo = storeInfo
+    enterShopMode(shop: Shop) {
+      this.mode = 'shop'
+      this.currentShop = shop
+      this.currentTable = null
+      // 兼容
+      this.store = shop
+      this.desk = {}
+      this.isScan = false
+      this.orderType = 'takeout' // 默认外带
     },
 
     /**
-     * 设置桌台信息
+     * 进入桌位模式 (堂食)
      */
-    setDesk(desk) {
-      this.desk = desk
+    enterTableMode(shop: Shop, table: TableInfo) {
+      this.mode = 'table'
+      this.currentShop = shop
+      this.currentTable = table
+      // 兼容
+      this.store = shop
+      this.desk = table
       this.isScan = true
+      this.orderType = 'takein'
     },
 
-    /**
-     * 清除桌台信息
-     */
+    // 兼容方法
+    setStore(store: any) {
+      this.store = store
+      if (store && store.id) {
+        this.currentShop = store
+        if (this.mode === 'platform') this.mode = 'shop'
+      }
+    },
+    setDesk(desk: any) {
+      this.desk = desk
+      this.isScan = true
+      if (desk && desk.code) {
+        this.currentTable = desk
+        this.mode = 'table'
+      }
+    },
     clearDesk() {
       this.desk = {}
       this.isScan = false
+      this.currentTable = null
+      if (this.currentShop) {
+        this.mode = 'shop'
+      } else {
+        this.mode = 'platform'
+      }
     },
-
-    /**
-     * 设置位置信息
-     */
-    setLocation(location) {
-      this.location = location
-    },
-
-    /**
-     * 设置订单类型
-     */
-    setOrderType(type) {
+    setOrderType(type: 'takein' | 'takeout' | 'delivery') {
       this.orderType = type
+    },
+    setLocation(location: any) {
+      this.location = location
     }
   },
 
   persist: {
-    enabled: true,
-    strategies: [
-      {
-        storage: localStorage,
-        paths: ['lang', 'store', 'desk', 'isScan', 'orderType']
-      }
-    ]
+    storage: localStorage,
+    paths: ['lang', 'mode', 'currentShop', 'currentTable', 'store', 'desk', 'isScan', 'orderType']
   }
 })

+ 277 - 0
src/store/modules/buffet.ts

@@ -0,0 +1,277 @@
+import { defineStore } from 'pinia'
+
+/**
+ * 放题方案
+ */
+export interface BuffetPlan {
+  id: string
+  name: string
+  duration: number         // 时长(分钟)
+  price: number            // 价格
+  description?: string
+  menuCategories?: string[] // 允许的菜品分类
+  maxOrders?: number       // 最大点单次数(0=不限制)
+  reminderMinutes: number[] // 提醒时间点(剩余分钟)
+  status: 'active' | 'inactive'
+}
+
+/**
+ * 放题会话
+ */
+export interface BuffetSession {
+  id: string
+  planId: string
+  planName: string
+  tableId: string
+  tableName: string
+  startTime: number
+  endTime: number
+  duration: number         // 总时长(分钟)
+  remainingTime: number    // 剩余时间(秒)
+  status: 'active' | 'paused' | 'expired' | 'completed'
+  orderCount: number       // 已点单次数
+  maxOrders?: number
+  reminders: number[]      // 已触发的提醒(剩余分钟)
+}
+
+export const useBuffetStore = defineStore('buffet', {
+  state: () => ({
+    // 放题方案列表(商家配置)
+    plans: [] as BuffetPlan[],
+
+    // 当前放题会话(顾客端)
+    currentSession: null as BuffetSession | null,
+
+    // 计时器ID
+    timerId: null as number | null,
+
+    // 是否显示计时器
+    showTimer: false
+  }),
+
+  getters: {
+    /**
+     * 激活的放题方案
+     */
+    activePlans: (state): BuffetPlan[] => {
+      return state.plans.filter(p => p.status === 'active')
+    },
+
+    /**
+     * 是否在放题模式
+     */
+    isBuffetMode: (state): boolean => {
+      return state.currentSession !== null &&
+             state.currentSession.status === 'active'
+    },
+
+    /**
+     * 剩余时间(格式化)
+     */
+    remainingTimeFormatted: (state): string => {
+      if (!state.currentSession) return '00:00'
+      const seconds = state.currentSession.remainingTime
+      const minutes = Math.floor(seconds / 60)
+      const secs = seconds % 60
+      return `${String(minutes).padStart(2, '0')}:${String(secs).padStart(2, '0')}`
+    },
+
+    /**
+     * 是否快过期(最后10分钟)
+     */
+    isExpiringSoon: (state): boolean => {
+      if (!state.currentSession) return false
+      return state.currentSession.remainingTime <= 10 * 60
+    },
+
+    /**
+     * 是否可以继续点餐
+     */
+    canOrder: (state): boolean => {
+      const session = state.currentSession
+      if (!session || session.status !== 'active') return false
+      if (session.remainingTime <= 0) return false
+      if (session.maxOrders && session.orderCount >= session.maxOrders) return false
+      return true
+    }
+  },
+
+  actions: {
+    /**
+     * 加载放题方案列表
+     */
+    async loadPlans() {
+      // TODO: 调用API
+      this.plans = [
+        {
+          id: 'plan_1',
+          name: '90分钟畅饮畅食',
+          duration: 90,
+          price: 2980,
+          description: '90分钟内无限点餐,含酒水',
+          reminderMinutes: [30, 10, 5],
+          status: 'active'
+        },
+        {
+          id: 'plan_2',
+          name: '120分钟豪华套餐',
+          duration: 120,
+          price: 3980,
+          description: '120分钟豪华放题',
+          reminderMinutes: [60, 30, 10],
+          status: 'active'
+        }
+      ]
+    },
+
+    /**
+     * 开始放题会话
+     */
+    startSession(plan: BuffetPlan, tableId: string, tableName: string) {
+      const now = Date.now()
+      const durationMs = plan.duration * 60 * 1000
+
+      this.currentSession = {
+        id: `session_${Date.now()}`,
+        planId: plan.id,
+        planName: plan.name,
+        tableId,
+        tableName,
+        startTime: now,
+        endTime: now + durationMs,
+        duration: plan.duration,
+        remainingTime: plan.duration * 60,
+        status: 'active',
+        orderCount: 0,
+        maxOrders: plan.maxOrders,
+        reminders: []
+      }
+
+      this.showTimer = true
+      this.startTimer()
+    },
+
+    /**
+     * 启动计时器
+     */
+    startTimer() {
+      this.stopTimer()
+
+      this.timerId = window.setInterval(() => {
+        if (!this.currentSession || this.currentSession.status !== 'active') {
+          this.stopTimer()
+          return
+        }
+
+        this.currentSession.remainingTime--
+
+        // 检查是否到达提醒时间
+        const remainingMinutes = Math.floor(this.currentSession.remainingTime / 60)
+        const plan = this.plans.find(p => p.id === this.currentSession!.planId)
+
+        if (plan && plan.reminderMinutes.includes(remainingMinutes)) {
+          if (!this.currentSession.reminders.includes(remainingMinutes)) {
+            this.currentSession.reminders.push(remainingMinutes)
+            this.triggerReminder(remainingMinutes)
+          }
+        }
+
+        // 检查是否超时
+        if (this.currentSession.remainingTime <= 0) {
+          this.expireSession()
+        }
+      }, 1000)
+    },
+
+    /**
+     * 停止计时器
+     */
+    stopTimer() {
+      if (this.timerId) {
+        clearInterval(this.timerId)
+        this.timerId = null
+      }
+    },
+
+    /**
+     * 暂停会话
+     */
+    pauseSession() {
+      if (this.currentSession && this.currentSession.status === 'active') {
+        this.currentSession.status = 'paused'
+        this.stopTimer()
+      }
+    },
+
+    /**
+     * 恢复会话
+     */
+    resumeSession() {
+      if (this.currentSession && this.currentSession.status === 'paused') {
+        this.currentSession.status = 'active'
+        this.startTimer()
+      }
+    },
+
+    /**
+     * 会话过期
+     */
+    expireSession() {
+      if (this.currentSession) {
+        this.currentSession.status = 'expired'
+        this.currentSession.remainingTime = 0
+        this.stopTimer()
+        // TODO: 触发过期通知
+      }
+    },
+
+    /**
+     * 完成会话(提前结束)
+     */
+    completeSession() {
+      if (this.currentSession) {
+        this.currentSession.status = 'completed'
+        this.stopTimer()
+        this.showTimer = false
+      }
+    },
+
+    /**
+     * 增加点单次数
+     */
+    incrementOrderCount() {
+      if (this.currentSession) {
+        this.currentSession.orderCount++
+      }
+    },
+
+    /**
+     * 触发提醒
+     */
+    triggerReminder(remainingMinutes: number) {
+      // TODO: 显示提醒通知
+      console.log(`放题提醒: 还剩 ${remainingMinutes} 分钟`)
+    },
+
+    /**
+     * 清除会话
+     */
+    clearSession() {
+      this.stopTimer()
+      this.currentSession = null
+      this.showTimer = false
+    },
+
+    /**
+     * 设置计时器显示状态
+     */
+    setTimerVisibility(visible: boolean) {
+      this.showTimer = visible
+    }
+  },
+
+  persist: {
+    storage: localStorage,
+    paths: ['currentSession']
+  }
+})

+ 305 - 0
src/store/modules/company.ts

@@ -0,0 +1,305 @@
+import { defineStore } from 'pinia'
+
+/**
+ * 公司状态
+ */
+export type CompanyStatus = 'active' | 'suspended' | 'pending'
+
+/**
+ * 店铺状态
+ */
+export type ShopStatus = 'operating' | 'closed' | 'preparing'
+
+/**
+ * 公司实体
+ */
+export interface Company {
+  id: string
+  name: string
+  ownerId: string         // 老板用户ID
+  ownerName?: string
+  logo?: string
+  contactPhone?: string
+  contactEmail?: string
+  address?: string
+  createdAt: number
+  status: CompanyStatus
+  shopCount?: number      // 店铺数量
+}
+
+/**
+ * 店铺实体
+ */
+export interface Shop {
+  id: string
+  companyId: string       // 所属公司
+  name: string
+  address: string
+  phone?: string
+  logo?: string
+  businessHours?: string
+  status: ShopStatus
+  latitude?: number
+  longitude?: number
+  createdAt?: number
+}
+
+/**
+ * 多店铺汇总数据
+ */
+export interface CompanySummary {
+  totalRevenue: number           // 总营业额
+  totalOrders: number            // 总订单数
+  totalCustomers: number         // 总顾客数
+  comparedToYesterday: number    // 较昨日增长%
+  shopSummaries: ShopSummary[]
+}
+
+/**
+ * 单店汇总数据
+ */
+export interface ShopSummary {
+  shopId: string
+  shopName: string
+  todayRevenue: number
+  todayOrders: number
+  occupiedTables: number
+  totalTables: number
+  status: ShopStatus
+}
+
+export const useCompanyStore = defineStore('company', {
+  state: () => ({
+    // 当前用户的公司(owner角色)
+    currentCompany: null as Company | null,
+    
+    // 公司下的店铺列表
+    shops: [] as Shop[],
+    
+    // 当前选中的店铺
+    selectedShopId: '' as string,
+    
+    // 多店铺汇总数据
+    companySummary: null as CompanySummary | null,
+    
+    // 加载状态
+    loading: false,
+    
+    // Admin: 所有公司列表
+    allCompanies: [] as Company[]
+  }),
+
+  getters: {
+    /**
+     * 当前选中的店铺
+     */
+    selectedShop: (state): Shop | null => {
+      if (!state.selectedShopId) return state.shops[0] || null
+      return state.shops.find(s => s.id === state.selectedShopId) || null
+    },
+
+    /**
+     * 店铺数量
+     */
+    shopCount: (state): number => state.shops.length,
+
+    /**
+     * 营业中的店铺
+     */
+    operatingShops: (state): Shop[] => {
+      return state.shops.filter(s => s.status === 'operating')
+    },
+
+    /**
+     * 是否有多家店铺
+     */
+    hasMultipleShops: (state): boolean => state.shops.length > 1
+  },
+
+  actions: {
+    /**
+     * 设置当前公司
+     */
+    setCurrentCompany(company: Company) {
+      this.currentCompany = company
+    },
+
+    /**
+     * 设置店铺列表
+     */
+    setShops(shops: Shop[]) {
+      this.shops = shops
+      // 默认选中第一家店铺
+      if (shops.length > 0 && !this.selectedShopId) {
+        this.selectedShopId = shops[0]?.id ?? ''
+      }
+    },
+
+    /**
+     * 选择店铺
+     */
+    selectShop(shopId: string) {
+      if (this.shops.some(s => s.id === shopId)) {
+        this.selectedShopId = shopId
+      }
+    },
+
+    /**
+     * 设置汇总数据
+     */
+    setCompanySummary(summary: CompanySummary) {
+      this.companySummary = summary
+    },
+
+    /**
+     * 加载公司及店铺数据
+     */
+    async loadCompanyData(companyId?: string) {
+      this.loading = true
+      try {
+        // TODO: 调用真实API
+        // const company = await getCompanyInfo(companyId)
+        // const shops = await getCompanyShops(companyId)
+        
+        // 模拟数据
+        this.currentCompany = {
+          id: companyId || 'company_1',
+          name: 'XXX餐饮集团',
+          ownerId: 'owner_1',
+          ownerName: '张老板',
+          status: 'active',
+          createdAt: Date.now(),
+          shopCount: 3
+        }
+
+        this.shops = [
+          {
+            id: 'shop_1',
+            companyId: 'company_1',
+            name: '渋谷店',
+            address: '東京都渋谷区...',
+            status: 'operating'
+          },
+          {
+            id: 'shop_2',
+            companyId: 'company_1',
+            name: '新宿店',
+            address: '東京都新宿区...',
+            status: 'operating'
+          },
+          {
+            id: 'shop_3',
+            companyId: 'company_1',
+            name: '池袋店',
+            address: '東京都豊島区...',
+            status: 'closed'
+          }
+        ]
+
+        if (!this.selectedShopId) {
+          this.selectedShopId = this.shops[0]?.id || ''
+        }
+      } finally {
+        this.loading = false
+      }
+    },
+
+    /**
+     * 加载多店铺汇总报表
+     */
+    async loadCompanySummary() {
+      this.loading = true
+      try {
+        // TODO: 调用真实API
+        // const summary = await getCompanySummary()
+        
+        // 模拟数据
+        this.companySummary = {
+          totalRevenue: 12580,
+          totalOrders: 156,
+          totalCustomers: 89,
+          comparedToYesterday: 12.5,
+          shopSummaries: [
+            {
+              shopId: 'shop_1',
+              shopName: '渋谷店',
+              todayRevenue: 5280,
+              todayOrders: 68,
+              occupiedTables: 8,
+              totalTables: 15,
+              status: 'operating'
+            },
+            {
+              shopId: 'shop_2',
+              shopName: '新宿店',
+              todayRevenue: 4300,
+              todayOrders: 55,
+              occupiedTables: 6,
+              totalTables: 12,
+              status: 'operating'
+            },
+            {
+              shopId: 'shop_3',
+              shopName: '池袋店',
+              todayRevenue: 3000,
+              todayOrders: 33,
+              occupiedTables: 0,
+              totalTables: 10,
+              status: 'closed'
+            }
+          ]
+        }
+      } finally {
+        this.loading = false
+      }
+    },
+
+    /**
+     * Admin: 加载所有公司
+     */
+    async loadAllCompanies() {
+      this.loading = true
+      try {
+        // TODO: 调用真实API
+        this.allCompanies = [
+          {
+            id: 'company_1',
+            name: 'XXX餐饮集团',
+            ownerId: 'owner_1',
+            ownerName: '张老板',
+            status: 'active',
+            createdAt: Date.now() - 86400000 * 30,
+            shopCount: 3
+          },
+          {
+            id: 'company_2',
+            name: 'YYY美食连锁',
+            ownerId: 'owner_2',
+            ownerName: '李老板',
+            status: 'active',
+            createdAt: Date.now() - 86400000 * 60,
+            shopCount: 5
+          }
+        ]
+      } finally {
+        this.loading = false
+      }
+    },
+
+    /**
+     * 清除数据
+     */
+    clear() {
+      this.currentCompany = null
+      this.shops = []
+      this.selectedShopId = ''
+      this.companySummary = null
+      this.allCompanies = []
+    }
+  },
+
+  persist: {
+    storage: localStorage,
+    paths: ['selectedShopId']
+  }
+})

+ 80 - 0
src/store/modules/merchant.ts

@@ -0,0 +1,80 @@
+import { defineStore } from 'pinia'
+
+export const useMerchantStore = defineStore('merchant', {
+  state: () => ({
+    shopInfo: {
+      name: '',
+      isOpen: true,
+      hours: ''
+    },
+    todayStats: {
+      revenue: 0,
+      orderCount: 0,
+      takeoutCount: 0,
+      dineInCount: 0
+    },
+    orderStats: {
+      pending: 0,
+      preparing: 0,
+      delivering: 0,
+      completed: 0
+    },
+    pendingOrderCount: 0,
+    orders: []
+  }),
+
+  getters: {
+    totalPendingOrders: (state) => {
+      return state.orderStats.pending + state.orderStats.preparing
+    }
+  },
+
+  actions: {
+    async loadDashboardData() {
+      // Mock数据
+      this.shopInfo = {
+        name: '测试餐厅',
+        isOpen: true,
+        hours: '10:00-22:00'
+      }
+      this.todayStats = {
+        revenue: 15680,
+        orderCount: 128,
+        takeoutCount: 56,
+        dineInCount: 72
+      }
+      this.orderStats = {
+        pending: 5,
+        preparing: 8,
+        delivering: 3,
+        completed: 112
+      }
+      this.pendingOrderCount = 5
+    },
+
+    async loadOrders(params: any) {
+      // Mock数据
+      this.orders = [
+        {
+          id: '1',
+          orderNo: 'ORD20240101001',
+          createTime: '2024-01-01 12:30',
+          amount: 128.5,
+          status: 'pending',
+          items: [{ name: '招牌拉面', quantity: 2 }]
+        }
+      ]
+      return { list: this.orders, total: 1 }
+    }
+  },
+
+  persist: {
+    enabled: true,
+    strategies: [
+      {
+        storage: localStorage,
+        paths: ['shopInfo']
+      }
+    ]
+  }
+})

+ 75 - 0
src/store/modules/pos.ts

@@ -0,0 +1,75 @@
+import { defineStore } from 'pinia'
+
+export const usePosStore = defineStore('pos', {
+  state: () => ({
+    deskInfo: {
+      shopId: '',
+      deskId: '',
+      deskNumber: '',
+      peopleCount: 0,
+      isActive: false
+    },
+    cart: [] as any[],
+    orders: [] as any[]
+  }),
+
+  getters: {
+    cartTotal: (state) => {
+      return state.cart.reduce((sum, item) => sum + item.price * item.quantity, 0)
+    },
+    cartCount: (state) => {
+      return state.cart.reduce((sum, item) => sum + item.quantity, 0)
+    }
+  },
+
+  actions: {
+    initDesk(deskInfo: any) {
+      this.deskInfo = { ...deskInfo, isActive: true }
+    },
+
+    addToCart(product: any) {
+      const existItem = this.cart.find(item => item.id === product.id)
+      if (existItem) {
+        existItem.quantity++
+      } else {
+        this.cart.push({ ...product, quantity: 1 })
+      }
+    },
+
+    updateCartItemQuantity(productId: string, quantity: number) {
+      const item = this.cart.find(i => i.id === productId)
+      if (item) {
+        if (quantity <= 0) {
+          this.cart = this.cart.filter(i => i.id !== productId)
+        } else {
+          item.quantity = quantity
+        }
+      }
+    },
+
+    clearCart() {
+      this.cart = []
+    },
+
+    async createOrder(orderData: any) {
+      const order = {
+        id: Date.now().toString(),
+        ...orderData,
+        createTime: new Date().toISOString()
+      }
+      this.orders.unshift(order)
+      this.clearCart()
+      return order
+    }
+  },
+
+  persist: {
+    enabled: true,
+    strategies: [
+      {
+        storage: localStorage,
+        paths: ['deskInfo', 'cart']
+      }
+    ]
+  }
+})

+ 243 - 86
src/store/modules/user.ts

@@ -1,31 +1,107 @@
 import { defineStore } from 'pinia'
 import { storage } from '@/utils/storage'
 
+/**
+ * 用户角色类型
+ * - customer: 普通顾客(已登录)
+ * - guest: 扫码访客(未登录,绑定桌位)
+ * - staff: 员工/服务员
+ * - manager: 店长
+ * - owner: 老板/店主
+ * - admin: 系统管理员
+ */
+export type UserRole = 'customer' | 'guest' | 'staff' | 'manager' | 'owner' | 'admin'
+
+/**
+ * 角色特定数据接口
+ */
+export interface RoleData {
+  staff: {
+    employeeId?: string
+    employeeName?: string
+    pin?: string
+  } | null
+  manager: {
+    shopId?: string
+    shopName?: string
+  } | null
+  owner: {
+    shopIds?: string[]
+    primaryShopId?: string
+  } | null
+  admin: {
+    level?: number
+  } | null
+}
+
+/**
+ * 店铺信息
+ */
+export interface ShopInfo {
+  id: string
+  name: string
+  logo?: string
+  address?: string
+}
+
+/**
+ * 桌位会话(扫码点餐用)
+ */
+export interface TableSession {
+  tableId: string
+  tableCode: string
+  tableName: string
+  shopId: string
+  sessionId: string
+  createdAt: number
+}
+
 export const useUserStore = defineStore('user', {
   state: () => ({
-    member: {},
+    // 基础用户信息
+    member: {} as Record<string, any>,
     token: '',
     openid: '',
-    isMer: 0, // 是否是商家(保留兼容性)
-    merchartShop: {}, // 商家店铺信息(保留兼容性)
-
-    // 多角色系统(新增)
-    currentRole: 'user' as 'user' | 'merchant' | 'waiter' | 'admin', // 当前激活角色
-    availableRoles: ['user'] as Array<'user' | 'merchant' | 'waiter' | 'admin'>, // 用户拥有的角色列表
-    permissions: [] as string[], // 当前角色的权限列表
-    roleData: { // 角色特定数据
-      merchant: null as any,
-      waiter: null as any,
-      admin: null as any
-    }
+
+    // 多角色系统
+    currentRole: 'customer' as UserRole,
+    availableRoles: ['customer'] as UserRole[],
+    permissions: [] as string[],
+    roleData: {
+      staff: null,
+      manager: null,
+      owner: null,
+      admin: null
+    } as RoleData,
+
+    // 店铺绑定
+    currentShopId: '' as string,
+    currentShop: null as ShopInfo | null,
+    managedShops: [] as ShopInfo[], // owner 可管理的店铺列表
+
+    // 扫码访客会话
+    isGuest: false,
+    tableSession: null as TableSession | null,
+
+    // 兼容性字段
+    isMer: 0,
+    merchartShop: {} as Record<string, any>
   }),
 
   getters: {
     /**
-     * 是否登录
+     * 是否登录(guest 不算登录)
      */
     isLogin: (state) => {
-      return Object.keys(state.member).length > 0 && !!state.token
+      return Object.keys(state.member).length > 0 && !!state.token && !state.isGuest
+    },
+
+    /**
+     * 是否有有效会话(包括 guest)
+     */
+    hasSession: (state) => {
+      const isLoggedIn = Object.keys(state.member).length > 0 && !!state.token && !state.isGuest
+      return isLoggedIn || (state.isGuest && state.tableSession !== null)
     },
 
     /**
@@ -33,34 +109,43 @@ export const useUserStore = defineStore('user', {
      */
     userInfo: (state) => state.member,
 
+    // 角色判断
+    isCustomer: (state) => state.currentRole === 'customer',
+    isGuestRole: (state) => state.currentRole === 'guest',
+    isStaff: (state) => state.currentRole === 'staff',
+    isManager: (state) => state.currentRole === 'manager',
+    isOwner: (state) => state.currentRole === 'owner',
+    isAdmin: (state) => state.currentRole === 'admin',
+
     /**
-     * 是否是商家(保留兼容性)
+     * 是否为店铺管理角色(staff/manager/owner
      */
-    isMerchant: (state) => state.isMer === 1,
+    isShopRole: (state) => ['staff', 'manager', 'owner'].includes(state.currentRole),
 
-    // 角色判断(新增)
-    isUser: (state) => state.currentRole === 'user',
-    isMerchantRole: (state) => state.currentRole === 'merchant',
-    isWaiter: (state) => state.currentRole === 'waiter',
-    isAdmin: (state) => state.currentRole === 'admin',
+    /**
+     * 是否是商家(兼容旧代码)
+     */
+    isMerchant: (state) => state.isMer === 1 || ['manager', 'owner'].includes(state.currentRole),
 
     /**
      * 检查是否拥有某个角色
      */
-    hasRole: (state) => (role: string) => state.availableRoles.includes(role as any),
+    hasRole: (state) => (role: UserRole) => state.availableRoles.includes(role),
 
     /**
      * 检查是否拥有任一角色
      */
-    hasAnyRole: (state) => (roles: string[]) => roles.some(r => state.availableRoles.includes(r as any)),
+    hasAnyRole: (state) => (roles: UserRole[]) => roles.some(r => state.availableRoles.includes(r)),
 
     /**
      * 当前角色数据
      */
     currentRoleData: (state) => {
-      if (state.currentRole === 'merchant') return state.roleData.merchant
-      if (state.currentRole === 'waiter') return state.roleData.waiter
-      if (state.currentRole === 'admin') return state.roleData.admin
+      const role = state.currentRole
+      if (role === 'staff') return state.roleData.staff
+      if (role === 'manager') return state.roleData.manager
+      if (role === 'owner') return state.roleData.owner
+      if (role === 'admin') return state.roleData.admin
       return null
     }
   },
@@ -69,10 +154,8 @@ export const useUserStore = defineStore('user', {
     /**
      * 设置用户信息
      */
-    setMember(member: any) {
+    setMember(member: Record<string, any>) {
       this.member = member
-
-      // 根据用户信息初始化角色
       this.initUserRoles(member)
     },
 
@@ -91,24 +174,10 @@ export const useUserStore = defineStore('user', {
       this.openid = openid
     },
 
-    /**
-     * 设置是否是商家(保留兼容性)
-     */
-    setMer(isMer: number) {
-      this.isMer = isMer
-    },
-
-    /**
-     * 设置商家店铺信息(保留兼容性)
-     */
-    setMerchartShop(shop: any) {
-      this.merchartShop = shop
-    },
-
     /**
      * 设置当前角色
      */
-    setCurrentRole(role: 'user' | 'merchant' | 'waiter' | 'admin') {
+    setCurrentRole(role: UserRole) {
       if (this.availableRoles.includes(role)) {
         this.currentRole = role
         this.loadRolePermissions(role)
@@ -118,66 +187,141 @@ export const useUserStore = defineStore('user', {
     /**
      * 设置可用角色
      */
-    setAvailableRoles(roles: Array<'user' | 'merchant' | 'waiter' | 'admin'>) {
+    setAvailableRoles(roles: UserRole[]) {
       this.availableRoles = roles
     },
 
     /**
      * 设置角色数据
      */
-    setRoleData(role: 'merchant' | 'waiter' | 'admin', data: any) {
+    setRoleData<K extends keyof RoleData>(role: K, data: RoleData[K]) {
       this.roleData[role] = data
     },
 
     /**
-     * 初始化用户角色
+     * 设置当前店铺
      */
-    initUserRoles(userInfo: any) {
-      const roles: Array<'user' | 'merchant' | 'waiter' | 'admin'> = ['user'] // 默认都是普通用户
+    setCurrentShop(shop: ShopInfo) {
+      this.currentShopId = shop.id
+      this.currentShop = shop
+    },
 
-      // 判断是否为商家
-      if (userInfo.isMerchant || userInfo.isMer === 1 || this.isMer === 1) {
-        roles.push('merchant')
-        this.isMer = 1 // 保持兼容性
+    /**
+     * 设置管理的店铺列表
+     */
+    setManagedShops(shops: ShopInfo[]) {
+      this.managedShops = shops
+    },
+
+    /**
+     * 初始化访客会话(扫码点餐)
+     */
+    initGuestSession(session: TableSession) {
+      this.isGuest = true
+      this.tableSession = session
+      this.currentRole = 'guest'
+      this.availableRoles = ['guest']
+      this.currentShopId = session.shopId
+      this.loadRolePermissions('guest')
+    },
+
+    /**
+     * 清除访客会话
+     */
+    clearGuestSession() {
+      this.isGuest = false
+      this.tableSession = null
+      if (this.currentRole === 'guest') {
+        this.currentRole = 'customer'
+        this.availableRoles = ['customer']
       }
+    },
 
-      // 判断是否为服务员
-      if (userInfo.isWaiter || userInfo.roleType === 'waiter') {
-        roles.push('waiter')
+    /**
+     * 访客升级为登录用户
+     */
+    upgradeGuestToUser(member: Record<string, any>, token: string) {
+      const savedSession = this.tableSession
+      this.setToken(token)
+      this.setMember(member)
+      this.isGuest = false
+      // 保留桌位信息
+      if (savedSession) {
+        this.tableSession = savedSession
       }
+    },
+
+    /**
+     * 初始化用户角色
+     */
+    initUserRoles(userInfo: Record<string, any>) {
+      const roles: UserRole[] = ['customer']
 
-      // 判断是否为管理员
-      if (userInfo.isAdmin || userInfo.roleType === 'admin') {
+      // 判断角色类型
+      const roleType = userInfo.roleType || userInfo.role
+
+      if (roleType === 'admin' || userInfo.isAdmin) {
         roles.push('admin')
       }
 
+      if (roleType === 'owner' || userInfo.isOwner || userInfo.isMer === 1) {
+        roles.push('owner')
+        this.isMer = 1
+      }
+
+      if (roleType === 'manager' || userInfo.isManager) {
+        roles.push('manager')
+      }
+
+      if (roleType === 'staff' || userInfo.isStaff || userInfo.isWaiter) {
+        roles.push('staff')
+      }
+
       this.setAvailableRoles(roles)
 
-      // 设置默认角色(优先级:管理员 > 商家 > 服务员 > 用户)
-      if (roles.includes('admin')) {
-        this.setCurrentRole('admin')
-      } else if (roles.includes('merchant')) {
-        this.setCurrentRole('merchant')
-      } else if (roles.includes('waiter')) {
-        this.setCurrentRole('waiter')
-      } else {
-        this.setCurrentRole('user')
+      // 设置默认角色(优先级:admin > owner > manager > staff > customer)
+      const priorityOrder: UserRole[] = ['admin', 'owner', 'manager', 'staff', 'customer']
+      for (const role of priorityOrder) {
+        if (roles.includes(role)) {
+          this.setCurrentRole(role)
+          break
+        }
       }
     },
 
     /**
-     * 加载角色权限(简化版)
+     * 加载角色权限
      */
-    loadRolePermissions(role: string) {
-      const permissionMap: Record<string, string[]> = {
-        user: ['view:menu', 'create:order', 'view:order'],
-        merchant: ['view:menu', 'manage:orders', 'manage:menu', 'view:analytics', 'manage:finance'],
-        waiter: ['view:orders', 'update:order', 'print:order'],
-        admin: ['*'] // 所有权限
+    loadRolePermissions(role: UserRole) {
+      const permissionMap: Record<UserRole, string[]> = {
+        guest: ['view:menu', 'create:order'],
+        customer: ['view:menu', 'create:order', 'view:order', 'view:profile'],
+        staff: [
+          'view:menu', 'create:order', 'view:orders', 'update:order',
+          'print:order', 'manage:tables', 'open:table', 'close:table'
+        ],
+        manager: [
+          'view:menu', 'manage:menu', 'view:orders', 'manage:orders',
+          'view:analytics', 'manage:tables', 'manage:staff', 'view:reports'
+        ],
+        owner: [
+          'view:menu', 'manage:menu', 'view:orders', 'manage:orders',
+          'view:analytics', 'manage:tables', 'manage:staff', 'view:reports',
+          'manage:finance', 'manage:shop', 'config:buffet'
+        ],
+        admin: ['*']
       }
       this.permissions = permissionMap[role] || []
     },
 
+    /**
+     * 检查权限
+     */
+    can(permission: string): boolean {
+      if (this.permissions.includes('*')) return true
+      return this.permissions.includes(permission)
+    },
+
     /**
      * 登出
      */
@@ -187,31 +331,44 @@ export const useUserStore = defineStore('user', {
       this.openid = ''
       this.isMer = 0
       this.merchartShop = {}
-      this.currentRole = 'user'
-      this.availableRoles = ['user']
+      this.currentRole = 'customer'
+      this.availableRoles = ['customer']
       this.permissions = []
-      this.roleData = { merchant: null, waiter: null, admin: null }
+      this.roleData = { staff: null, manager: null, owner: null, admin: null }
+      this.currentShopId = ''
+      this.currentShop = null
+      this.managedShops = []
+      this.isGuest = false
+      this.tableSession = null
       storage.remove('accessToken')
     },
 
     /**
-     * 初始化用户信息
+     * 初始化
      */
     init() {
       const token = storage.get('accessToken')
       if (token) {
-        this.token = token
+        this.token = token as string
       }
+    },
+
+    // 兼容性方法
+    setMer(isMer: number) {
+      this.isMer = isMer
+    },
+
+    setMerchartShop(shop: Record<string, any>) {
+      this.merchartShop = shop
     }
   },
 
   persist: {
-    enabled: true,
-    strategies: [
-      {
-        storage: localStorage,
-        paths: ['member', 'token', 'openid', 'isMer', 'merchartShop', 'currentRole', 'availableRoles', 'roleData']
-      }
+    storage: localStorage,
+    paths: [
+      'member', 'token', 'openid', 'isMer', 'merchartShop',
+      'currentRole', 'availableRoles', 'roleData',
+      'currentShopId', 'currentShop', 'managedShops'
     ]
   }
 })

+ 6 - 6
src/utils/format.ts

@@ -17,7 +17,7 @@ dayjs.extend(relativeTime)
  * @param {string} format - 格式化模板
  * @returns {string} - 格式化后的字符串
  */
-export const formatDateTime = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
+export const formatDateTime = (date: Date | string | number, format: string = 'YYYY-MM-DD HH:mm:ss'): string => {
   if (!date) return ''
   return dayjs(date).format(format)
 }
@@ -27,7 +27,7 @@ export const formatDateTime = (date, format = 'YYYY-MM-DD HH:mm:ss') => {
  * @param {Date|string|number} date - 日期
  * @returns {string} - 相对时间字符串
  */
-export const formatPast = (date) => {
+export const formatPast = (date: Date | string | number): string => {
   if (!date) return '从未下单'
   return dayjs(date).fromNow() + '下单'
 }
@@ -37,7 +37,7 @@ export const formatPast = (date) => {
  * @param {number} seconds - 秒数
  * @returns {string} - HH:mm:ss格式
  */
-export const formatTime = (seconds) => {
+export const formatTime = (seconds: number): string => {
   if (typeof seconds !== 'number' || seconds < 0) {
     return '00:00:00'
   }
@@ -56,7 +56,7 @@ export const formatTime = (seconds) => {
  * @param {number} meters - 米数
  * @returns {string} - 格式化后的距离
  */
-export const formatDistance = (meters) => {
+export const formatDistance = (meters: number): string => {
   if (typeof meters !== 'number' || isNaN(meters)) {
     return '0m'
   }
@@ -72,8 +72,8 @@ export const formatDistance = (meters) => {
  * 设置dayjs语言
  * @param {string} lang - 语言代码
  */
-export const setDayjsLocale = (lang) => {
-  const localeMap = {
+export const setDayjsLocale = (lang: string): void => {
+  const localeMap: Record<string, string> = {
     'zh-Hans': 'zh-cn',
     'zh-Hant': 'zh-tw',
     'ja': 'ja',

+ 130 - 0
src/utils/geocoding.ts

@@ -0,0 +1,130 @@
+/**
+ * 反向地理编码工具
+ * 通过后端 API 将经纬度坐标转换为地址
+ */
+
+export interface GeocodeResult {
+  latitude: number
+  longitude: number
+  address: string
+  city?: string
+  district?: string
+  country?: string
+}
+
+/**
+ * 通过后端 API 进行反向地理编码(推荐方式)
+ * 后端会调用 Google Maps 或其他服务,并缓存结果
+ */
+export async function reverseGeocode(
+  latitude: number,
+  longitude: number
+): Promise<GeocodeResult | null> {
+  try {
+    const response = await fetch(
+      `/api/geocoding/reverse?lat=${latitude}&lng=${longitude}`,
+      {
+        method: 'GET',
+        headers: {
+          'Content-Type': 'application/json'
+        }
+      }
+    )
+
+    if (!response.ok) {
+      throw new Error('Backend geocoding failed')
+    }
+
+    const data = await response.json()
+    
+    return {
+      latitude,
+      longitude,
+      address: data.address || '',
+      city: data.city,
+      district: data.district,
+      country: data.country
+    }
+  } catch (error) {
+    console.error('Geocoding request failed:', error)
+    // 降级方案:使用免费的 Nominatim(前端直接调用)
+    return await reverseGeocodeWithNominatim(latitude, longitude)
+  }
+}
+
+/**
+ * 降级方案:使用 OpenStreetMap Nominatim
+ * 仅在后端 API 不可用时使用
+ */
+async function reverseGeocodeWithNominatim(
+  latitude: number,
+  longitude: number
+): Promise<GeocodeResult | null> {
+  try {
+    const response = await fetch(
+      `https://nominatim.openstreetmap.org/reverse?` +
+      `format=json&lat=${latitude}&lon=${longitude}&` +
+      `accept-language=ja,zh-CN,en`,
+      {
+        headers: {
+          'User-Agent': 'FastEat-OrderApp/1.0'
+        }
+      }
+    )
+
+    if (!response.ok) {
+      return null
+    }
+
+    const data = await response.json()
+    const addr = data.address || {}
+    
+    let displayAddress = ''
+    if (addr.suburb || addr.neighbourhood) {
+      displayAddress = addr.suburb || addr.neighbourhood
+    } else if (addr.city || addr.town) {
+      displayAddress = addr.city || addr.town
+    } else if (addr.state) {
+      displayAddress = addr.state
+    }
+
+    return {
+      latitude,
+      longitude,
+      address: displayAddress || '当前位置',
+      city: addr.city || addr.town,
+      district: addr.suburb || addr.district,
+      country: addr.country
+    }
+  } catch (error) {
+    console.error('Nominatim fallback failed:', error)
+    return null
+  }
+}
+
+/**
+ * 格式化地址显示
+ */
+export function formatAddress(result: GeocodeResult, lang: string = 'ja'): string {
+  if (!result.address) {
+    return '当前位置'
+  }
+
+  let address = result.address
+  
+  // 日语/中文:优先显示市区名
+  if (lang === 'ja' || lang.startsWith('zh')) {
+    if (result.district && result.city) {
+      address = `${result.city}${result.district}`
+    } else if (result.city) {
+      address = result.city
+    }
+  }
+
+  // 限制长度
+  if (address.length > 20) {
+    address = address.substring(0, 20) + '...'
+  }
+
+  return address
+}

+ 17 - 3
src/utils/image.ts

@@ -10,7 +10,13 @@ import { API_URL } from '@/config'
  * @param {string} defaultImage - 默认图片路径
  * @returns {string} - 处理后的完整URL
  */
-export const getImageUrl = (url, defaultImage = '/default.png') => {
+/**
+ * 处理图片URL
+ * @param {string} url - 图片URL
+ * @param {string} defaultImage - 默认图片路径
+ * @returns {string} - 处理后的完整URL
+ */
+export const getImageUrl = (url: string, defaultImage: string = '/default.png'): string => {
   if (!url) return defaultImage
 
   // 如果是完整的http(s)链接,直接返回
@@ -32,6 +38,14 @@ export const getImageUrl = (url, defaultImage = '/default.png') => {
  * @param {Event} e - 错误事件
  * @param {string} defaultImage - 默认图片路径
  */
-export const handleImageError = (e, defaultImage = '/default.png') => {
-  e.target.src = defaultImage
+/**
+ * 图片加载错误处理
+ * @param {Event} e - 错误事件
+ * @param {string} defaultImage - 默认图片路径
+ */
+export const handleImageError = (e: Event, defaultImage: string = '/default.png'): void => {
+  const target = e.target as HTMLImageElement
+  if (target) {
+    target.src = defaultImage
+  }
 }

+ 28 - 12
src/utils/index.ts

@@ -11,9 +11,12 @@ export * from './price'
 /**
  * 防抖函数
  */
-export function debounce(fn, delay = 300) {
-  let timer = null
-  return function (...args) {
+/**
+ * 防抖函数
+ */
+export function debounce(fn: (...args: any[]) => void, delay: number = 300): (...args: any[]) => void {
+  let timer: ReturnType<typeof setTimeout> | null = null
+  return function (this: any, ...args: any[]) {
     if (timer) clearTimeout(timer)
     timer = setTimeout(() => {
       fn.apply(this, args)
@@ -24,9 +27,12 @@ export function debounce(fn, delay = 300) {
 /**
  * 节流函数
  */
-export function throttle(fn, delay = 300) {
+/**
+ * 节流函数
+ */
+export function throttle(fn: (...args: any[]) => void, delay: number = 300): (...args: any[]) => void {
   let lastTime = 0
-  return function (...args) {
+  return function (this: any, ...args: any[]) {
     const now = Date.now()
     if (now - lastTime >= delay) {
       lastTime = now
@@ -38,30 +44,40 @@ export function throttle(fn, delay = 300) {
 /**
  * 深拷贝
  */
-export function deepClone(obj) {
+/**
+ * 深拷贝
+ */
+export function deepClone<T = any>(obj: T): T {
   if (obj === null || typeof obj !== 'object') return obj
-  if (obj instanceof Date) return new Date(obj)
-  if (obj instanceof Array) return obj.map((item) => deepClone(item))
+  if (obj instanceof Date) return new Date(obj) as any
+  if (obj instanceof Array) return obj.map((item) => deepClone(item)) as any
   if (obj instanceof Object) {
-    const copy = {}
+    const copy: any = {}
     Object.keys(obj).forEach((key) => {
-      copy[key] = deepClone(obj[key])
+      copy[key] = deepClone((obj as any)[key])
     })
     return copy
   }
+  return obj
 }
 
 /**
  * 生成唯一ID
  */
-export function generateId() {
+/**
+ * 生成唯一ID
+ */
+export function generateId(): string {
   return Date.now().toString(36) + Math.random().toString(36).substr(2)
 }
 
 /**
  * 复制到剪贴板
  */
-export function copyToClipboard(text) {
+/**
+ * 复制到剪贴板
+ */
+export function copyToClipboard(text: string): Promise<void> {
   if (navigator.clipboard) {
     return navigator.clipboard.writeText(text)
   }

+ 31 - 3
src/utils/price.ts

@@ -8,7 +8,13 @@
  * @returns {number} - 转换后的数字
  * @throws {Error} - 当无法转换为有效数字时抛出错误
  */
-const toNumber = (value) => {
+/**
+ * 将输入值转换为数字
+ * @param {any} value - 输入值
+ * @returns {number} - 转换后的数字
+ * @throws {Error} - 当无法转换为有效数字时抛出错误
+ */
+const toNumber = (value: any): number => {
   if (value === undefined || value === null) {
     throw new Error('输入值不能为空')
   }
@@ -29,7 +35,23 @@ const toNumber = (value) => {
  * @returns {object} - 包含税前税后价格的对象
  * @throws {Error} - 当参数无效时抛出错误
  */
-export const withTax = (price, taxRate = 0.1) => {
+interface PriceObject {
+  beforeTax: string;
+  afterTax: string;
+  labels: {
+    beforeTax: Record<string, string>;
+    afterTax: Record<string, string>;
+  };
+}
+
+/**
+ * 计算含税价格并返回价格对象
+ * @param {number|string} price - 原始价格
+ * @param {number|string} [taxRate=0.1] - 税率(小数形式,如0.1表示10%),默认10%
+ * @returns {object} - 包含税前税后价格的对象
+ * @throws {Error} - 当参数无效时抛出错误
+ */
+export const withTax = (price: number | string, taxRate: number | string = 0.1): PriceObject => {
   // 转换价格为数字
   const numPrice = toNumber(price)
 
@@ -70,7 +92,13 @@ export const withTax = (price, taxRate = 0.1) => {
  * @param {string} currency - 货币符号
  * @returns {string} - 格式化后的价格字符串
  */
-export const formatPrice = (price, currency = '¥') => {
+/**
+ * 格式化价格显示
+ * @param {number|string} price - 价格
+ * @param {string} currency - 货币符号
+ * @returns {string} - 格式化后的价格字符串
+ */
+export const formatPrice = (price: number | string, currency: string = '¥'): string => {
   const num = toNumber(price)
   return `${currency}${num.toFixed(2)}`
 }

+ 11 - 11
src/utils/storage.ts

@@ -6,13 +6,13 @@ export const storage = {
   /**
    * 获取存储值
    */
-  get(key, defaultValue = null) {
+  get<T = any>(key: string, defaultValue: T | null = null): T | null {
     try {
       const value = localStorage.getItem(key)
       if (value === null) {
         return defaultValue
       }
-      return JSON.parse(value)
+      return JSON.parse(value) as T
     } catch (error) {
       console.error(`Error getting ${key} from storage:`, error)
       return defaultValue
@@ -22,7 +22,7 @@ export const storage = {
   /**
    * 设置存储值
    */
-  set(key, value) {
+  set(key: string, value: any): boolean {
     try {
       localStorage.setItem(key, JSON.stringify(value))
       return true
@@ -35,7 +35,7 @@ export const storage = {
   /**
    * 移除存储值
    */
-  remove(key) {
+  remove(key: string): boolean {
     try {
       localStorage.removeItem(key)
       return true
@@ -48,7 +48,7 @@ export const storage = {
   /**
    * 清空所有存储
    */
-  clear() {
+  clear(): boolean {
     try {
       localStorage.clear()
       return true
@@ -61,7 +61,7 @@ export const storage = {
   /**
    * 检查key是否存在
    */
-  has(key) {
+  has(key: string): boolean {
     return localStorage.getItem(key) !== null
   }
 }
@@ -70,20 +70,20 @@ export const storage = {
  * sessionStorage 封装
  */
 export const sessionStorage = {
-  get(key, defaultValue = null) {
+  get<T = any>(key: string, defaultValue: T | null = null): T | null {
     try {
       const value = window.sessionStorage.getItem(key)
       if (value === null) {
         return defaultValue
       }
-      return JSON.parse(value)
+      return JSON.parse(value) as T
     } catch (error) {
       console.error(`Error getting ${key} from sessionStorage:`, error)
       return defaultValue
     }
   },
 
-  set(key, value) {
+  set(key: string, value: any): boolean {
     try {
       window.sessionStorage.setItem(key, JSON.stringify(value))
       return true
@@ -93,7 +93,7 @@ export const sessionStorage = {
     }
   },
 
-  remove(key) {
+  remove(key: string): boolean {
     try {
       window.sessionStorage.removeItem(key)
       return true
@@ -103,7 +103,7 @@ export const sessionStorage = {
     }
   },
 
-  clear() {
+  clear(): boolean {
     try {
       window.sessionStorage.clear()
       return true

+ 31 - 6
src/utils/validate.ts

@@ -7,7 +7,12 @@
  * @param {string} phone - 手机号码
  * @returns {boolean} - 是否有效
  */
-export function validatePhoneNumber(phone) {
+/**
+ * 验证手机号码
+ * @param {string} phone - 手机号码
+ * @returns {boolean} - 是否有效
+ */
+export function validatePhoneNumber(phone: string): boolean {
   const reg = /^1[3-9]\d{9}$/
   return reg.test(phone)
 }
@@ -17,7 +22,12 @@ export function validatePhoneNumber(phone) {
  * @param {string} cardNumber - 银行卡号
  * @returns {boolean} - 是否有效
  */
-export function isValidBankCard(cardNumber) {
+/**
+ * 验证银行卡号
+ * @param {string} cardNumber - 银行卡号
+ * @returns {boolean} - 是否有效
+ */
+export function isValidBankCard(cardNumber: string): boolean {
   const reg = /^(\d{16}|\d{19})$/
   return reg.test(cardNumber)
 }
@@ -27,7 +37,12 @@ export function isValidBankCard(cardNumber) {
  * @param {string} email - 邮箱地址
  * @returns {boolean} - 是否有效
  */
-export function validateEmail(email) {
+/**
+ * 验证邮箱
+ * @param {string} email - 邮箱地址
+ * @returns {boolean} - 是否有效
+ */
+export function validateEmail(email: string): boolean {
   const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
   return reg.test(email)
 }
@@ -37,7 +52,12 @@ export function validateEmail(email) {
  * @param {string} url - URL地址
  * @returns {boolean} - 是否有效
  */
-export function validateURL(url) {
+/**
+ * 验证URL
+ * @param {string} url - URL地址
+ * @returns {boolean} - 是否有效
+ */
+export function validateURL(url: string): boolean {
   const reg = /^https?:\/\/.+/
   return reg.test(url)
 }
@@ -47,7 +67,12 @@ export function validateURL(url) {
  * @param {string} password - 密码
  * @returns {object} - {valid: boolean, strength: string}
  */
-export function validatePassword(password) {
+/**
+ * 验证密码强度
+ * @param {string} password - 密码
+ * @returns {object} - {valid: boolean, strength: string}
+ */
+export function validatePassword(password: string): { valid: boolean; strength: string } {
   if (!password || password.length < 6) {
     return { valid: false, strength: 'weak' }
   }
@@ -59,7 +84,7 @@ export function validatePassword(password) {
   if (/\d/.test(password)) strength++
   if (/[^a-zA-Z\d]/.test(password)) strength++
 
-  const strengthMap = {
+  const strengthMap: Record<number, string> = {
     1: 'weak',
     2: 'weak',
     3: 'medium',

+ 163 - 0
src/views/admin/dashboard.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="admin-dashboard">
+    <van-nav-bar title="管理后台" left-arrow @click-left="goBack" />
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <div class="dashboard-content">
+        <div class="welcome-section">
+          <h2>欢迎来到管理后台</h2>
+          <p>系统管理员</p>
+        </div>
+
+        <!-- 统计卡片 -->
+        <div class="stats-section">
+          <h4>平台统计</h4>
+          <van-grid :column-num="2" :border="false">
+            <van-grid-item icon="friends-o" text="总用户数">
+              <template #text>
+                <div class="grid-value">{{ stats.totalUsers }}</div>
+                <div class="grid-label">总用户数</div>
+              </template>
+            </van-grid-item>
+            <van-grid-item icon="shop-o" text="总商家数">
+              <template #text>
+                <div class="grid-value">{{ stats.totalMerchants }}</div>
+                <div class="grid-label">总商家数</div>
+              </template>
+            </van-grid-item>
+            <van-grid-item icon="orders-o" text="今日订单">
+              <template #text>
+                <div class="grid-value">{{ stats.todayOrders }}</div>
+                <div class="grid-label">今日订单</div>
+              </template>
+            </van-grid-item>
+            <van-grid-item icon="gold-coin-o" text="总营业额">
+              <template #text>
+                <div class="grid-value">¥{{ (stats.totalRevenue / 10000).toFixed(1) }}万</div>
+                <div class="grid-label">总营业额</div>
+              </template>
+            </van-grid-item>
+          </van-grid>
+        </div>
+
+        <!-- 快捷操作 -->
+        <div class="actions-section">
+          <h4>快捷操作</h4>
+          <van-grid :column-num="3" :border="false">
+            <van-grid-item icon="friends-o" text="用户管理" @click="goToUsers" />
+            <van-grid-item icon="shop-o" text="商家管理" @click="goToMerchants" />
+            <van-grid-item icon="setting-o" text="系统设置" @click="showDev" />
+            <van-grid-item icon="bar-chart-o" text="数据统计" @click="showDev" />
+            <van-grid-item icon="records" text="操作日志" @click="showDev" />
+            <van-grid-item icon="down" text="数据导出" @click="showDev" />
+          </van-grid>
+        </div>
+      </div>
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+import { useAdminStore } from '@/store/modules/admin'
+
+const router = useRouter()
+const adminStore = useAdminStore()
+
+const refreshing = ref(false)
+
+const stats = computed(() => adminStore.stats)
+
+const loadData = async () => {
+  try {
+    await adminStore.loadDashboardStats()
+  } catch (error) {
+    showToast('加载失败')
+  }
+}
+
+const onRefresh = async () => {
+  await loadData()
+  refreshing.value = false
+  showToast('刷新成功')
+}
+
+const goBack = () => {
+  router.push('/mine')
+}
+
+const goToUsers = () => {
+  showToast('用户管理功能开发中')
+}
+
+const goToMerchants = () => {
+  showToast('商家管理功能开发中')
+}
+
+const showDev = () => {
+  showToast('功能开发中')
+}
+
+onMounted(() => {
+  loadData()
+})
+</script>
+
+<style scoped lang="scss">
+.admin-dashboard {
+  min-height: 100vh;
+  background: #f0f2f5;
+}
+
+.dashboard-content {
+  padding: 15px;
+}
+
+.welcome-section {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 12px;
+  padding: 30px 20px;
+  color: #fff;
+  margin-bottom: 15px;
+  text-align: center;
+
+  h2 {
+    margin: 0 0 10px 0;
+    font-size: 24px;
+  }
+
+  p {
+    margin: 0;
+    font-size: 14px;
+    opacity: 0.9;
+  }
+}
+
+.stats-section,
+.actions-section {
+  background: #fff;
+  border-radius: 12px;
+  padding: 15px;
+  margin-bottom: 15px;
+
+  h4 {
+    margin: 0 0 15px 0;
+    font-size: 16px;
+    font-weight: 600;
+  }
+}
+
+.grid-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #323233;
+  margin-bottom: 5px;
+}
+
+.grid-label {
+  font-size: 12px;
+  color: #969799;
+}
+</style>

+ 194 - 0
src/views/admin/merchants.vue

@@ -0,0 +1,194 @@
+<template>
+  <div class="admin-merchants">
+    <van-nav-bar title="商家管理" left-arrow @click-left="$router.back()" fixed placeholder>
+      <template #right>
+        <van-icon name="plus" size="20" @click="addMerchant" />
+      </template>
+    </van-nav-bar>
+
+    <van-search v-model="searchText" placeholder="搜索商家/公司名" />
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <div class="merchant-list">
+        <div v-for="merchant in merchants" :key="merchant.id" class="merchant-card">
+          <div class="merchant-header">
+            <div class="merchant-info">
+              <h3>{{ merchant.companyName }}</h3>
+              <p>老板: {{ merchant.ownerName }} | {{ merchant.shopCount }}家店铺</p>
+            </div>
+            <van-tag :type="merchant.status === 'active' ? 'success' : 'warning'">
+              {{ merchant.status === 'active' ? '正常' : '暂停' }}
+            </van-tag>
+          </div>
+          <div class="merchant-stats">
+            <div class="stat-item">
+              <span class="value">¥{{ formatNumber(merchant.totalRevenue) }}</span>
+              <span class="label">总营业额</span>
+            </div>
+            <div class="stat-item">
+              <span class="value">{{ merchant.totalOrders }}</span>
+              <span class="label">总订单</span>
+            </div>
+            <div class="stat-item">
+              <span class="value">{{ merchant.memberCount }}</span>
+              <span class="label">会员数</span>
+            </div>
+          </div>
+          <div class="merchant-actions">
+            <van-button size="small" plain @click="viewMerchant(merchant)">查看详情</van-button>
+            <van-button size="small" plain type="primary" @click="editMerchant(merchant)">编辑</van-button>
+            <van-button
+              size="small"
+              plain
+              :type="merchant.status === 'active' ? 'warning' : 'success'"
+              @click="toggleStatus(merchant)"
+            >
+              {{ merchant.status === 'active' ? '暂停' : '启用' }}
+            </van-button>
+          </div>
+        </div>
+      </div>
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+
+const router = useRouter()
+
+interface Merchant {
+  id: string
+  companyName: string
+  ownerName: string
+  shopCount: number
+  status: 'active' | 'suspended'
+  totalRevenue: number
+  totalOrders: number
+  memberCount: number
+}
+
+const searchText = ref('')
+const refreshing = ref(false)
+
+const merchants = ref<Merchant[]>([
+  {
+    id: '1',
+    companyName: 'XXX餐饮集团',
+    ownerName: '张老板',
+    shopCount: 3,
+    status: 'active',
+    totalRevenue: 258000,
+    totalOrders: 1560,
+    memberCount: 380
+  },
+  {
+    id: '2',
+    companyName: 'YYY美食连锁',
+    ownerName: '李老板',
+    shopCount: 5,
+    status: 'active',
+    totalRevenue: 480000,
+    totalOrders: 2890,
+    memberCount: 650
+  }
+])
+
+const formatNumber = (num: number) => {
+  if (num >= 10000) return (num / 10000).toFixed(1) + '万'
+  return num.toLocaleString()
+}
+
+const onRefresh = () => {
+  setTimeout(() => {
+    refreshing.value = false
+    showToast('刷新成功')
+  }, 1000)
+}
+
+const addMerchant = () => {
+  showToast('添加商家')
+}
+
+const viewMerchant = (merchant: Merchant) => {
+  showToast(`查看: ${merchant.companyName}`)
+}
+
+const editMerchant = (merchant: Merchant) => {
+  showToast(`编辑: ${merchant.companyName}`)
+}
+
+const toggleStatus = (merchant: Merchant) => {
+  merchant.status = merchant.status === 'active' ? 'suspended' : 'active'
+  showToast(merchant.status === 'active' ? '已启用' : '已暂停')
+}
+</script>
+
+<style scoped>
+.admin-merchants {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.merchant-list {
+  padding: 12px;
+}
+
+.merchant-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 16px;
+  margin-bottom: 12px;
+}
+
+.merchant-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: flex-start;
+  margin-bottom: 16px;
+}
+
+.merchant-info h3 {
+  font-size: 16px;
+  margin: 0 0 4px 0;
+}
+
+.merchant-info p {
+  font-size: 12px;
+  color: #999;
+  margin: 0;
+}
+
+.merchant-stats {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 16px;
+  padding: 12px;
+  background: #f8f8f8;
+  border-radius: 8px;
+}
+
+.stat-item {
+  flex: 1;
+  text-align: center;
+}
+
+.stat-item .value {
+  display: block;
+  font-size: 18px;
+  font-weight: bold;
+  color: #333;
+}
+
+.stat-item .label {
+  font-size: 12px;
+  color: #999;
+}
+
+.merchant-actions {
+  display: flex;
+  gap: 8px;
+}
+</style>

+ 163 - 0
src/views/admin/users.vue

@@ -0,0 +1,163 @@
+<template>
+  <div class="admin-users">
+    <van-nav-bar title="用户管理" left-arrow @click-left="$router.back()" fixed placeholder />
+
+    <van-search v-model="searchText" placeholder="搜索用户名/手机号" />
+
+    <van-tabs v-model:active="activeTab">
+      <van-tab title="全部" />
+      <van-tab title="普通用户" />
+      <van-tab title="商家" />
+      <van-tab title="员工" />
+    </van-tabs>
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <van-list
+        v-model:loading="loading"
+        :finished="finished"
+        finished-text="没有更多了"
+        @load="onLoad"
+      >
+        <div class="user-list">
+          <div v-for="user in users" :key="user.id" class="user-card" @click="viewUser(user)">
+            <van-image
+              :src="user.avatar || '/default-avatar.png'"
+              round
+              width="50"
+              height="50"
+              fit="cover"
+            />
+            <div class="user-info">
+              <div class="user-name">{{ user.nickname }}</div>
+              <div class="user-meta">
+                <span>{{ user.phone }}</span>
+                <van-tag :type="getRoleType(user.role)">{{ getRoleText(user.role) }}</van-tag>
+              </div>
+            </div>
+            <div class="user-stats">
+              <div>订单: {{ user.orderCount || 0 }}</div>
+              <div>消费: ¥{{ user.totalSpent || 0 }}</div>
+            </div>
+          </div>
+        </div>
+      </van-list>
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+
+const router = useRouter()
+
+interface User {
+  id: string
+  nickname: string
+  phone: string
+  avatar?: string
+  role: string
+  orderCount?: number
+  totalSpent?: number
+}
+
+const searchText = ref('')
+const activeTab = ref(0)
+const refreshing = ref(false)
+const loading = ref(false)
+const finished = ref(false)
+
+const users = ref<User[]>([
+  { id: '1', nickname: '张三', phone: '138****1234', role: 'customer', orderCount: 15, totalSpent: 1280 },
+  { id: '2', nickname: '李四', phone: '139****5678', role: 'merchant', orderCount: 0, totalSpent: 0 },
+  { id: '3', nickname: '王五', phone: '136****9012', role: 'staff', orderCount: 0, totalSpent: 0 },
+])
+
+const getRoleType = (role: string) => {
+  const types: Record<string, any> = {
+    customer: 'default',
+    merchant: 'primary',
+    staff: 'success',
+    admin: 'danger'
+  }
+  return types[role] || 'default'
+}
+
+const getRoleText = (role: string) => {
+  const texts: Record<string, string> = {
+    customer: '顾客',
+    merchant: '商家',
+    staff: '员工',
+    admin: '管理员'
+  }
+  return texts[role] || role
+}
+
+const onRefresh = () => {
+  setTimeout(() => {
+    refreshing.value = false
+    showToast('刷新成功')
+  }, 1000)
+}
+
+const onLoad = () => {
+  setTimeout(() => {
+    loading.value = false
+    finished.value = true
+  }, 1000)
+}
+
+const viewUser = (user: User) => {
+  showToast(`查看用户: ${user.nickname}`)
+}
+</script>
+
+<style scoped>
+.admin-users {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.user-list {
+  padding: 12px;
+}
+
+.user-card {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  background: #fff;
+  border-radius: 8px;
+  padding: 12px;
+  margin-bottom: 12px;
+}
+
+.user-info {
+  flex: 1;
+}
+
+.user-name {
+  font-size: 15px;
+  font-weight: 500;
+  margin-bottom: 4px;
+}
+
+.user-meta {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 12px;
+  color: #999;
+}
+
+.user-stats {
+  text-align: right;
+  font-size: 12px;
+  color: #666;
+}
+
+.user-stats div {
+  margin-bottom: 4px;
+}
+</style>

+ 268 - 0
src/views/buffet/menu.vue

@@ -0,0 +1,268 @@
+<template>
+  <div class="buffet-menu">
+    <!-- 放题计时器 -->
+    <BuffetTimer />
+
+    <van-nav-bar :title="$t('buffet.menu')" left-arrow @click-left="handleBack" fixed :placeholder="true" :style="{ top: timerHeight }" />
+
+    <!-- 不可点餐提示 -->
+    <div v-if="!canOrder" class="order-disabled-banner">
+      <van-icon name="warning-o" />
+      <span v-if="session?.status === 'expired'">放题时间已结束</span>
+      <span v-else-if="session?.maxOrders && session.orderCount >= session.maxOrders">
+        已达点单次数上限 ({{ session.orderCount }}/{{ session.maxOrders }})
+      </span>
+      <span v-else>当前无法点餐</span>
+    </div>
+
+    <!-- 点单统计 -->
+    <div class="order-stats">
+      <div class="stat-item">
+        <span class="label">已点单</span>
+        <span class="value">{{ session?.orderCount || 0 }}次</span>
+      </div>
+      <div class="stat-item" v-if="session?.maxOrders">
+        <span class="label">剩余次数</span>
+        <span class="value">{{ remainingOrders }}次</span>
+      </div>
+    </div>
+
+    <!-- 菜单内容(复用普通菜单组件或自定义) -->
+    <div class="menu-content">
+      <van-tabs>
+        <van-tab v-for="category in categories" :key="category.id" :title="category.name">
+          <div class="dishes-grid">
+            <div v-for="dish in category.dishes" :key="dish.id" class="dish-card" @click="selectDish(dish)">
+              <van-image :src="dish.image" width="100%" height="120" fit="cover" />
+              <div class="dish-info">
+                <div class="dish-name">{{ dish.name }}</div>
+                <div class="dish-badge">
+                  <van-tag type="success">放题</van-tag>
+                </div>
+              </div>
+              <div class="dish-action">
+                <van-stepper
+                  :model-value="getQuantity(dish.id)"
+                  @update:model-value="(val) => updateQuantity(dish.id, val)"
+                  :disabled="!canOrder"
+                  min="0"
+                  max="10"
+                />
+              </div>
+            </div>
+          </div>
+        </van-tab>
+      </van-tabs>
+    </div>
+
+    <!-- 底部购物车 -->
+    <div class="cart-bar" v-if="cartCount > 0">
+      <div class="cart-info">
+        <van-icon name="shopping-cart-o" :badge="cartCount" />
+        <span>已选{{ cartCount }}份</span>
+      </div>
+      <van-button type="primary" round @click="submitOrder" :disabled="!canOrder">
+        确认点餐
+      </van-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast, showConfirmDialog } from 'vant'
+import { useBuffetStore } from '@/store/modules/buffet'
+import BuffetTimer from '@/components/buffet/BuffetTimer.vue'
+
+const router = useRouter()
+const buffetStore = useBuffetStore()
+
+const cart = ref<Record<string, number>>({})
+
+const session = computed(() => buffetStore.currentSession)
+const canOrder = computed(() => buffetStore.canOrder)
+const timerHeight = computed(() => buffetStore.showTimer ? '86px' : '46px')
+const remainingOrders = computed(() => {
+  if (!session.value || !session.value.maxOrders) return 0
+  return session.value.maxOrders - session.value.orderCount
+})
+const cartCount = computed(() => Object.values(cart.value).reduce((sum, n) => sum + n, 0))
+
+// Mock数据
+const categories = ref([
+  {
+    id: 'cat_1',
+    name: '热菜',
+    dishes: [
+      { id: 'dish_1', name: '宫保鸡丁', image: '' },
+      { id: 'dish_2', name: '麻婆豆腐', image: '' }
+    ]
+  },
+  {
+    id: 'cat_2',
+    name: '寿司',
+    dishes: [
+      { id: 'dish_3', name: '三文鱼寿司', image: '' },
+      { id: 'dish_4', name: '金枪鱼寿司', image: '' }
+    ]
+  }
+])
+
+const getQuantity = (dishId: string) => cart.value[dishId] || 0
+
+const updateQuantity = (dishId: string, quantity: number) => {
+  if (quantity === 0) {
+    delete cart.value[dishId]
+  } else {
+    cart.value[dishId] = quantity
+  }
+}
+
+const selectDish = (dish: any) => {
+  if (!canOrder.value) {
+    showToast('当前无法点餐')
+    return
+  }
+  const current = getQuantity(dish.id)
+  updateQuantity(dish.id, current + 1)
+}
+
+const submitOrder = async () => {
+  if (!canOrder.value) {
+    showToast('当前无法点餐')
+    return
+  }
+
+  try {
+    await showConfirmDialog({
+      title: '确认点餐',
+      message: `本次点餐 ${cartCount.value} 份菜品\n${session.value?.maxOrders ? `点单次数: ${session.value.orderCount + 1}/${session.value.maxOrders}` : ''}`
+    })
+
+    // TODO: 提交订单到后端
+    buffetStore.incrementOrderCount()
+    showToast('点餐成功!')
+    cart.value = {}
+
+  } catch {
+    // 用户取消
+  }
+}
+
+const handleBack = async () => {
+  if (cartCount.value > 0) {
+    try {
+      await showConfirmDialog({
+        title: '提示',
+        message: '购物车中还有商品,确定要离开吗?'
+      })
+      router.back()
+    } catch {
+      // 用户取消
+    }
+  } else {
+    router.back()
+  }
+}
+</script>
+
+<style scoped>
+.buffet-menu {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 60px;
+}
+
+.order-disabled-banner {
+  background: #fff3e6;
+  color: #ff976a;
+  padding: 12px 16px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 14px;
+}
+
+.order-stats {
+  background: #fff;
+  display: flex;
+  padding: 12px 16px;
+  margin-bottom: 8px;
+}
+
+.stat-item {
+  flex: 1;
+  text-align: center;
+}
+
+.stat-item .label {
+  display: block;
+  font-size: 12px;
+  color: #999;
+  margin-bottom: 4px;
+}
+
+.stat-item .value {
+  font-size: 18px;
+  font-weight: bold;
+  color: #333;
+}
+
+.menu-content {
+  background: #fff;
+}
+
+.dishes-grid {
+  display: grid;
+  grid-template-columns: repeat(2, 1fr);
+  gap: 12px;
+  padding: 12px;
+}
+
+.dish-card {
+  background: #fff;
+  border-radius: 8px;
+  overflow: hidden;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
+}
+
+.dish-info {
+  padding: 8px;
+}
+
+.dish-name {
+  font-size: 14px;
+  font-weight: 500;
+  margin-bottom: 4px;
+}
+
+.dish-badge {
+  margin-bottom: 8px;
+}
+
+.dish-action {
+  padding: 0 8px 8px;
+}
+
+.cart-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  padding: 12px 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
+}
+
+.cart-info {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 15px;
+  font-weight: 500;
+}
+</style>

+ 315 - 0
src/views/buffet/select.vue

@@ -0,0 +1,315 @@
+<template>
+  <div class="buffet-select">
+    <van-nav-bar title="选择放题方案" left-arrow @click-left="$router.back()" fixed placeholder />
+
+    <div class="intro-banner">
+      <van-icon name="fire-o" size="36" color="#ff6b6b" />
+      <h2>畅饮畅食 无限享受</h2>
+      <p>选择您喜欢的放题方案,开启美食之旅</p>
+    </div>
+
+    <div class="plans-container">
+      <div
+        v-for="plan in activePlans"
+        :key="plan.id"
+        class="plan-option"
+        :class="{ selected: selectedPlan === plan.id }"
+        @click="selectPlan(plan)"
+      >
+        <div class="plan-badge" v-if="plan.duration >= 120">
+          <van-tag type="danger">推荐</van-tag>
+        </div>
+
+        <div class="plan-header">
+          <h3>{{ plan.name }}</h3>
+          <div class="plan-price">
+            <span class="currency">¥</span>
+            <span class="amount">{{ plan.price }}</span>
+            <span class="unit">/人</span>
+          </div>
+        </div>
+
+        <div class="plan-duration">
+          <van-icon name="clock-o" />
+          <span>{{ plan.duration }}分钟</span>
+        </div>
+
+        <div class="plan-description" v-if="plan.description">
+          {{ plan.description }}
+        </div>
+
+        <div class="plan-features">
+          <div class="feature-item">
+            <van-icon name="success" color="#07c160" />
+            <span>无限点餐</span>
+          </div>
+          <div class="feature-item" v-if="plan.maxOrders">
+            <van-icon name="success" color="#07c160" />
+            <span>最多{{ plan.maxOrders }}次点单</span>
+          </div>
+          <div class="feature-item">
+            <van-icon name="success" color="#07c160" />
+            <span>含酒水饮料</span>
+          </div>
+        </div>
+
+        <van-icon name="checked" class="check-icon" v-if="selectedPlan === plan.id" />
+      </div>
+    </div>
+
+    <div class="guest-selector">
+      <div class="selector-label">就餐人数</div>
+      <van-stepper v-model="guestCount" min="1" max="20" integer />
+    </div>
+
+    <div class="total-price">
+      <div class="price-breakdown">
+        <span>小计</span>
+        <span>¥{{ subtotal }}</span>
+      </div>
+      <div class="price-breakdown total">
+        <span>合计</span>
+        <span>¥{{ totalAmount }}</span>
+      </div>
+    </div>
+
+    <div class="action-bar">
+      <van-button type="primary" block round size="large" @click="confirmSelection" :disabled="!selectedPlan">
+        开始放题
+      </van-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { showToast, showConfirmDialog } from 'vant'
+import { useBuffetStore, type BuffetPlan } from '@/store/modules/buffet'
+
+const route = useRoute()
+const router = useRouter()
+const buffetStore = useBuffetStore()
+
+const selectedPlan = ref('')
+const guestCount = ref(2)
+
+const activePlans = computed(() => buffetStore.activePlans)
+const selectedPlanData = computed(() =>
+  activePlans.value.find(p => p.id === selectedPlan.value)
+)
+const subtotal = computed(() =>
+  selectedPlanData.value ? selectedPlanData.value.price * guestCount.value : 0
+)
+const totalAmount = computed(() => subtotal.value)
+
+const selectPlan = (plan: BuffetPlan) => {
+  selectedPlan.value = plan.id
+}
+
+const confirmSelection = async () => {
+  if (!selectedPlanData.value) return
+
+  try {
+    await showConfirmDialog({
+      title: '确认开始放题',
+      message: `方案: ${selectedPlanData.value.name}\n人数: ${guestCount.value}人\n金额: ¥${totalAmount.value}\n\n开始后将立即计时,确认开始吗?`
+    })
+
+    // 获取桌位信息(从路由或store)
+    const tableId = route.query.tableId as string || 'table_1'
+    const tableName = route.query.tableName as string || 'A1'
+
+    // 开始放题会话
+    buffetStore.startSession(selectedPlanData.value, tableId, tableName)
+
+    showToast('放题已开始!')
+
+    // 跳转到菜单页面
+    router.push({
+      path: '/menu',
+      query: { buffet: '1' }
+    })
+
+  } catch {
+    // 用户取消
+  }
+}
+
+onMounted(() => {
+  buffetStore.loadPlans()
+})
+</script>
+
+<style scoped>
+.buffet-select {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 100px;
+}
+
+.intro-banner {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  text-align: center;
+  padding: 30px 20px;
+}
+
+.intro-banner h2 {
+  margin: 12px 0 8px;
+  font-size: 22px;
+}
+
+.intro-banner p {
+  margin: 0;
+  font-size: 14px;
+  opacity: 0.9;
+}
+
+.plans-container {
+  padding: 16px 12px;
+}
+
+.plan-option {
+  position: relative;
+  background: #fff;
+  border-radius: 16px;
+  padding: 20px;
+  margin-bottom: 16px;
+  border: 2px solid transparent;
+  transition: all 0.3s;
+  cursor: pointer;
+}
+
+.plan-option.selected {
+  border-color: #1989fa;
+  box-shadow: 0 4px 12px rgba(25, 137, 250, 0.2);
+}
+
+.plan-badge {
+  position: absolute;
+  top: 12px;
+  right: 12px;
+}
+
+.plan-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+}
+
+.plan-header h3 {
+  margin: 0;
+  font-size: 18px;
+  font-weight: bold;
+}
+
+.plan-price {
+  display: flex;
+  align-items: baseline;
+}
+
+.plan-price .currency {
+  font-size: 14px;
+  color: #ee0a24;
+}
+
+.plan-price .amount {
+  font-size: 24px;
+  font-weight: bold;
+  color: #ee0a24;
+  margin: 0 2px;
+}
+
+.plan-price .unit {
+  font-size: 12px;
+  color: #999;
+}
+
+.plan-duration {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 14px;
+  color: #666;
+  margin-bottom: 12px;
+}
+
+.plan-description {
+  font-size: 13px;
+  color: #999;
+  margin-bottom: 12px;
+  line-height: 1.5;
+}
+
+.plan-features {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 12px;
+}
+
+.feature-item {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 13px;
+  color: #666;
+}
+
+.check-icon {
+  position: absolute;
+  bottom: 16px;
+  right: 16px;
+  font-size: 24px;
+  color: #1989fa;
+}
+
+.guest-selector {
+  background: #fff;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding: 16px;
+  margin: 0 12px 16px;
+  border-radius: 12px;
+}
+
+.selector-label {
+  font-size: 15px;
+  font-weight: 500;
+}
+
+.total-price {
+  background: #fff;
+  padding: 16px;
+  margin: 0 12px 16px;
+  border-radius: 12px;
+}
+
+.price-breakdown {
+  display: flex;
+  justify-content: space-between;
+  font-size: 14px;
+  margin-bottom: 8px;
+}
+
+.price-breakdown.total {
+  font-size: 18px;
+  font-weight: bold;
+  color: #ee0a24;
+  padding-top: 8px;
+  border-top: 1px dashed #eee;
+  margin-bottom: 0;
+}
+
+.action-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  padding: 12px 16px;
+  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
+}
+</style>

+ 304 - 363
src/views/index/index.vue

@@ -1,329 +1,295 @@
 <template>
   <div class="index-page">
-    <!-- 导航栏 -->
-    <van-nav-bar title="LINE Order" fixed :border="false" :safe-area-inset-top="true">
-      <template #right>
-        <van-icon name="search" size="18" @click="goToSearch" />
-      </template>
-    </van-nav-bar>
+    <!-- 顶部栏 -->
+    <div class="top-bar">
+      <div class="top-row">
+        <div class="location-bar" @click="showLocationPicker = true">
+          <van-icon name="location-o" />
+          <span class="location-text">{{ currentLocation }}</span>
+          <van-icon name="arrow-down" size="12" />
+        </div>
+        <LanguageSwitcher compact />
+      </div>
+      <SearchBar
+        v-model="searchKeyword"
+        :placeholder="t('search.placeholder') || '搜索店铺、菜品'"
+        @search="handleSearch"
+      />
+    </div>
 
     <!-- 下拉刷新 -->
     <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
-      <div class="page-content" style="padding-top: 46px">
-        <!-- 轮播图 -->
-        <van-swipe class="banner-swipe" :autoplay="3000" indicator-color="white" lazy-render>
+      <div class="page-content">
+        <!-- Banner轮播 -->
+        <van-swipe class="banner-swipe" :autoplay="3000" indicator-color="#fff">
           <van-swipe-item v-for="banner in bannerList" :key="banner.id">
-            <YImage
-              :src="banner.image"
-              width="100%"
-              height="180px"
-              fit="cover"
-              @click="handleBannerClick(banner)"
-            />
+            <div class="banner-item" @click="handleBannerClick(banner)">
+              <van-image
+                v-if="banner.image"
+                :src="banner.image"
+                width="100%"
+                height="160"
+                fit="cover"
+              />
+              <div v-else class="banner-placeholder">
+                <van-icon name="photo-o" size="48" />
+              </div>
+            </div>
           </van-swipe-item>
         </van-swipe>
 
-        <!-- 公告 -->
-        <van-notice-bar
-          v-if="notice"
-          left-icon="volume-o"
-          :text="notice"
-          background="#fff7f0"
-          color="#ed6a0c"
-          :scrollable="true"
-        />
-
-        <!-- 分类导航 -->
+        <!-- 金刚区 (分类) -->
         <div class="category-section">
           <div class="category-grid">
             <div
               v-for="category in categories"
               :key="category.id"
               class="category-item"
-              @click="goToCategory(category)"
+              @click="handleCategoryClick(category)"
             >
-              <div class="category-icon" :style="{ background: `${category.color}15` }">
-                <van-icon :name="category.icon" size="28" :color="category.color" />
+              <div class="category-icon" :style="{ background: category.color + '20' }">
+                <van-icon :name="category.icon" size="24" :color="category.color" />
               </div>
               <div class="category-name">{{ category.name }}</div>
             </div>
           </div>
         </div>
 
-        <!-- 快捷入口 -->
-        <div class="quick-entry">
-          <van-grid :column-num="4" :border="false">
-            <van-grid-item icon="coupon-o" text="优惠券" @click="$router.push('/coupons')" />
-            <van-grid-item icon="orders-o" text="我的订单" @click="$router.push('/order')" />
-            <van-grid-item icon="star-o" text="积分商城" @click="showToast('功能开发中')" />
-            <van-grid-item icon="service-o" text="在线客服" @click="showToast('功能开发中')" />
-          </van-grid>
-        </div>
-
-        <!-- 今日推荐 -->
-        <div class="section">
-          <div class="section-header">
-            <div class="section-title">
-              <van-icon name="fire-o" color="#ee0a24" size="20" />
-              <h3>{{ $t('index.todayRecommend') || '今日推荐' }}</h3>
-            </div>
-            <van-button
-              type="primary"
-              size="small"
-              plain
-              round
-              icon="arrow"
-              @click="$router.push('/menu')"
-            >
-              {{ $t('common.viewMore') || '更多' }}
-            </van-button>
-          </div>
-
-          <!-- 骨架屏 -->
-          <van-skeleton :row="3" :loading="loading">
-            <div class="goods-list">
-              <GoodsCard
-                v-for="goods in recommendList"
-                :key="goods.id"
-                :goods="goods"
-                @click="goToDetail(goods)"
-                @add="addToCart(goods)"
-              />
-            </div>
-          </van-skeleton>
-        </div>
-
-        <!-- 热销商品 -->
-        <div class="section">
+        <!-- 推荐模块 -->
+        <div class="recommend-section">
           <div class="section-header">
-            <div class="section-title">
-              <van-icon name="hot-o" color="#ff976a" size="20" />
-              <h3>{{ $t('index.hotSales') || '热销商品' }}</h3>
-            </div>
+            <h3>{{ $t('index.nearbyHot') }}</h3>
+            <span class="more" @click="handleViewMore">{{ $t('common.viewMore') }} <van-icon name="arrow" /></span>
           </div>
-          <div class="goods-grid">
-            <div
-              v-for="goods in hotList"
-              :key="goods.id"
-              class="goods-grid-item"
-              @click="goToDetail(goods)"
-            >
-              <div class="grid-image-wrapper">
-                <YImage :src="goods.image" width="100%" height="120px" radius="8" fit="cover" />
-                <van-tag v-if="goods.sales" type="danger" class="sales-tag">
-                  已售{{ goods.sales }}
-                </van-tag>
-              </div>
-              <div class="grid-goods-info">
-                <div class="grid-goods-name">{{ goods.name }}</div>
-                <div class="grid-goods-footer">
-                  <span class="grid-goods-price">¥{{ goods.price }}</span>
-                  <van-icon name="add" class="add-icon" @click.stop="addToCart(goods)" />
-                </div>
-              </div>
-            </div>
+          <div class="shop-list">
+            <ShopCard
+              v-for="shop in recommendShops"
+              :key="shop.id"
+              :shop="shop"
+              @click="handleShopClick"
+            />
           </div>
         </div>
 
-        <!-- 占位 -->
-        <div style="height: 80px"></div>
+        <!-- Loading -->
+        <div v-if="loading" class="loading-more">
+          <van-loading type="spinner" size="24" />
+          <span>加载中...</span>
+        </div>
       </div>
     </van-pull-refresh>
 
     <!-- 底部导航 -->
-    <YTabBar />
-
-    <!-- 回到顶部 -->
-    <van-back-top target=".index-page" />
+    <van-tabbar v-model="activeTab" fixed placeholder>
+      <van-tabbar-item icon="home-o" to="/index">{{ $t('index.home') }}</van-tabbar-item>
+      <van-tabbar-item icon="orders-o" to="/order">{{ $t('order.title') }}</van-tabbar-item>
+      <van-tabbar-item icon="user-o" to="/mine">{{ $t('mine.title') }}</van-tabbar-item>
+    </van-tabbar>
+
+    <!-- 位置选择弹窗 -->
+    <van-popup v-model:show="showLocationPicker" position="bottom" round>
+      <van-area
+        title="选择送餐地址"
+        :area-list="areaList"
+        @confirm="handleLocationConfirm"
+        @cancel="showLocationPicker = false"
+      />
+    </van-popup>
   </div>
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
+import { ref, computed, onMounted } from 'vue'
 import { useRouter } from 'vue-router'
+import { useI18n } from 'vue-i18n'
 import { showToast } from 'vant'
-import { useCartStore } from '@/store/index'
-import { useLoading } from '@/composables/useLoading'
-import YImage from '@/components/common/YImage.vue'
-import YTabBar from '@/components/common/YTabBar.vue'
-import GoodsCard from '@/components/common/GoodsCard.vue'
+import { useAppStore } from '@/store/modules/app'
+import SearchBar from '@/components/SearchBar.vue'
+import ShopCard, { type Shop } from '@/components/ShopCard.vue'
+import LanguageSwitcher from '@/components/LanguageSwitcher.vue'
 
 const router = useRouter()
-const cartStore = useCartStore()
-const { loading } = useLoading()
+const appStore = useAppStore()
 
-// 下拉刷新
+const activeTab = ref(0)
 const refreshing = ref(false)
+const loading = ref(false)
+const searchKeyword = ref('')
+const currentLocation = ref('东京都渋谷区')
+const showLocationPicker = ref(false)
+
+// Mock数据
+const areaList = ref({
+  province_list: {},
+  city_list: {},
+  county_list: {}
+})
 
-// 轮播图数据
 const bannerList = ref([
   {
-    id: 1,
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-1.jpeg',
-    link: '/menu?categoryId=1'
+    id: '1',
+    image: '',
+    title: '新春优惠',
+    link: ''
   },
   {
-    id: 2,
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-2.jpeg',
-    link: '/menu?categoryId=2'
-  },
-  {
-    id: 3,
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/apple-3.jpeg',
-    link: '/menu?categoryId=3'
+    id: '2',
+    image: '',
+    title: '限时特惠',
+    link: ''
   }
 ])
 
-// 公告
-const notice = ref('🎉 欢迎使用LINE订餐系统! 首次下单立减10元! 新用户专享优惠!')
-
-// 分类数据
-const categories = ref([
-  { id: 1, name: '热销', icon: 'fire-o', color: '#ee0a24' },
-  { id: 2, name: '新品', icon: 'gift-o', color: '#07c160' },
-  { id: 3, name: '饮品', icon: 'hot-o', color: '#ff976a' },
-  { id: 4, name: '小食', icon: 'goods-collect-o', color: '#ffa940' },
-  { id: 5, name: '套餐', icon: 'bag-o', color: '#1989fa' },
-  { id: 6, name: '优惠', icon: 'label-o', color: '#ed6a0c' },
-  { id: 7, name: '积分', icon: 'medal-o', color: '#f2826a' },
-  { id: 8, name: '更多', icon: 'ellipsis', color: '#969799' }
+const { t } = useI18n()
+
+const categories = computed(() => [
+  { id: '1', name: t('index.categories.japanese'), icon: 'fire-o', color: '#ff6b6b' },
+  { id: '2', name: t('index.categories.chinese'), icon: 'hot-o', color: '#ee0a24' },
+  { id: '3', name: t('index.categories.western'), icon: 'gem-o', color: '#ff976a' },
+  { id: '4', name: t('index.categories.fastFood'), icon: 'shopping-cart-o', color: '#07c160' },
+  { id: '5', name: t('index.categories.dessert'), icon: 'gift-o', color: '#ff6bcb' },
+  { id: '6', name: t('index.categories.drinks'), icon: 'service-o', color: '#1989fa' },
+  { id: '7', name: t('index.categories.favorites'), icon: 'star-o', color: '#ffbb00' },
+  { id: '8', name: t('index.categories.coupons'), icon: 'coupon-o', color: '#07c160' }
 ])
 
-// 推荐商品
-const recommendList = ref([
+const recommendShops = ref<Shop[]>([
   {
-    id: 1,
-    name: '招牌咖啡',
-    desc: '香浓醇厚,回味无穷',
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
-    price: '28.00',
-    originalPrice: '38.00',
-    tag: '热销',
-    sales: 1234
+    id: 'shop_1',
+    name: 'XXX日本料理',
+    logo: '',
+    tags: ['日料', '寿司'],
+    rating: 4.8,
+    monthlySales: 1200,
+    deliveryTime: '25分钟',
+    deliveryFee: 3,
+    minPrice: 20
   },
   {
-    id: 2,
-    name: '抹茶拿铁',
-    desc: '清新抹茶香,浓郁奶香味',
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
-    price: '32.00',
-    tag: '新品',
-    sales: 856
+    id: 'shop_2',
+    name: 'YYY拉面馆',
+    logo: '',
+    tags: ['拉面', '快餐'],
+    rating: 4.6,
+    monthlySales: 856,
+    deliveryTime: '30分钟',
+    deliveryFee: 0,
+    minPrice: 15
   },
   {
-    id: 3,
-    name: '草莓奶昔',
-    desc: '新鲜草莓,香甜可口',
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
-    price: '26.00',
-    sales: 645
+    id: 'shop_3',
+    name: 'ZZZ烧烤店',
+    logo: '',
+    tags: ['烧烤', '日料'],
+    rating: 4.9,
+    monthlySales: 2300,
+    deliveryTime: '35分钟',
+    deliveryFee: 5,
+    minPrice: 30
   }
 ])
 
-// 热销商品
-const hotList = ref([
-  {
-    id: 4,
-    name: '美式咖啡',
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
-    price: '22.00',
-    sales: 2345
-  },
-  {
-    id: 5,
-    name: '卡布奇诺',
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
-    price: '26.00',
-    sales: 1876
-  },
-  {
-    id: 6,
-    name: '拿铁咖啡',
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
-    price: '28.00',
-    sales: 1654
-  },
-  {
-    id: 7,
-    name: '摩卡咖啡',
-    image: 'https://fastly.jsdelivr.net/npm/@vant/assets/cat.jpeg',
-    price: '30.00',
-    sales: 1432
-  }
-])
-
-// 加载页面数据
-const loadPageData = async () => {
-  loading.value = true
-  try {
-    // 模拟API调用
-    await new Promise((resolve) => setTimeout(resolve, 500))
-    // const [banners, recommend, hot] = await Promise.all([
-    //   getBanners(),
-    //   getRecommendGoods(),
-    //   getHotGoods()
-    // ])
-  } catch (error) {
-    console.error('加载失败:', error)
-    showToast('加载失败')
-  } finally {
-    loading.value = false
-  }
-}
-
-// 下拉刷新
 const onRefresh = async () => {
-  await loadPageData()
+  // 模拟刷新
+  await new Promise(resolve => setTimeout(resolve, 1000))
   refreshing.value = false
   showToast('刷新成功')
 }
 
-// 轮播图点击
-const handleBannerClick = (banner) => {
-  if (banner.link) {
-    router.push(banner.link)
+const handleSearch = (keyword: string) => {
+  if (!keyword.trim()) {
+    showToast('请输入搜索关键词')
+    return
   }
+  showToast(`搜索: ${keyword}`)
 }
 
-// 去搜索
-const goToSearch = () => {
-  showToast('搜索功能开发中...')
+const handleBannerClick = (banner: any) => {
+  showToast(`点击Banner: ${banner.title}`)
 }
 
-// 去分类
-const goToCategory = (category) => {
+const handleCategoryClick = (category: any) => {
+  if (category.id === '7') {
+    // 我的收藏
+    router.push('/mine')
+  } else if (category.id === '8') {
+    // 领券中心
+    showToast('领券中心')
+  } else {
+    showToast(`分类: ${category.name}`)
+  }
+}
+
+const handleShopClick = (shop: Shop) => {
+  // 进入店铺模式
+  appStore.enterShopMode({
+    id: shop.id,
+    name: shop.name,
+    companyId: '',
+    address: '',
+    status: 'operating'
+  })
+  
   router.push({
     path: '/menu',
-    query: { categoryId: category.id }
+    query: { shopId: shop.id }
   })
 }
 
-// 去详情
-const goToDetail = (goods) => {
-  router.push({
-    path: '/menu/detail',
-    query: { id: goods.id }
-  })
+const handleViewMore = () => {
+  showToast('查看更多店铺')
 }
 
-// 加入购物车
-const addToCart = (goods) => {
-  cartStore.addToCart({
-    id: goods.id,
-    name: goods.name,
-    image: goods.image,
-    price: parseFloat(goods.price),
-    quantity: 1
-  })
-  showToast({
-    message: '已加入购物车',
-    icon: 'success'
-  })
+const handleLocationConfirm = (values: any) => {
+  currentLocation.value = values[2]?.name || '东京都渋谷区'
+  showLocationPicker.value = false
 }
 
-// 页面加载
-onMounted(() => {
-  loadPageData()
+const requestLocation = async () => {
+  try {
+    if ('geolocation' in navigator) {
+      const position = await new Promise<GeolocationPosition>((resolve, reject) => {
+        navigator.geolocation.getCurrentPosition(resolve, reject, {
+          enableHighAccuracy: false,
+          timeout: 5000,
+          maximumAge: 300000
+        })
+      })
+      
+      const { latitude, longitude } = position.coords
+      
+      // 调用反向地理编码获取地址
+      const { reverseGeocode, formatAddress } = await import('@/utils/geocoding')
+      const geocodeResult = await reverseGeocode(latitude, longitude)
+      
+      if (geocodeResult) {
+        // 根据当前语言格式化地址
+        const formattedAddress = formatAddress(geocodeResult, appStore.lang)
+        currentLocation.value = formattedAddress
+      } else {
+        // 编码失败,显示通用文本
+        currentLocation.value = '当前位置附近'
+      }
+      
+      // 存储位置到store
+      appStore.setLocation({ latitude, longitude })
+    }
+  } catch (error) {
+    console.log('用户拒绝了位置权限或定位失败', error)
+    // 使用默认位置(不更改 currentLocation)
+  }
+}
+
+onMounted(async () => {
+  // 确保进入平台模式
+  appStore.enterPlatformMode()
+  
+  // 首次访问时请求位置权限
+  const hasRequestedLocation = localStorage.getItem('hasRequestedLocation')
+  if (!hasRequestedLocation) {
+    await requestLocation()
+    localStorage.setItem('hasRequestedLocation', 'true')
+  }
 })
 </script>
 
@@ -331,173 +297,148 @@ onMounted(() => {
 .index-page {
   min-height: 100vh;
   background: #f5f5f5;
-  padding-bottom: 60px;
-  overflow-x: hidden;
 }
 
-/* 轮播图 */
-.banner-swipe {
-  background: white;
-}
-
-/* 分类区域 */
-.category-section {
-  background: white;
-  padding: 16px 0;
-  margin-top: 8px;
-}
-
-.category-grid {
-  display: grid;
-  grid-template-columns: repeat(4, 1fr);
-  gap: 16px 8px;
-  padding: 0 8px;
-}
-
-.category-item {
+.top-bar {
+  position: fixed;
+  top: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  padding: 12px 16px;
+  z-index: 100;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
   display: flex;
   flex-direction: column;
-  align-items: center;
-  padding: 8px;
-  cursor: pointer;
-  transition: transform 0.2s;
+  gap: 12px;
 }
 
-.category-item:active {
-  transform: scale(0.95);
+.top-row {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  gap: 12px;
 }
 
-.category-icon {
-  width: 52px;
-  height: 52px;
+.location-bar {
   display: flex;
   align-items: center;
-  justify-content: center;
-  border-radius: 50%;
-  margin-bottom: 8px;
-  transition: all 0.3s;
+  gap: 4px;
+  flex: 1;
+  cursor: pointer;
 }
 
-.category-name {
-  font-size: 12px;
-  color: #323233;
+.location-text {
+  flex: 1;
+  font-size: 14px;
   font-weight: 500;
+  color: #333;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
 }
 
-/* 快捷入口 */
-.quick-entry {
-  background: white;
-  margin-top: 8px;
+.page-content {
+  padding-top: 100px;
+  padding-bottom: 60px;
 }
 
-/* 区块 */
-.section {
-  margin-top: 8px;
-  background: white;
-  padding: 16px;
-  border-radius: 8px 8px 0 0;
+.banner-swipe {
+  margin: 0 12px 12px;
+  border-radius: 12px;
+  overflow: hidden;
 }
 
-.section-header {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-  margin-bottom: 16px;
+.banner-item {
+  background: #f5f5f5;
+  cursor: pointer;
 }
 
-.section-title {
+.banner-placeholder {
+  height: 160px;
   display: flex;
   align-items: center;
-  gap: 6px;
+  justify-content: center;
+  color: #ddd;
 }
 
-.section-title h3 {
-  font-size: 17px;
-  font-weight: 600;
-  color: #323233;
-  margin: 0;
+.category-section {
+  background: #fff;
+  margin: 0 12px 12px;
+  border-radius: 12px;
+  padding: 16px;
+}
+
+.category-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 16px;
 }
 
-/* 商品列表 */
-.goods-list {
+.category-item {
   display: flex;
   flex-direction: column;
-  gap: 12px;
+  align-items: center;
+  gap: 8px;
+  cursor: pointer;
 }
 
-/* 商品网格 */
-.goods-grid {
-  display: grid;
-  grid-template-columns: repeat(2, 1fr);
-  gap: 12px;
+.category-icon {
+  width: 48px;
+  height: 48px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  transition: transform 0.3s;
 }
 
-.goods-grid-item {
-  background: #fafafa;
-  border-radius: 8px;
-  overflow: hidden;
-  cursor: pointer;
-  transition: all 0.3s;
+.category-item:active .category-icon {
+  transform: scale(0.9);
 }
 
-.goods-grid-item:active {
-  transform: translateY(-2px);
-  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+.category-name {
+  font-size: 12px;
+  color: #666;
 }
 
-.grid-image-wrapper {
-  position: relative;
+.recommend-section {
+  margin: 0 12px;
 }
 
-.sales-tag {
-  position: absolute;
-  top: 6px;
-  right: 6px;
-  font-size: 10px;
-  padding: 2px 6px;
+.section-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
 }
 
-.grid-goods-info {
-  padding: 10px;
+.section-header h3 {
+  margin: 0;
+  font-size: 18px;
+  font-weight: bold;
+  color: #333;
 }
 
-.grid-goods-name {
+.more {
   font-size: 14px;
-  color: #323233;
-  margin-bottom: 8px;
-  display: -webkit-box;
-  -webkit-line-clamp: 1;
-  -webkit-box-orient: vertical;
-  overflow: hidden;
-  font-weight: 500;
+  color: #1989fa;
+  cursor: pointer;
 }
 
-.grid-goods-footer {
+.shop-list {
   display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
-
-.grid-goods-price {
-  font-size: 17px;
-  font-weight: 600;
-  color: #ee0a24;
+  flex-direction: column;
+  gap: 12px;
 }
 
-.add-icon {
-  width: 24px;
-  height: 24px;
-  background: #ee0a24;
-  color: white;
-  border-radius: 50%;
+.loading-more {
   display: flex;
   align-items: center;
   justify-content: center;
+  gap: 8px;
+  padding: 20px;
+  color: #999;
   font-size: 14px;
-  transition: transform 0.2s;
-}
-
-.add-icon:active {
-  transform: scale(1.2);
 }
 </style>

+ 61 - 32
src/views/login/components/PasswordLogin.vue

@@ -1,31 +1,23 @@
 <template>
   <div class="password-login">
-    <!-- 区号选择和手机号输入 -->
-    <div class="input-group">
-      <van-field
-        v-model="areaCodeDisplay"
-        readonly
-        :label="$t('login.areaCode')"
-        :placeholder="$t('login.selectAreaCode')"
-        @click="showAreaCodePicker = true"
-      >
-        <template #input>
-          <div class="area-code-display">
-            <span>+{{ areaCode }}</span>
-            <van-icon name="arrow-down" />
-          </div>
-        </template>
-      </van-field>
-    </div>
-
-    <div class="input-group">
-      <van-field
-        v-model="mobile"
-        type="tel"
-        :label="$t('login.phone')"
-        :placeholder="$t('login.enterPhone')"
-        clearable
-      />
+    <!-- 区号选择和手机号输入(同一行) -->
+    <div class="phone-input-group">
+      <div class="area-code-field" @click="showAreaCodePicker = true">
+        <div class="area-code-label">{{ $t('login.areaCode') }}</div>
+        <div class="area-code-value">
+          <span>+{{ areaCode }}</span>
+          <van-icon name="arrow-down" size="12" />
+        </div>
+      </div>
+      <div class="phone-field">
+        <van-field
+          v-model="mobile"
+          type="tel"
+          :label="$t('login.phone')"
+          :placeholder="$t('login.enterPhone')"
+          clearable
+        />
+      </div>
     </div>
 
     <!-- 密码输入 -->
@@ -203,15 +195,52 @@ const handleRegister = () => {
 
 <style scoped lang="scss">
 .password-login {
-  .input-group {
+  .phone-input-group {
+    display: flex;
+    align-items: flex-end;
+    gap: 12px;
     margin-bottom: 20px;
   }
 
-  .area-code-display {
-    display: flex;
-    align-items: center;
-    gap: 4px;
-    color: #323233;
+  .area-code-field {
+    width: 100px;
+    flex-shrink: 0;
+    padding: 10px 16px;
+    background: #f7f8fa;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: background 0.3s;
+
+    &:active {
+      background: #ebedf0;
+    }
+
+    .area-code-label {
+      font-size: 12px;
+      color: #646566;
+      margin-bottom: 4px;
+    }
+
+    .area-code-value {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      font-size: 14px;
+      color: #323233;
+      font-weight: 500;
+    }
+  }
+
+  .phone-field {
+    flex: 1;
+
+    :deep(.van-cell) {
+      padding: 10px 16px;
+    }
+  }
+
+  .input-group {
+    margin-bottom: 20px;
   }
 
   .forgot-password {

+ 57 - 32
src/views/login/components/PhoneCodeLogin.vue

@@ -1,31 +1,23 @@
 <template>
   <div class="phone-code-login">
-    <!-- 区号选择和手机号输入 -->
-    <div class="input-group">
-      <van-field
-        v-model="areaCodeDisplay"
-        readonly
-        :label="$t('login.areaCode')"
-        :placeholder="$t('login.selectAreaCode')"
-        @click="showAreaCodePicker = true"
-      >
-        <template #input>
-          <div class="area-code-display">
-            <span>+{{ areaCode }}</span>
-            <van-icon name="arrow-down" />
-          </div>
-        </template>
-      </van-field>
-    </div>
-
-    <div class="input-group">
-      <van-field
-        v-model="mobile"
-        type="tel"
-        :label="$t('login.phone')"
-        :placeholder="$t('login.enterPhone')"
-        clearable
-      />
+    <!-- 区号选择和手机号输入(同一行) -->
+    <div class="phone-input-group">
+      <div class="area-code-field" @click="showAreaCodePicker = true">
+        <div class="area-code-label">{{ $t('login.areaCode') }}</div>
+        <div class="area-code-value">
+          <span>+{{ areaCode }}</span>
+          <van-icon name="arrow-down" size="12" />
+        </div>
+      </div>
+      <div class="phone-field">
+        <van-field
+          v-model="mobile"
+          type="tel"
+          :label="$t('login.phone')"
+          :placeholder="$t('login.enterPhone')"
+          clearable
+        />
+      </div>
     </div>
 
     <div class="tips">
@@ -252,15 +244,48 @@ onUnmounted(() => {
 
 <style scoped lang="scss">
 .phone-code-login {
-  .input-group {
+  .phone-input-group {
+    display: flex;
+    align-items: flex-end;
+    gap: 12px;
     margin-bottom: 20px;
   }
 
-  .area-code-display {
-    display: flex;
-    align-items: center;
-    gap: 4px;
-    color: #323233;
+  .area-code-field {
+    width: 100px;
+    flex-shrink: 0;
+    padding: 10px 16px;
+    background: #f7f8fa;
+    border-radius: 4px;
+    cursor: pointer;
+    transition: background 0.3s;
+
+    &:active {
+      background: #ebedf0;
+    }
+
+    .area-code-label {
+      font-size: 12px;
+      color: #646566;
+      margin-bottom: 4px;
+    }
+
+    .area-code-value {
+      display: flex;
+      align-items: center;
+      justify-content: space-between;
+      font-size: 14px;
+      color: #323233;
+      font-weight: 500;
+    }
+  }
+
+  .phone-field {
+    flex: 1;
+
+    :deep(.van-cell) {
+      padding: 10px 16px;
+    }
   }
 
   .tips {

+ 252 - 0
src/views/merchant/buffet-plans.vue

@@ -0,0 +1,252 @@
+<template>
+  <div class="buffet-plans">
+    <van-nav-bar title="放题方案管理" left-arrow @click-left="$router.back()" fixed placeholder>
+      <template #right>
+        <van-icon name="plus" size="20" @click="addPlan" />
+      </template>
+    </van-nav-bar>
+
+    <div class="plans-list">
+      <div v-for="plan in plans" :key="plan.id" class="plan-card">
+        <div class="plan-header">
+          <div class="plan-title">
+            <h3>{{ plan.name }}</h3>
+            <van-tag :type="plan.status === 'active' ? 'success' : 'default'">
+              {{ plan.status === 'active' ? '启用中' : '已停用' }}
+            </van-tag>
+          </div>
+          <van-switch
+            :model-value="plan.status === 'active'"
+            size="20"
+            @update:model-value="toggleStatus(plan)"
+          />
+        </div>
+
+        <div class="plan-info">
+          <div class="info-item">
+            <van-icon name="clock-o" />
+            <span>{{ plan.duration }}分钟</span>
+          </div>
+          <div class="info-item price">
+            <van-icon name="gold-coin-o" />
+            <span>¥{{ plan.price }}</span>
+          </div>
+        </div>
+
+        <div class="plan-desc" v-if="plan.description">
+          {{ plan.description }}
+        </div>
+
+        <div class="plan-details">
+          <div class="detail-row" v-if="plan.maxOrders">
+            <span>点单限制:</span>
+            <span>{{ plan.maxOrders }}次</span>
+          </div>
+          <div class="detail-row">
+            <span>提醒时间:</span>
+            <span>{{ plan.reminderMinutes.join('分钟、') }}分钟</span>
+          </div>
+        </div>
+
+        <div class="plan-actions">
+          <van-button size="small" plain @click="editPlan(plan)">编辑</van-button>
+          <van-button size="small" plain type="primary" @click="viewStats(plan)">数据统计</van-button>
+          <van-button size="small" plain type="danger" @click="deletePlan(plan)">删除</van-button>
+        </div>
+      </div>
+
+      <van-empty v-if="plans.length === 0" description="暂无放题方案" />
+    </div>
+
+    <!-- 编辑弹窗 -->
+    <van-popup v-model:show="showEdit" position="bottom" :style="{ height: '80%' }">
+      <div class="edit-form">
+        <van-nav-bar title="编辑方案" left-text="取消" right-text="保存" @click-left="showEdit = false" @click-right="savePlan" />
+        <van-form>
+          <van-cell-group inset>
+            <van-field v-model="editForm.name" label="方案名称" placeholder="请输入方案名称" required />
+            <van-field v-model="editForm.duration" type="number" label="时长(分钟)" placeholder="90" required />
+            <van-field v-model="editForm.price" type="number" label="价格(¥)" placeholder="2980" required />
+            <van-field v-model="editForm.description" label="描述" type="textarea" placeholder="方案描述" rows="2" />
+            <van-field v-model="editForm.maxOrders" type="number" label="点单限制" placeholder="0=不限制" />
+            <van-field v-model="reminderInput" label="提醒时间" placeholder="30,10,5" />
+          </van-cell-group>
+        </van-form>
+      </div>
+    </van-popup>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast, showConfirmDialog } from 'vant'
+import { useBuffetStore, type BuffetPlan } from '@/store/modules/buffet'
+
+const router = useRouter()
+const buffetStore = useBuffetStore()
+
+const showEdit = ref(false)
+const editForm = ref({
+  id: '',
+  name: '',
+  duration: '',
+  price: '',
+  description: '',
+  maxOrders: '',
+  status: 'active' as 'active' | 'inactive'
+})
+const reminderInput = ref('30,10,5')
+
+const plans = computed(() => buffetStore.plans)
+
+const addPlan = () => {
+  editForm.value = {
+    id: '',
+    name: '',
+    duration: '',
+    price: '',
+    description: '',
+    maxOrders: '',
+    status: 'active'
+  }
+  reminderInput.value = '30,10,5'
+  showEdit.value = true
+}
+
+const editPlan = (plan: BuffetPlan) => {
+  editForm.value = {
+    id: plan.id,
+    name: plan.name,
+    duration: String(plan.duration),
+    price: String(plan.price),
+    description: plan.description || '',
+    maxOrders: plan.maxOrders ? String(plan.maxOrders) : '0',
+    status: plan.status
+  }
+  reminderInput.value = plan.reminderMinutes.join(',')
+  showEdit.value = true
+}
+
+const savePlan = () => {
+  // TODO: 调用API保存
+  showToast('保存成功')
+  showEdit.value = false
+  buffetStore.loadPlans()
+}
+
+const toggleStatus = async (plan: BuffetPlan) => {
+  plan.status = plan.status === 'active' ? 'inactive' : 'active'
+  showToast(plan.status === 'active' ? '已启用' : '已停用')
+}
+
+const viewStats = (plan: BuffetPlan) => {
+  showToast(`查看「${plan.name}」统计数据`)
+}
+
+const deletePlan = async (plan: BuffetPlan) => {
+  try {
+    await showConfirmDialog({
+      title: '确认删除',
+      message: `确定要删除「${plan.name}」吗?`
+    })
+    showToast('删除成功')
+  } catch {
+    // 用户取消
+  }
+}
+
+// 初始加载
+buffetStore.loadPlans()
+</script>
+
+<style scoped>
+.buffet-plans {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.plans-list {
+  padding: 12px;
+}
+
+.plan-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 16px;
+  margin-bottom: 12px;
+}
+
+.plan-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+}
+
+.plan-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.plan-title h3 {
+  margin: 0;
+  font-size: 16px;
+}
+
+.plan-info {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 8px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 14px;
+  color: #666;
+}
+
+.info-item.price {
+  color: #ee0a24;
+  font-weight: bold;
+}
+
+.plan-desc {
+  font-size: 13px;
+  color: #999;
+  margin-bottom: 12px;
+}
+
+.plan-details {
+  background: #f8f8f8;
+  border-radius: 6px;
+  padding: 8px 12px;
+  margin-bottom: 12px;
+}
+
+.detail-row {
+  display: flex;
+  justify-content: space-between;
+  font-size: 12px;
+  color: #666;
+  margin-bottom: 4px;
+}
+
+.detail-row:last-child {
+  margin-bottom: 0;
+}
+
+.plan-actions {
+  display: flex;
+  gap: 8px;
+}
+
+.edit-form {
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+}
+</style>

+ 203 - 0
src/views/merchant/dashboard.vue

@@ -0,0 +1,203 @@
+<template>
+  <div class="merchant-dashboard">
+    <van-nav-bar
+      title="商家仪表盘"
+      left-arrow
+      @click-left="goBack"
+    />
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <div class="dashboard-content">
+        <!-- 店铺信息 -->
+        <div class="shop-card">
+          <h3>{{ shopInfo.name || '我的店铺' }}</h3>
+          <van-tag :type="shopInfo.isOpen ? 'success' : 'default'">
+            {{ shopInfo.isOpen ? '营业中' : '休息中' }}
+          </van-tag>
+          <p>{{ shopInfo.hours }}</p>
+        </div>
+
+        <!-- 今日数据 -->
+        <div class="stats-section">
+          <h4>今日数据</h4>
+          <van-grid :column-num="2" :border="false">
+            <van-grid-item icon="gold-coin-o" text="今日营业额">
+              <template #text>
+                <div class="grid-value">¥{{ todayStats.revenue }}</div>
+                <div class="grid-label">今日营业额</div>
+              </template>
+            </van-grid-item>
+            <van-grid-item icon="shopping-cart-o" text="今日订单">
+              <template #text>
+                <div class="grid-value">{{ todayStats.orderCount }}</div>
+                <div class="grid-label">今日订单</div>
+              </template>
+            </van-grid-item>
+            <van-grid-item icon="logistics" text="今日外卖">
+              <template #text>
+                <div class="grid-value">{{ todayStats.takeoutCount }}</div>
+                <div class="grid-label">今日外卖</div>
+              </template>
+            </van-grid-item>
+            <van-grid-item icon="shop-o" text="今日堂食">
+              <template #text>
+                <div class="grid-value">{{ todayStats.dineInCount }}</div>
+                <div class="grid-label">今日堂食</div>
+              </template>
+            </van-grid-item>
+          </van-grid>
+        </div>
+
+        <!-- 订单统计 -->
+        <div class="order-stats-section">
+          <h4>订单统计</h4>
+          <div class="order-stats-grid">
+            <div class="stat-card" @click="goToOrders('pending')">
+              <div class="stat-count" style="color: #ff976a">{{ orderStats.pending }}</div>
+              <div class="stat-name">待接单</div>
+            </div>
+            <div class="stat-card" @click="goToOrders('preparing')">
+              <div class="stat-count" style="color: #1989fa">{{ orderStats.preparing }}</div>
+              <div class="stat-name">制作中</div>
+            </div>
+            <div class="stat-card" @click="goToOrders('delivering')">
+              <div class="stat-count" style="color: #07c160">{{ orderStats.delivering }}</div>
+              <div class="stat-name">配送中</div>
+            </div>
+            <div class="stat-card" @click="goToOrders('completed')">
+              <div class="stat-count" style="color: #969799">{{ orderStats.completed }}</div>
+              <div class="stat-name">已完成</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+import { useMerchantStore } from '@/store/modules/merchant'
+
+const router = useRouter()
+const merchantStore = useMerchantStore()
+
+const refreshing = ref(false)
+
+const shopInfo = computed(() => merchantStore.shopInfo)
+const todayStats = computed(() => merchantStore.todayStats)
+const orderStats = computed(() => merchantStore.orderStats)
+
+const loadData = async () => {
+  try {
+    await merchantStore.loadDashboardData()
+  } catch (error) {
+    showToast('加载失败')
+  }
+}
+
+const onRefresh = async () => {
+  await loadData()
+  refreshing.value = false
+  showToast('刷新成功')
+}
+
+const goBack = () => {
+  router.push('/mine')
+}
+
+const goToOrders = (status: string) => {
+  showToast('订单管理功能开发中')
+}
+
+onMounted(() => {
+  loadData()
+})
+</script>
+
+<style scoped lang="scss">
+.merchant-dashboard {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.dashboard-content {
+  padding: 15px;
+}
+
+.shop-card {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-radius: 12px;
+  padding: 20px;
+  color: #fff;
+  margin-bottom: 15px;
+
+  h3 {
+    margin: 0 0 10px 0;
+    font-size: 20px;
+  }
+
+  p {
+    margin: 10px 0 0 0;
+    font-size: 14px;
+    opacity: 0.9;
+  }
+}
+
+.stats-section,
+.order-stats-section {
+  background: #fff;
+  border-radius: 12px;
+  padding: 15px;
+  margin-bottom: 15px;
+
+  h4 {
+    margin: 0 0 15px 0;
+    font-size: 16px;
+    font-weight: 600;
+  }
+}
+
+.grid-value {
+  font-size: 24px;
+  font-weight: bold;
+  color: #323233;
+  margin-bottom: 5px;
+}
+
+.grid-label {
+  font-size: 12px;
+  color: #969799;
+}
+
+.order-stats-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 12px;
+}
+
+.stat-card {
+  text-align: center;
+  padding: 15px;
+  background: #f7f8fa;
+  border-radius: 8px;
+  cursor: pointer;
+
+  &:active {
+    transform: scale(0.95);
+  }
+
+  .stat-count {
+    font-size: 24px;
+    font-weight: bold;
+    margin-bottom: 5px;
+  }
+
+  .stat-name {
+    font-size: 12px;
+    color: #969799;
+  }
+}
+</style>

+ 203 - 0
src/views/merchant/orders.vue

@@ -0,0 +1,203 @@
+<template>
+  <div class="merchant-orders">
+    <van-nav-bar title="订单管理" left-arrow @click-left="$router.back()" fixed placeholder />
+
+    <van-tabs v-model:active="activeTab" sticky @change="onTabChange">
+      <van-tab title="全部" :badge="stats.total > 99 ? '99+' : stats.total" />
+      <van-tab title="待接单" :badge="stats.pending || ''" />
+      <van-tab title="制作中" :badge="stats.preparing || ''" />
+      <van-tab title="配送中" :badge="stats.delivering || ''" />
+      <van-tab title="已完成" />
+    </van-tabs>
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <van-list v-model:loading="loading" :finished="finished" @load="onLoad">
+        <div class="order-list">
+          <div v-for="order in orders" :key="order.id" class="order-card" @click="viewOrder(order)">
+            <div class="order-header">
+              <span class="order-no">#{{ order.orderNo }}</span>
+              <van-tag :type="getStatusType(order.status)">{{ getStatusText(order.status) }}</van-tag>
+            </div>
+            <div class="order-info">
+              <div class="info-item">
+                <van-icon name="user-o" />
+                <span>{{ order.customerName || '顾客' }}</span>
+              </div>
+              <div class="info-item">
+                <van-icon name="clock-o" />
+                <span>{{ formatTime(order.createTime) }}</span>
+              </div>
+              <div class="info-item" v-if="order.tableNo">
+                <van-icon name="shop-o" />
+                <span>{{ order.tableNo }}</span>
+              </div>
+            </div>
+            <div class="order-items">
+              <span v-for="(item, idx) in order.items.slice(0, 2)" :key="idx">
+                {{ item.name }} x{{ item.quantity }}
+              </span>
+              <span v-if="order.items.length > 2">等{{ order.items.length }}件</span>
+            </div>
+            <div class="order-footer">
+              <span class="amount">¥{{ order.amount }}</span>
+              <van-button
+                v-if="order.status === 'pending'"
+                size="small"
+                type="primary"
+                @click.stop="acceptOrder(order)"
+              >
+                接单
+              </van-button>
+            </div>
+          </div>
+        </div>
+      </van-list>
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+
+const router = useRouter()
+
+const activeTab = ref(0)
+const refreshing = ref(false)
+const loading = ref(false)
+const finished = ref(false)
+
+const stats = ref({
+  total: 28,
+  pending: 5,
+  preparing: 8,
+  delivering: 3
+})
+
+const orders = ref([
+  {
+    id: '1',
+    orderNo: '202401120001',
+    customerName: '张三',
+    status: 'pending',
+    amount: 158,
+    createTime: Date.now() - 3 * 60 * 1000,
+    tableNo: 'A1',
+    items: [{ name: '宫保鸡丁', quantity: 1 }, { name: '米饭', quantity: 2 }]
+  }
+])
+
+const getStatusType = (status: string) => {
+  const map: Record<string, any> = {
+    pending: 'warning',
+    preparing: 'primary',
+    delivering: 'success',
+    completed: 'default'
+  }
+  return map[status]
+}
+
+const getStatusText = (status: string) => {
+  const map: Record<string, string> = {
+    pending: '待接单',
+    preparing: '制作中',
+    delivering: '配送中',
+    completed: '已完成'
+  }
+  return map[status]
+}
+
+const formatTime = (time: number) => {
+  const date = new Date(time)
+  return `${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
+}
+
+const onTabChange = () => {}
+const onRefresh = () => {
+  setTimeout(() => {
+    refreshing.value = false
+    showToast('刷新成功')
+  }, 1000)
+}
+const onLoad = () => {
+  loading.value = false
+  finished.value = true
+}
+const viewOrder = (order: any) => {
+  router.push(`/merchant/orders/${order.id}`)
+}
+const acceptOrder = (order: any) => {
+  order.status = 'preparing'
+  stats.value.pending--
+  stats.value.preparing++
+  showToast('已接单')
+}
+</script>
+
+<style scoped>
+.merchant-orders {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.order-list {
+  padding: 12px;
+}
+
+.order-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 12px;
+  margin-bottom: 12px;
+}
+
+.order-header {
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 8px;
+}
+
+.order-no {
+  font-size: 15px;
+  font-weight: bold;
+}
+
+.order-info {
+  display: flex;
+  gap: 16px;
+  margin-bottom: 8px;
+}
+
+.info-item {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 12px;
+  color: #666;
+}
+
+.order-items {
+  font-size: 13px;
+  color: #999;
+  margin-bottom: 12px;
+}
+
+.order-items span:not(:last-child)::after {
+  content: '、';
+}
+
+.order-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-top: 12px;
+  border-top: 1px solid #f5f5f5;
+}
+
+.amount {
+  font-size: 18px;
+  font-weight: bold;
+  color: #ee0a24;
+}
+</style>

+ 113 - 0
src/views/mine/mine.vue

@@ -72,6 +72,30 @@
         </div>
       </div>
 
+      <!-- 身份切换卡片 -->
+      <div v-if="isLogin && availableRoles.length > 1" class="role-switch-card">
+        <div class="card-header">
+          <van-icon name="user-circle-o" size="18" color="#323233" />
+          <span class="card-title">{{ $t('mine.switchIdentity') }}</span>
+          <van-tag type="primary">{{ getRoleLabel(currentRole) }}</van-tag>
+        </div>
+        <div class="role-list">
+          <div
+            v-for="role in availableRoles"
+            :key="role"
+            class="role-item"
+            :class="{ active: currentRole === role }"
+            @click="handleSwitchRole(role)"
+          >
+            <div class="role-item-left">
+              <van-icon :name="getRoleIcon(role)" size="20" />
+              <span class="role-name">{{ getRoleLabel(role) }}</span>
+            </div>
+            <van-icon v-if="currentRole === role" name="success" color="#07c160" size="16" />
+          </div>
+        </div>
+      </div>
+
       <!-- 我的订单 -->
       <div class="section-card">
         <div class="section-title">{{ $t('mine.my-orders') }}</div>
@@ -145,12 +169,22 @@ import { showToast } from 'vant'
 import { useUserStore } from '@/store/modules/user'
 import { getUserInfo, getMineServices } from '@/api/user'
 import { useEnv } from '@/composables/useEnv'
+import { useRole } from '@/composables/useRole'
 import YTabBar from '@/components/common/YTabBar.vue'
 
 const { t } = useI18n()
 const router = useRouter()
 const userStore = useUserStore()
 
+// 角色管理
+const {
+  currentRole,
+  availableRoles,
+  switchRole,
+  getRoleLabel,
+  getRoleIcon
+} = useRole()
+
 // 环境检测
 const {
   shouldShowLineFeatures,
@@ -411,6 +445,25 @@ const checkLogin = () => {
   }
   return true
 }
+
+// 处理角色切换
+const handleSwitchRole = async (role: 'user' | 'merchant' | 'waiter' | 'admin') => {
+  if (role === currentRole.value) return
+
+  const success = await switchRole(role)
+  if (success) {
+    // 切换成功后跳转到对应首页
+    if (role === 'merchant') {
+      router.push('/merchant/dashboard')
+    } else if (role === 'waiter') {
+      router.push('/pos/welcome')
+    } else if (role === 'admin') {
+      router.push('/admin/dashboard')
+    } else {
+      router.push('/index')
+    }
+  }
+}
 </script>
 
 <style scoped lang="scss">
@@ -604,6 +657,66 @@ const checkLogin = () => {
   text-align: center;
 }
 
+.role-switch-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 15px;
+  margin-bottom: 15px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
+}
+
+.card-header {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin-bottom: 12px;
+}
+
+.card-title {
+  flex: 1;
+  font-size: 16px;
+  font-weight: 500;
+  color: #323233;
+}
+
+.role-list {
+  display: flex;
+  flex-direction: column;
+  gap: 8px;
+}
+
+.role-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: #f7f8fa;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: all 0.3s;
+
+  &.active {
+    background: #e8f5e9;
+    border: 1px solid #07c160;
+  }
+
+  &:active {
+    transform: scale(0.98);
+  }
+}
+
+.role-item-left {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.role-name {
+  font-size: 14px;
+  color: #323233;
+  font-weight: 500;
+}
+
 .env-indicator {
   padding: 20px 0;
   text-align: center;

+ 375 - 0
src/views/owner/dashboard.vue

@@ -0,0 +1,375 @@
+<template>
+  <div class="owner-dashboard">
+    <van-nav-bar title="公司总览" fixed placeholder>
+      <template #right>
+        <van-icon name="setting-o" size="20" @click="goToSettings" />
+      </template>
+    </van-nav-bar>
+
+    <!-- 公司信息卡片 -->
+    <div class="company-card">
+      <div class="company-header">
+        <div class="company-logo">
+          <van-image
+            v-if="company?.logo"
+            :src="company.logo"
+            width="48"
+            height="48"
+            round
+            fit="cover"
+          />
+          <div v-else class="logo-placeholder">
+            <van-icon name="shop-o" size="28" />
+          </div>
+        </div>
+        <div class="company-info">
+          <h2>{{ company?.name || '我的公司' }}</h2>
+          <p>{{ shopCount }} 家店铺</p>
+        </div>
+      </div>
+    </div>
+
+    <!-- 今日汇总 -->
+    <div class="summary-section">
+      <div class="section-title">
+        <span>今日数据</span>
+        <span class="update-time">实时更新</span>
+      </div>
+      <div class="summary-cards">
+        <div class="summary-card primary">
+          <div class="card-value">¥{{ formatNumber(summary?.totalRevenue || 0) }}</div>
+          <div class="card-label">总营业额</div>
+          <div class="card-trend" :class="{ up: (summary?.comparedToYesterday || 0) > 0 }">
+            <van-icon :name="(summary?.comparedToYesterday || 0) > 0 ? 'arrow-up' : 'arrow-down'" />
+            {{ Math.abs(summary?.comparedToYesterday || 0) }}%
+          </div>
+        </div>
+        <div class="summary-card">
+          <div class="card-value">{{ summary?.totalOrders || 0 }}</div>
+          <div class="card-label">总订单</div>
+        </div>
+        <div class="summary-card">
+          <div class="card-value">{{ summary?.totalCustomers || 0 }}</div>
+          <div class="card-label">顾客数</div>
+        </div>
+      </div>
+    </div>
+
+    <!-- 店铺列表 -->
+    <div class="shops-section">
+      <div class="section-title">
+        <span>店铺概况</span>
+        <span class="view-all" @click="goToShopList">查看全部</span>
+      </div>
+      <div class="shop-list">
+        <div
+          v-for="shop in shopSummaries"
+          :key="shop.shopId"
+          class="shop-item"
+          @click="goToShopDetail(shop.shopId)"
+        >
+          <div class="shop-main">
+            <div class="shop-name">
+              {{ shop.shopName }}
+              <van-tag :type="shop.status === 'operating' ? 'success' : 'default'">
+                {{ shop.status === 'operating' ? '营业中' : '休息中' }}
+              </van-tag>
+            </div>
+            <div class="shop-stats">
+              <span>¥{{ formatNumber(shop.todayRevenue) }}</span>
+              <span class="divider">|</span>
+              <span>{{ shop.todayOrders }}单</span>
+            </div>
+          </div>
+          <div class="shop-tables">
+            <div class="tables-bar">
+              <div
+                class="tables-fill"
+                :style="{ width: `${(shop.occupiedTables / shop.totalTables) * 100}%` }"
+              ></div>
+            </div>
+            <span class="tables-text">{{ shop.occupiedTables }}/{{ shop.totalTables }}桌</span>
+          </div>
+          <van-icon name="arrow" class="arrow" />
+        </div>
+      </div>
+    </div>
+
+    <!-- 快捷操作 -->
+    <div class="quick-actions">
+      <div class="action-item" @click="goToReports">
+        <van-icon name="chart-trending-o" size="24" />
+        <span>综合报表</span>
+      </div>
+      <div class="action-item" @click="goToShopList">
+        <van-icon name="shop-o" size="24" />
+        <span>店铺管理</span>
+      </div>
+      <div class="action-item" @click="goToStaffList">
+        <van-icon name="friends-o" size="24" />
+        <span>员工管理</span>
+      </div>
+      <div class="action-item" @click="goToFinance">
+        <van-icon name="balance-o" size="24" />
+        <span>财务中心</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+import { useCompanyStore } from '@/store/modules/company'
+
+const router = useRouter()
+const companyStore = useCompanyStore()
+
+const company = computed(() => companyStore.currentCompany)
+const shopCount = computed(() => companyStore.shopCount)
+const summary = computed(() => companyStore.companySummary)
+const shopSummaries = computed(() => companyStore.companySummary?.shopSummaries || [])
+
+const formatNumber = (num: number) => {
+  if (num >= 10000) {
+    return (num / 10000).toFixed(1) + '万'
+  }
+  return num.toLocaleString()
+}
+
+const goToSettings = () => {
+  showToast('设置')
+}
+
+const goToShopList = () => {
+  router.push('/owner/shops')
+}
+
+const goToShopDetail = (shopId: string) => {
+  companyStore.selectShop(shopId)
+  router.push(`/shop/${shopId}/dashboard`)
+}
+
+const goToReports = () => {
+  router.push('/owner/reports')
+}
+
+const goToStaffList = () => {
+  showToast('员工管理')
+}
+
+const goToFinance = () => {
+  showToast('财务中心')
+}
+
+onMounted(async () => {
+  await companyStore.loadCompanyData()
+  await companyStore.loadCompanySummary()
+})
+</script>
+
+<style scoped>
+.owner-dashboard {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 20px;
+}
+
+.company-card {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20px;
+  margin: 12px;
+  border-radius: 12px;
+  color: #fff;
+}
+
+.company-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.logo-placeholder {
+  width: 48px;
+  height: 48px;
+  background: rgba(255,255,255,0.2);
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.company-info h2 {
+  margin: 0;
+  font-size: 18px;
+}
+
+.company-info p {
+  margin: 4px 0 0;
+  font-size: 13px;
+  opacity: 0.8;
+}
+
+.summary-section,
+.shops-section {
+  background: #fff;
+  margin: 12px;
+  border-radius: 12px;
+  padding: 16px;
+}
+
+.section-title {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 12px;
+  font-size: 15px;
+  font-weight: bold;
+}
+
+.update-time {
+  font-size: 12px;
+  color: #999;
+  font-weight: normal;
+}
+
+.view-all {
+  font-size: 13px;
+  color: #1989fa;
+  font-weight: normal;
+}
+
+.summary-cards {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 12px;
+}
+
+.summary-card {
+  text-align: center;
+  padding: 12px 8px;
+  background: #f8f8f8;
+  border-radius: 8px;
+}
+
+.summary-card.primary {
+  background: linear-gradient(135deg, #fff6e6 0%, #fff 100%);
+  border: 1px solid #ffe0b2;
+}
+
+.card-value {
+  font-size: 20px;
+  font-weight: bold;
+  color: #333;
+}
+
+.summary-card.primary .card-value {
+  color: #ff6b00;
+}
+
+.card-label {
+  font-size: 12px;
+  color: #999;
+  margin-top: 4px;
+}
+
+.card-trend {
+  font-size: 12px;
+  color: #ee0a24;
+  margin-top: 4px;
+}
+
+.card-trend.up {
+  color: #07c160;
+}
+
+.shop-list {
+  display: flex;
+  flex-direction: column;
+  gap: 12px;
+}
+
+.shop-item {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding: 12px;
+  background: #f8f8f8;
+  border-radius: 8px;
+  cursor: pointer;
+}
+
+.shop-main {
+  flex: 1;
+}
+
+.shop-name {
+  font-size: 15px;
+  font-weight: 500;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.shop-stats {
+  font-size: 13px;
+  color: #666;
+  margin-top: 4px;
+}
+
+.shop-stats .divider {
+  margin: 0 8px;
+  color: #ddd;
+}
+
+.shop-tables {
+  width: 80px;
+  text-align: right;
+}
+
+.tables-bar {
+  height: 4px;
+  background: #eee;
+  border-radius: 2px;
+  overflow: hidden;
+}
+
+.tables-fill {
+  height: 100%;
+  background: #07c160;
+  border-radius: 2px;
+}
+
+.tables-text {
+  font-size: 11px;
+  color: #999;
+}
+
+.arrow {
+  color: #999;
+}
+
+.quick-actions {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 12px;
+  margin: 12px;
+  background: #fff;
+  border-radius: 12px;
+  padding: 16px;
+}
+
+.action-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 8px;
+  cursor: pointer;
+}
+
+.action-item span {
+  font-size: 12px;
+  color: #666;
+}
+</style>

+ 238 - 0
src/views/owner/reports.vue

@@ -0,0 +1,238 @@
+<template>
+  <div class="owner-reports">
+    <van-nav-bar title="综合报表" left-arrow @click-left="goBack" fixed placeholder />
+
+    <!-- 日期范围选择 -->
+    <div class="date-picker">
+      <van-cell title="日期范围" :value="dateRangeText" is-link @click="showDatePicker = true" />
+    </div>
+
+    <!-- 汇总卡片 -->
+    <div class="summary-cards">
+      <div class="summary-card">
+        <div class="card-label">总营业额</div>
+        <div class="card-value">¥{{ formatNumber(reportData.totalRevenue) }}</div>
+      </div>
+      <div class="summary-card">
+        <div class="card-label">总订单</div>
+        <div class="card-value">{{ reportData.totalOrders }}</div>
+      </div>
+      <div class="summary-card">
+        <div class="card-label">客单价</div>
+        <div class="card-value">¥{{ avgOrderValue }}</div>
+      </div>
+    </div>
+
+    <!-- 店铺对比 -->
+    <div class="section">
+      <div class="section-title">店铺对比</div>
+      <div class="compare-table">
+        <div class="table-header">
+          <span class="col-name">店铺</span>
+          <span class="col-revenue">营业额</span>
+          <span class="col-orders">订单数</span>
+          <span class="col-percent">占比</span>
+        </div>
+        <div
+          v-for="shop in shopComparison"
+          :key="shop.shopId"
+          class="table-row"
+        >
+          <span class="col-name">{{ shop.shopName }}</span>
+          <span class="col-revenue">¥{{ formatNumber(shop.revenue) }}</span>
+          <span class="col-orders">{{ shop.orders }}</span>
+          <span class="col-percent">
+            <div class="percent-bar">
+              <div class="percent-fill" :style="{ width: `${shop.percent}%` }"></div>
+            </div>
+            {{ shop.percent }}%
+          </span>
+        </div>
+      </div>
+    </div>
+
+    <!-- 趋势图(简化版)-->
+    <div class="section">
+      <div class="section-title">营业趋势</div>
+      <div class="trend-chart">
+        <div class="chart-placeholder">
+          <van-icon name="chart-trending-o" size="48" color="#ddd" />
+          <p>图表功能开发中</p>
+        </div>
+      </div>
+    </div>
+
+    <!-- 日期选择器 -->
+    <van-calendar
+      v-model:show="showDatePicker"
+      type="range"
+      @confirm="onDateConfirm"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { useRouter } from 'vue-router'
+
+const router = useRouter()
+
+const showDatePicker = ref(false)
+const dateRange = ref<Date[]>([])
+const dateRangeText = computed(() => {
+  const [startDate, endDate] = dateRange.value
+  if (startDate && endDate) {
+    return `${startDate.toLocaleDateString()} ~ ${endDate.toLocaleDateString()}`
+  }
+  return '今日'
+})
+
+// 模拟数据
+const reportData = ref({
+  totalRevenue: 125800,
+  totalOrders: 1560
+})
+
+const avgOrderValue = computed(() => {
+  if (reportData.value.totalOrders === 0) return '0'
+  return (reportData.value.totalRevenue / reportData.value.totalOrders).toFixed(0)
+})
+
+const shopComparison = ref([
+  { shopId: '1', shopName: '渋谷店', revenue: 52800, orders: 680, percent: 42 },
+  { shopId: '2', shopName: '新宿店', revenue: 43000, orders: 550, percent: 34 },
+  { shopId: '3', shopName: '池袋店', revenue: 30000, orders: 330, percent: 24 }
+])
+
+const formatNumber = (num: number) => {
+  if (num >= 10000) {
+    return (num / 10000).toFixed(1) + '万'
+  }
+  return num.toLocaleString()
+}
+
+const goBack = () => {
+  router.back()
+}
+
+const onDateConfirm = (values: Date[]) => {
+  dateRange.value = values
+  showDatePicker.value = false
+}
+</script>
+
+<style scoped>
+.owner-reports {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.date-picker {
+  background: #fff;
+  margin-bottom: 12px;
+}
+
+.summary-cards {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 12px;
+  padding: 0 12px;
+  margin-bottom: 12px;
+}
+
+.summary-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 16px;
+  text-align: center;
+}
+
+.card-label {
+  font-size: 12px;
+  color: #999;
+}
+
+.card-value {
+  font-size: 20px;
+  font-weight: bold;
+  margin-top: 8px;
+}
+
+.section {
+  background: #fff;
+  margin: 12px;
+  border-radius: 12px;
+  padding: 16px;
+}
+
+.section-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 16px;
+}
+
+.compare-table {
+  font-size: 13px;
+}
+
+.table-header,
+.table-row {
+  display: flex;
+  align-items: center;
+  padding: 12px 0;
+}
+
+.table-header {
+  color: #999;
+  border-bottom: 1px solid #f5f5f5;
+}
+
+.table-row {
+  border-bottom: 1px solid #f5f5f5;
+}
+
+.table-row:last-child {
+  border-bottom: none;
+}
+
+.col-name { flex: 1; }
+.col-revenue { width: 80px; text-align: right; }
+.col-orders { width: 60px; text-align: right; }
+.col-percent {
+  width: 100px;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.percent-bar {
+  flex: 1;
+  height: 6px;
+  background: #f0f0f0;
+  border-radius: 3px;
+  overflow: hidden;
+}
+
+.percent-fill {
+  height: 100%;
+  background: linear-gradient(90deg, #667eea, #764ba2);
+  border-radius: 3px;
+}
+
+.trend-chart {
+  height: 200px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+
+.chart-placeholder {
+  text-align: center;
+  color: #999;
+}
+
+.chart-placeholder p {
+  margin-top: 8px;
+  font-size: 13px;
+}
+</style>

+ 254 - 0
src/views/owner/shops.vue

@@ -0,0 +1,254 @@
+<template>
+  <div class="owner-shops">
+    <van-nav-bar title="店铺管理" left-arrow @click-left="goBack" fixed placeholder>
+      <template #right>
+        <van-icon name="add-o" size="20" @click="addShop" />
+      </template>
+    </van-nav-bar>
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <div class="shop-list">
+        <div
+          v-for="shop in shops"
+          :key="shop.id"
+          class="shop-card"
+          :class="{ selected: shop.id === selectedShopId }"
+          @click="selectShop(shop)"
+        >
+          <div class="shop-header">
+            <div class="shop-logo">
+              <van-image
+                v-if="shop.logo"
+                :src="shop.logo"
+                width="50"
+                height="50"
+                round
+                fit="cover"
+              />
+              <div v-else class="logo-placeholder">
+                <van-icon name="shop-o" size="24" />
+              </div>
+            </div>
+            <div class="shop-info">
+              <div class="shop-name">
+                {{ shop.name }}
+                <van-tag :type="getStatusType(shop.status)">
+                  {{ getStatusText(shop.status) }}
+                </van-tag>
+              </div>
+              <div class="shop-address">{{ shop.address }}</div>
+            </div>
+            <van-icon name="arrow" class="arrow" />
+          </div>
+
+          <div class="shop-actions">
+            <van-button size="small" plain type="primary" @click.stop="enterShop(shop)">
+              进入店铺
+            </van-button>
+            <van-button size="small" plain @click.stop="editShop(shop)">
+              编辑
+            </van-button>
+            <van-button
+              size="small"
+              plain
+              :type="shop.status === 'operating' ? 'warning' : 'success'"
+              @click.stop="toggleStatus(shop)"
+            >
+              {{ shop.status === 'operating' ? '暂停营业' : '开始营业' }}
+            </van-button>
+          </div>
+        </div>
+
+        <van-empty v-if="shops.length === 0" description="暂无店铺" />
+      </div>
+    </van-pull-refresh>
+
+    <!-- 当前选中店铺指示 -->
+    <div v-if="selectedShop" class="current-shop-bar">
+      <span>当前操作店铺: <strong>{{ selectedShop.name }}</strong></span>
+      <van-button size="small" type="primary" @click="enterSelectedShop">
+        进入管理
+      </van-button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed, onMounted } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast, showConfirmDialog } from 'vant'
+import { useCompanyStore, type Shop, type ShopStatus } from '@/store/modules/company'
+
+const router = useRouter()
+const companyStore = useCompanyStore()
+
+const refreshing = ref(false)
+
+const shops = computed(() => companyStore.shops)
+const selectedShopId = computed(() => companyStore.selectedShopId)
+const selectedShop = computed(() => companyStore.selectedShop)
+
+const getStatusType = (status: ShopStatus): 'success' | 'warning' | 'default' => {
+  const types: Record<ShopStatus, 'success' | 'warning' | 'default'> = {
+    operating: 'success',
+    closed: 'warning',
+    preparing: 'default'
+  }
+  return types[status]
+}
+
+const getStatusText = (status: ShopStatus) => {
+  const texts: Record<ShopStatus, string> = {
+    operating: '营业中',
+    closed: '休息中',
+    preparing: '准备中'
+  }
+  return texts[status]
+}
+
+const goBack = () => {
+  router.back()
+}
+
+const onRefresh = async () => {
+  await companyStore.loadCompanyData()
+  refreshing.value = false
+}
+
+const selectShop = (shop: Shop) => {
+  companyStore.selectShop(shop.id)
+}
+
+const enterShop = (shop: Shop) => {
+  companyStore.selectShop(shop.id)
+  router.push(`/shop/${shop.id}/dashboard`)
+}
+
+const enterSelectedShop = () => {
+  if (selectedShop.value) {
+    router.push(`/shop/${selectedShop.value.id}/dashboard`)
+  }
+}
+
+const editShop = (shop: Shop) => {
+  showToast(`编辑店铺: ${shop.name}`)
+}
+
+const addShop = () => {
+  showToast('添加店铺')
+}
+
+const toggleStatus = async (shop: Shop) => {
+  const newStatus = shop.status === 'operating' ? '暂停营业' : '开始营业'
+  try {
+    await showConfirmDialog({
+      title: '确认操作',
+      message: `确定要${newStatus}「${shop.name}」吗?`
+    })
+    // TODO: 调用API更新状态
+    showToast(`已${newStatus}`)
+  } catch {
+    // 用户取消
+  }
+}
+
+onMounted(() => {
+  if (shops.value.length === 0) {
+    companyStore.loadCompanyData()
+  }
+})
+</script>
+
+<style scoped>
+.owner-shops {
+  min-height: 100vh;
+  background: #f5f5f5;
+  padding-bottom: 70px;
+}
+
+.shop-list {
+  padding: 12px;
+}
+
+.shop-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 16px;
+  margin-bottom: 12px;
+  border: 2px solid transparent;
+  transition: border-color 0.2s;
+}
+
+.shop-card.selected {
+  border-color: #1989fa;
+}
+
+.shop-header {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+}
+
+.logo-placeholder {
+  width: 50px;
+  height: 50px;
+  background: #f5f5f5;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #999;
+}
+
+.shop-info {
+  flex: 1;
+}
+
+.shop-name {
+  font-size: 16px;
+  font-weight: 500;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.shop-address {
+  font-size: 13px;
+  color: #999;
+  margin-top: 4px;
+}
+
+.arrow {
+  color: #ccc;
+}
+
+.shop-actions {
+  display: flex;
+  gap: 8px;
+  margin-top: 12px;
+  padding-top: 12px;
+  border-top: 1px solid #f5f5f5;
+}
+
+.current-shop-bar {
+  position: fixed;
+  bottom: 0;
+  left: 0;
+  right: 0;
+  background: #fff;
+  padding: 12px 16px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  box-shadow: 0 -2px 10px rgba(0,0,0,0.05);
+}
+
+.current-shop-bar span {
+  font-size: 14px;
+  color: #666;
+}
+
+.current-shop-bar strong {
+  color: #1989fa;
+}
+</style>

+ 276 - 0
src/views/pos/order-detail.vue

@@ -0,0 +1,276 @@
+<template>
+  <div class="pos-order-detail">
+    <van-nav-bar title="订单详情" left-arrow @click-left="$router.back()" fixed placeholder>
+      <template #right>
+        <van-icon name="printer" size="20" @click="printOrder" />
+      </template>
+    </van-nav-bar>
+
+    <div class="detail-content">
+      <!-- 订单状态 -->
+      <div class="status-card">
+        <van-tag :type="getStatusType(order.status)" size="large">
+          {{ getStatusText(order.status) }}
+        </van-tag>
+        <div class="order-no">#{{ order.orderNo }}</div>
+        <div class="order-time">{{ formatFullTime(order.createTime) }}</div>
+      </div>
+
+      <!-- 桌位信息 -->
+      <div class="info-card">
+        <div class="card-title">桌位信息</div>
+        <van-cell title="桌位号" :value="order.tableName" />
+        <van-cell title="就餐人数" :value="`${order.guestCount}人`" />
+        <van-cell title="开台时间" :value="formatFullTime(order.startTime)" />
+      </div>
+
+      <!-- 菜品清单 -->
+      <div class="items-card">
+        <div class="card-title">菜品清单</div>
+        <div v-for="item in order.items" :key="item.id" class="item-row">
+          <span class="item-name">{{ item.name }}</span>
+          <span class="item-spec" v-if="item.spec">{{ item.spec }}</span>
+          <span class="item-quantity">x{{ item.quantity }}</span>
+          <span class="item-price">¥{{ item.price * item.quantity }}</span>
+        </div>
+      </div>
+
+      <!-- 费用明细 -->
+      <div class="fee-card">
+        <div class="fee-row">
+          <span>菜品小计</span>
+          <span>¥{{ order.subtotal }}</span>
+        </div>
+        <div class="fee-row" v-if="order.serviceCharge">
+          <span>服务费</span>
+          <span>¥{{ order.serviceCharge }}</span>
+        </div>
+        <div class="fee-row total">
+          <span>合计</span>
+          <span>¥{{ order.amount }}</span>
+        </div>
+      </div>
+
+      <!-- 操作按钮 -->
+      <div class="action-buttons">
+        <van-button
+          v-if="order.status === 'pending'"
+          type="primary"
+          block
+          @click="acceptOrder"
+        >
+          接单
+        </van-button>
+        <van-button
+          v-if="order.status === 'preparing'"
+          type="success"
+          block
+          @click="completeOrder"
+        >
+          完成制作
+        </van-button>
+        <van-button
+          v-if="order.status === 'completed'"
+          type="default"
+          block
+          @click="printOrder"
+        >
+          打印小票
+        </van-button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { showToast } from 'vant'
+
+const route = useRoute()
+const router = useRouter()
+
+interface OrderItem {
+  id: string
+  name: string
+  spec?: string
+  quantity: number
+  price: number
+}
+
+interface Order {
+  id: string
+  orderNo: string
+  tableName: string
+  guestCount: number
+  status: 'pending' | 'preparing' | 'completed' | 'cancelled'
+  amount: number
+  subtotal: number
+  serviceCharge?: number
+  createTime: number
+  startTime: number
+  items: OrderItem[]
+}
+
+const order = ref<Order>({
+  id: route.params.id as string,
+  orderNo: '2024011201',
+  tableName: 'A1',
+  guestCount: 2,
+  status: 'preparing',
+  amount: 158,
+  subtotal: 158,
+  serviceCharge: 0,
+  createTime: Date.now() - 15 * 60 * 1000,
+  startTime: Date.now() - 30 * 60 * 1000,
+  items: [
+    { id: '1', name: '宫保鸡丁', spec: '中辣', quantity: 1, price: 58 },
+    { id: '2', name: '麻婆豆腐', quantity: 1, price: 38 },
+    { id: '3', name: '米饭', spec: '大碗', quantity: 2, price: 3 }
+  ]
+})
+
+const getStatusType = (status: string) => {
+  const types: Record<string, any> = {
+    pending: 'warning',
+    preparing: 'primary',
+    completed: 'success',
+    cancelled: 'danger'
+  }
+  return types[status]
+}
+
+const getStatusText = (status: string) => {
+  const texts: Record<string, string> = {
+    pending: '待处理',
+    preparing: '制作中',
+    completed: '已完成',
+    cancelled: '已取消'
+  }
+  return texts[status]
+}
+
+const formatFullTime = (timestamp: number) => {
+  const date = new Date(timestamp)
+  const month = String(date.getMonth() + 1).padStart(2, '0')
+  const day = String(date.getDate()).padStart(2, '0')
+  const hours = String(date.getHours()).padStart(2, '0')
+  const minutes = String(date.getMinutes()).padStart(2, '0')
+  return `${month}-${day} ${hours}:${minutes}`
+}
+
+const acceptOrder = () => {
+  order.value.status = 'preparing'
+  showToast('已接单')
+}
+
+const completeOrder = () => {
+  order.value.status = 'completed'
+  showToast('订单完成')
+}
+
+const printOrder = () => {
+  showToast('打印小票')
+}
+
+onMounted(() => {
+  // 加载订单详情
+})
+</script>
+
+<style scoped>
+.pos-order-detail {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.detail-content {
+  padding: 12px;
+}
+
+.status-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 20px;
+  text-align: center;
+  margin-bottom: 12px;
+}
+
+.order-no {
+  font-size: 20px;
+  font-weight: bold;
+  margin: 12px 0 4px;
+}
+
+.order-time {
+  font-size: 13px;
+  color: #999;
+}
+
+.info-card,
+.items-card,
+.fee-card {
+  background: #fff;
+  border-radius: 12px;
+  padding: 16px;
+  margin-bottom: 12px;
+}
+
+.card-title {
+  font-size: 15px;
+  font-weight: bold;
+  margin-bottom: 12px;
+}
+
+.item-row {
+  display: flex;
+  align-items: center;
+  padding: 12px 0;
+  border-bottom: 1px solid #f5f5f5;
+}
+
+.item-row:last-child {
+  border-bottom: none;
+}
+
+.item-name {
+  flex: 1;
+  font-size: 14px;
+}
+
+.item-spec {
+  font-size: 12px;
+  color: #999;
+  margin-right: 8px;
+}
+
+.item-quantity {
+  font-size: 14px;
+  color: #666;
+  margin-right: 16px;
+}
+
+.item-price {
+  font-size: 14px;
+  font-weight: 500;
+}
+
+.fee-row {
+  display: flex;
+  justify-content: space-between;
+  padding: 8px 0;
+  font-size: 14px;
+}
+
+.fee-row.total {
+  padding-top: 12px;
+  border-top: 1px dashed #ddd;
+  font-size: 16px;
+  font-weight: bold;
+  color: #ee0a24;
+}
+
+.action-buttons {
+  padding: 16px 0;
+}
+</style>

+ 243 - 0
src/views/pos/orders.vue

@@ -0,0 +1,243 @@
+<template>
+  <div class="pos-orders">
+    <van-nav-bar title="订单管理" fixed placeholder />
+
+    <van-tabs v-model:active="activeTab" sticky @change="onTabChange">
+      <van-tab title="全部" />
+      <van-tab title="待处理" />
+      <van-tab title="制作中" />
+      <van-tab title="已完成" />
+    </van-tabs>
+
+    <van-pull-refresh v-model="refreshing" @refresh="onRefresh">
+      <div class="order-list">
+        <div v-for="order in filteredOrders" :key="order.id" class="order-card" @click="viewOrder(order)">
+          <div class="order-header">
+            <span class="order-no">#{{ order.orderNo }}</span>
+            <van-tag :type="getStatusType(order.status)">{{ getStatusText(order.status) }}</van-tag>
+          </div>
+          <div class="order-info">
+            <div class="info-row">
+              <span class="label">桌位:</span>
+              <span class="value">{{ order.tableName }}</span>
+            </div>
+            <div class="info-row">
+              <span class="label">时间:</span>
+              <span class="value">{{ formatTime(order.createTime) }}</span>
+            </div>
+          </div>
+          <div class="order-items">
+            <span v-for="item in order.items.slice(0, 3)" :key="item.id" class="item-tag">
+              {{ item.name }} x{{ item.quantity }}
+            </span>
+            <span v-if="order.items.length > 3" class="more">+{{ order.items.length - 3 }}</span>
+          </div>
+          <div class="order-footer">
+            <span class="amount">¥{{ order.amount }}</span>
+            <van-button v-if="order.status === 'pending'" size="small" type="primary" @click.stop="acceptOrder(order)">
+              接单
+            </van-button>
+            <van-button v-if="order.status === 'preparing'" size="small" type="success" @click.stop="completeOrder(order)">
+              完成
+            </van-button>
+          </div>
+        </div>
+        <van-empty v-if="filteredOrders.length === 0" description="暂无订单" />
+      </div>
+    </van-pull-refresh>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+
+const router = useRouter()
+
+interface OrderItem {
+  id: string
+  name: string
+  quantity: number
+}
+
+interface Order {
+  id: string
+  orderNo: string
+  tableName: string
+  status: 'pending' | 'preparing' | 'completed' | 'cancelled'
+  amount: number
+  createTime: number
+  items: OrderItem[]
+}
+
+const activeTab = ref(0)
+const refreshing = ref(false)
+
+const orders = ref<Order[]>([
+  {
+    id: '1',
+    orderNo: '2024011201',
+    tableName: 'A1',
+    status: 'pending',
+    amount: 158,
+    createTime: Date.now() - 5 * 60 * 1000,
+    items: [
+      { id: '1', name: '宫保鸡丁', quantity: 1 },
+      { id: '2', name: '麻婆豆腐', quantity: 1 }
+    ]
+  },
+  {
+    id: '2',
+    orderNo: '2024011202',
+    tableName: 'B2',
+    status: 'preparing',
+    amount: 268,
+    createTime: Date.now() - 15 * 60 * 1000,
+    items: [
+      { id: '3', name: '北京烤鸭', quantity: 1 },
+      { id: '4', name: '鱼香肉丝', quantity: 2 }
+    ]
+  }
+])
+
+const filteredOrders = computed(() => {
+  if (activeTab.value === 0) return orders.value
+  const statusMap = ['', 'pending', 'preparing', 'completed']
+  return orders.value.filter(o => o.status === statusMap[activeTab.value])
+})
+
+const getStatusType = (status: string) => {
+  const types: Record<string, any> = {
+    pending: 'warning',
+    preparing: 'primary',
+    completed: 'success',
+    cancelled: 'danger'
+  }
+  return types[status]
+}
+
+const getStatusText = (status: string) => {
+  const texts: Record<string, string> = {
+    pending: '待处理',
+    preparing: '制作中',
+    completed: '已完成',
+    cancelled: '已取消'
+  }
+  return texts[status]
+}
+
+const formatTime = (timestamp: number) => {
+  const date = new Date(timestamp)
+  return `${date.getHours()}:${String(date.getMinutes()).padStart(2, '0')}`
+}
+
+const onTabChange = () => {
+  // Tab切换逻辑
+}
+
+const onRefresh = () => {
+  setTimeout(() => {
+    refreshing.value = false
+    showToast('刷新成功')
+  }, 1000)
+}
+
+const viewOrder = (order: Order) => {
+  router.push(`/pos/orders/${order.id}`)
+}
+
+const acceptOrder = (order: Order) => {
+  order.status = 'preparing'
+  showToast('已接单')
+}
+
+const completeOrder = (order: Order) => {
+  order.status = 'completed'
+  showToast('订单完成')
+}
+</script>
+
+<style scoped>
+.pos-orders {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.order-list {
+  padding: 12px;
+}
+
+.order-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 12px;
+  margin-bottom: 12px;
+  cursor: pointer;
+}
+
+.order-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.order-no {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.order-info {
+  margin-bottom: 8px;
+}
+
+.info-row {
+  display: flex;
+  font-size: 13px;
+  margin-bottom: 4px;
+}
+
+.info-row .label {
+  color: #999;
+  width: 50px;
+}
+
+.info-row .value {
+  color: #333;
+}
+
+.order-items {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 6px;
+  margin-bottom: 12px;
+}
+
+.item-tag {
+  font-size: 12px;
+  color: #666;
+  background: #f5f5f5;
+  padding: 2px 8px;
+  border-radius: 4px;
+}
+
+.more {
+  font-size: 12px;
+  color: #1989fa;
+}
+
+.order-footer {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  padding-top: 12px;
+  border-top: 1px solid #f5f5f5;
+}
+
+.amount {
+  font-size: 18px;
+  font-weight: bold;
+  color: #ee0a24;
+}
+</style>

+ 333 - 0
src/views/pos/tables.vue

@@ -0,0 +1,333 @@
+<template>
+  <div class="pos-tables">
+    <van-nav-bar title="桌位管理" fixed placeholder>
+      <template #right>
+        <van-icon name="add-o" size="20" @click="showAddTable" />
+      </template>
+    </van-nav-bar>
+
+    <!-- 状态统计 -->
+    <div class="stats-bar">
+      <div class="stat-item">
+        <span class="stat-value available">{{ availableCount }}</span>
+        <span class="stat-label">空闲</span>
+      </div>
+      <div class="stat-item">
+        <span class="stat-value occupied">{{ occupiedCount }}</span>
+        <span class="stat-label">就餐中</span>
+      </div>
+      <div class="stat-item">
+        <span class="stat-value reserved">{{ reservedCount }}</span>
+        <span class="stat-label">已预约</span>
+      </div>
+    </div>
+
+    <!-- 楼层筛选 -->
+    <van-tabs v-model:active="currentFloor" sticky>
+      <van-tab v-for="floor in floors" :key="floor" :title="floor === 0 ? '全部' : `${floor}F`" />
+    </van-tabs>
+
+    <!-- 桌位列表 -->
+    <div class="tables-grid">
+      <div
+        v-for="table in filteredTables"
+        :key="table.id"
+        class="table-card"
+        :class="[getTableStatusClass(table)]"
+        @click="handleTableClick(table)"
+      >
+        <div class="table-header">
+          <span class="table-name">{{ table.name }}</span>
+          <van-tag :type="getTagType(table.status)">
+            {{ getStatusText(table.status) }}
+          </van-tag>
+        </div>
+        <div class="table-info">
+          <span>{{ table.capacity }}人桌</span>
+          <span v-if="table.status === 'occupied'" class="duration">
+            {{ formatDuration(table.startTime) }}
+          </span>
+        </div>
+        <div v-if="table.guestCount" class="guest-count">
+          当前: {{ table.guestCount }}人
+        </div>
+      </div>
+    </div>
+
+    <!-- 桌位操作弹窗 -->
+    <van-action-sheet
+      v-model:show="showActions"
+      :title="selectedTable?.name"
+      :actions="tableActions"
+      @select="onActionSelect"
+      cancel-text="取消"
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, computed } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast, showConfirmDialog } from 'vant'
+
+const router = useRouter()
+
+interface Table {
+  id: string
+  name: string
+  floor: number
+  capacity: number
+  status: 'available' | 'occupied' | 'reserved' | 'cleaning'
+  startTime?: number
+  guestCount?: number
+  orderId?: string
+}
+
+// Mock data
+const tables = ref<Table[]>([
+  { id: '1', name: 'A1', floor: 1, capacity: 2, status: 'available' },
+  { id: '2', name: 'A2', floor: 1, capacity: 2, status: 'occupied', startTime: Date.now() - 45 * 60 * 1000, guestCount: 2 },
+  { id: '3', name: 'A3', floor: 1, capacity: 4, status: 'occupied', startTime: Date.now() - 90 * 60 * 1000, guestCount: 3 },
+  { id: '4', name: 'B1', floor: 1, capacity: 4, status: 'available' },
+  { id: '5', name: 'B2', floor: 1, capacity: 6, status: 'reserved' },
+  { id: '6', name: 'C1', floor: 2, capacity: 8, status: 'available' },
+  { id: '7', name: 'C2', floor: 2, capacity: 10, status: 'occupied', startTime: Date.now() - 20 * 60 * 1000, guestCount: 8 },
+])
+
+const floors = [0, 1, 2] // 0 = 全部
+const currentFloor = ref(0)
+const showActions = ref(false)
+const selectedTable = ref<Table | null>(null)
+
+// 筛选后的桌位
+const filteredTables = computed(() => {
+  if (currentFloor.value === 0) return tables.value
+  return tables.value.filter(t => t.floor === currentFloor.value)
+})
+
+// 统计
+const availableCount = computed(() => tables.value.filter(t => t.status === 'available').length)
+const occupiedCount = computed(() => tables.value.filter(t => t.status === 'occupied').length)
+const reservedCount = computed(() => tables.value.filter(t => t.status === 'reserved').length)
+
+// 桌位操作
+const tableActions = computed(() => {
+  if (!selectedTable.value) return []
+  const table = selectedTable.value
+
+  if (table.status === 'available') {
+    return [
+      { name: '开台', value: 'open' },
+      { name: '设为预约', value: 'reserve' }
+    ]
+  }
+
+  if (table.status === 'occupied') {
+    return [
+      { name: '点餐', value: 'order' },
+      { name: '加餐', value: 'addOrder' },
+      { name: '结账', value: 'checkout' },
+      { name: '换桌', value: 'transfer' },
+      { name: '清台', value: 'clear', color: '#ee0a24' }
+    ]
+  }
+
+  if (table.status === 'reserved') {
+    return [
+      { name: '签到开台', value: 'checkin' },
+      { name: '取消预约', value: 'cancelReserve', color: '#ee0a24' }
+    ]
+  }
+
+  return []
+})
+
+const getTableStatusClass = (table: Table) => {
+  if (table.status === 'available') return 'status-available'
+  if (table.status === 'reserved') return 'status-reserved'
+  if (table.status === 'occupied') {
+    const duration = table.startTime ? (Date.now() - table.startTime) / 1000 / 60 : 0
+    if (duration < 30) return 'status-occupied-new'
+    if (duration < 60) return 'status-occupied-normal'
+    return 'status-occupied-long'
+  }
+  return ''
+}
+
+const getTagType = (status: string): 'success' | 'warning' | 'danger' | 'primary' => {
+  const types: Record<string, 'success' | 'warning' | 'danger' | 'primary'> = {
+    available: 'success',
+    occupied: 'warning',
+    reserved: 'primary',
+    cleaning: 'danger'
+  }
+  return types[status] || 'primary'
+}
+
+const getStatusText = (status: string) => {
+  const texts: Record<string, string> = {
+    available: '空闲',
+    occupied: '就餐中',
+    reserved: '已预约',
+    cleaning: '清理中'
+  }
+  return texts[status] || status
+}
+
+const formatDuration = (startTime?: number) => {
+  if (!startTime) return ''
+  const minutes = Math.floor((Date.now() - startTime) / 1000 / 60)
+  if (minutes < 60) return `${minutes}分钟`
+  const hours = Math.floor(minutes / 60)
+  const mins = minutes % 60
+  return `${hours}小时${mins}分`
+}
+
+const handleTableClick = (table: Table) => {
+  selectedTable.value = table
+  showActions.value = true
+}
+
+const onActionSelect = async (action: { value: string }) => {
+  showActions.value = false
+  const table = selectedTable.value
+  if (!table) return
+
+  switch (action.value) {
+    case 'open':
+      showToast('开台成功')
+      table.status = 'occupied'
+      table.startTime = Date.now()
+      break
+    case 'order':
+      router.push({ path: '/menu', query: { table: table.name } })
+      break
+    case 'checkout':
+      showToast('跳转结账页面')
+      break
+    case 'clear':
+      await showConfirmDialog({ title: '确认清台?', message: '将结束该桌的用餐' })
+      table.status = 'available'
+      table.startTime = undefined
+      table.guestCount = undefined
+      showToast('清台完成')
+      break
+    default:
+      showToast(`操作: ${action.value}`)
+  }
+}
+
+const showAddTable = () => {
+  showToast('添加桌位')
+}
+</script>
+
+<style scoped>
+.pos-tables {
+  min-height: 100vh;
+  background: #f5f5f5;
+}
+
+.stats-bar {
+  display: flex;
+  background: #fff;
+  padding: 12px 16px;
+  margin-bottom: 8px;
+}
+
+.stat-item {
+  flex: 1;
+  text-align: center;
+}
+
+.stat-value {
+  display: block;
+  font-size: 24px;
+  font-weight: bold;
+}
+
+.stat-value.available { color: #07c160; }
+.stat-value.occupied { color: #ff976a; }
+.stat-value.reserved { color: #1989fa; }
+
+.stat-label {
+  font-size: 12px;
+  color: #999;
+}
+
+.tables-grid {
+  display: grid;
+  grid-template-columns: repeat(3, 1fr);
+  gap: 12px;
+  padding: 16px;
+}
+
+.table-card {
+  background: #fff;
+  border-radius: 8px;
+  padding: 12px;
+  text-align: center;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.06);
+  border-left: 4px solid transparent;
+  cursor: pointer;
+  transition: transform 0.2s;
+}
+
+.table-card:active {
+  transform: scale(0.98);
+}
+
+.table-header {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 8px;
+}
+
+.table-name {
+  font-size: 16px;
+  font-weight: bold;
+}
+
+.table-info {
+  font-size: 12px;
+  color: #666;
+  display: flex;
+  justify-content: space-between;
+}
+
+.duration {
+  color: #ff976a;
+}
+
+.guest-count {
+  margin-top: 4px;
+  font-size: 12px;
+  color: #1989fa;
+}
+
+/* 状态颜色 */
+.status-available {
+  border-left-color: #07c160;
+}
+
+.status-occupied-new {
+  border-left-color: #07c160;
+  background: linear-gradient(135deg, #f0fff4 0%, #fff 100%);
+}
+
+.status-occupied-normal {
+  border-left-color: #ff976a;
+  background: linear-gradient(135deg, #fff7e6 0%, #fff 100%);
+}
+
+.status-occupied-long {
+  border-left-color: #ee0a24;
+  background: linear-gradient(135deg, #fff0f0 0%, #fff 100%);
+}
+
+.status-reserved {
+  border-left-color: #1989fa;
+  background: linear-gradient(135deg, #e6f7ff 0%, #fff 100%);
+}
+</style>

+ 130 - 0
src/views/pos/welcome.vue

@@ -0,0 +1,130 @@
+<template>
+  <div class="pos-welcome">
+    <van-nav-bar title="POS系统" left-arrow @click-left="goBack" />
+
+    <div class="welcome-content">
+      <h1>欢迎使用POS系统</h1>
+      <p>请先初始化桌台信息</p>
+
+      <van-form @submit="handleSubmit">
+        <van-cell-group inset>
+          <van-field
+            v-model="formData.shopId"
+            name="shopId"
+            label="店铺ID"
+            placeholder="请输入店铺ID"
+            :rules="[{ required: true, message: '请输入店铺ID' }]"
+          />
+
+          <van-field
+            v-model="formData.deskId"
+            name="deskId"
+            label="桌台ID"
+            placeholder="请输入桌台ID"
+            :rules="[{ required: true, message: '请输入桌台ID' }]"
+          />
+
+          <van-field
+            v-model="formData.deskNumber"
+            name="deskNumber"
+            label="桌台号"
+            placeholder="请输入桌台号 (如: A01)"
+            :rules="[{ required: true, message: '请输入桌台号' }]"
+          />
+
+          <van-field
+            v-model="formData.peopleCount"
+            name="peopleCount"
+            type="number"
+            label="就餐人数"
+            placeholder="请输入就餐人数"
+            :rules="[{ required: true, message: '请输入就餐人数' }]"
+          />
+        </van-cell-group>
+
+        <div style="margin: 16px;">
+          <van-button
+            round
+            block
+            type="primary"
+            native-type="submit"
+            :loading="loading"
+          >
+            开始点餐
+          </van-button>
+        </div>
+      </van-form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+import { usePosStore } from '@/store/modules/pos'
+
+const router = useRouter()
+const posStore = usePosStore()
+
+const loading = ref(false)
+const formData = ref({
+  shopId: '',
+  deskId: '',
+  deskNumber: '',
+  peopleCount: ''
+})
+
+const handleSubmit = () => {
+  loading.value = true
+
+  try {
+    posStore.initDesk({
+      ...formData.value,
+      peopleCount: parseInt(formData.value.peopleCount)
+    })
+
+    showToast({
+      message: '初始化成功',
+      icon: 'success'
+    })
+
+    setTimeout(() => {
+      showToast('POS菜单功能开发中')
+      loading.value = false
+    }, 1000)
+  } catch (error) {
+    showToast('初始化失败')
+    loading.value = false
+  }
+}
+
+const goBack = () => {
+  router.push('/mine')
+}
+</script>
+
+<style scoped lang="scss">
+.pos-welcome {
+  min-height: 100vh;
+  background: #f0f2f5;
+}
+
+.welcome-content {
+  padding: 40px 20px;
+  text-align: center;
+
+  h1 {
+    font-size: 28px;
+    font-weight: 600;
+    color: #323233;
+    margin: 0 0 12px 0;
+  }
+
+  p {
+    font-size: 14px;
+    color: #969799;
+    margin: 0 0 40px 0;
+  }
+}
+</style>

+ 330 - 0
src/views/scan/index.vue

@@ -0,0 +1,330 @@
+<template>
+  <div class="scan-page">
+    <!-- Loading State -->
+    <div v-if="loading" class="loading-container">
+      <van-loading type="spinner" size="48" color="#1989fa" />
+      <p class="loading-text">正在获取信息...</p>
+    </div>
+
+    <!-- Error State -->
+    <div v-else-if="error" class="error-container">
+      <van-empty image="error" :description="errorMessage">
+        <van-button round type="primary" @click="retry">
+          重新尝试
+        </van-button>
+      </van-empty>
+    </div>
+
+    <!-- Success State -->
+    <div v-else-if="shopInfo" class="success-container">
+      <div class="shop-info">
+        <van-image
+          v-if="shopInfo?.logo"
+          :src="shopInfo.logo"
+          width="60"
+          height="60"
+          round
+          fit="cover"
+        />
+        <div v-else class="shop-logo-placeholder">
+          <van-icon name="shop-o" size="32" />
+        </div>
+        <div class="shop-detail">
+          <h2>{{ shopInfo?.name || '店铺' }}</h2>
+          <p v-if="shopInfo?.address">{{ shopInfo.address }}</p>
+        </div>
+      </div>
+
+      <!-- Mode Display -->
+      <div v-if="mode === 'table'" class="table-card table-mode">
+        <van-icon name="scan" size="48" color="#1989fa" />
+        <h3>{{ tableInfo?.tableName || `桌位 ${tableInfo?.code}` }}</h3>
+        <p class="hint">扫码点餐,无需登录</p>
+        <van-tag type="primary" size="medium" class="mode-tag">堂食模式</van-tag>
+      </div>
+
+      <div v-else class="table-card shop-mode">
+        <van-icon name="bag-o" size="48" color="#ff976a" />
+        <h3>欢迎光临</h3>
+        <p class="hint">外卖配送 · 到店自提 · 预约订座</p>
+        <van-tag type="warning" size="medium" class="mode-tag">店铺模式</van-tag>
+      </div>
+
+      <van-button
+        type="primary"
+        block
+        round
+        size="large"
+        class="start-btn"
+        @click="startOrdering"
+      >
+        {{ mode === 'table' ? '开始点餐' : '进入店铺' }}
+      </van-button>
+
+      <p class="login-hint">
+        已有账号?
+        <span class="link" @click="goToLogin">登录</span>
+        可获得积分
+      </p>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted } from 'vue'
+import { useRoute, useRouter } from 'vue-router'
+import { showToast } from 'vant'
+import { useUserStore } from '@/store/modules/user'
+import { useAppStore, type AppMode } from '@/store/modules/app'
+import type { Shop } from '@/store/modules/company'
+
+const route = useRoute()
+const router = useRouter()
+const userStore = useUserStore()
+const appStore = useAppStore()
+
+const loading = ref(true)
+const error = ref(false)
+const errorMessage = ref('')
+const mode = ref<AppMode>('platform')
+
+interface TableInfo {
+  id: string
+  code: string
+  tableName: string
+}
+
+const tableInfo = ref<TableInfo | null>(null)
+const shopInfo = ref<Shop | null>(null)
+
+/**
+ * 解析URL参数并验证桌位
+ */
+const validateParams = async () => {
+  loading.value = true
+  error.value = false
+
+  try {
+    // 获取URL参数
+    const tableCode = route.query.t as string
+    const shopId = route.query.s as string
+
+    if (!shopId) {
+      throw new Error('缺少店铺参数')
+    }
+
+    // 模拟API响应
+    await new Promise(resolve => setTimeout(resolve, 800))
+
+    // 模拟店铺数据
+    shopInfo.value = {
+      id: shopId,
+      companyId: 'company_1',
+      name: '示例餐厅',
+      address: '东京都渋谷区...',
+      status: 'operating',
+      logo: '',
+    }
+
+    if (tableCode) {
+      // === 桌位模式 ===
+      mode.value = 'table'
+      tableInfo.value = {
+        id: `table_${tableCode}`,
+        code: tableCode,
+        tableName: `${tableCode}号桌`
+      }
+
+      // 1. 设置App模式
+      appStore.enterTableMode(shopInfo.value, {
+        id: tableInfo.value.id,
+        code: tableInfo.value.code,
+        name: tableInfo.value.tableName
+      })
+
+      // 2. 初始化用户会话
+      userStore.initGuestSession({
+        tableId: tableInfo.value.id,
+        tableCode: tableInfo.value.code,
+        tableName: tableInfo.value.tableName,
+        shopId: shopId,
+        sessionId: `session_${Date.now()}`,
+        createdAt: Date.now()
+      })
+    } else {
+      // === 店铺模式 ===
+      mode.value = 'shop'
+      tableInfo.value = null
+      
+      // 1. 设置App模式
+      appStore.enterShopMode(shopInfo.value)
+      
+      // 店铺模式不需要Guest Session
+      userStore.clearGuestSession()
+    }
+
+  } catch (err: any) {
+    error.value = true
+    errorMessage.value = err.message || '识别失败'
+  } finally {
+    loading.value = false
+  }
+}
+
+/**
+ * 开始点餐
+ */
+const startOrdering = () => {
+  if (mode.value === 'table' && tableInfo.value) {
+    router.push({
+      path: '/menu',
+      query: { shopId: shopInfo.value?.id }
+    })
+  } else if (mode.value === 'shop' && shopInfo.value) {
+    router.push({
+      path: '/menu',
+      query: { shopId: shopInfo.value.id }
+    })
+  }
+}
+
+/**
+ * 跳转登录
+ */
+const goToLogin = () => {
+  router.push({
+    path: '/login',
+    query: {
+      redirect: mode.value === 'table' ? '/scan' : `/menu?shopId=${shopInfo.value?.id}`
+    }
+  })
+}
+
+/**
+ * 重试
+ */
+const retry = () => {
+  validateParams()
+}
+
+onMounted(() => {
+  validateParams()
+})
+</script>
+
+<style scoped>
+.scan-page {
+  min-height: 100vh;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  padding: 20px;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  justify-content: center;
+}
+
+.loading-container,
+.error-container,
+.success-container {
+  width: 100%;
+  max-width: 360px;
+  text-align: center;
+}
+
+.loading-container {
+  color: #fff;
+}
+
+.loading-text {
+  margin-top: 16px;
+  font-size: 14px;
+}
+
+.error-container {
+  background: #fff;
+  border-radius: 16px;
+  padding: 40px 20px;
+}
+
+.success-container {
+  background: #fff;
+  border-radius: 16px;
+  padding: 24px;
+  position: relative;
+}
+
+.shop-info {
+  display: flex;
+  align-items: center;
+  gap: 12px;
+  padding-bottom: 20px;
+  border-bottom: 1px solid #eee;
+}
+
+.shop-logo-placeholder {
+  width: 60px;
+  height: 60px;
+  background: #f5f5f5;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: #999;
+}
+
+.shop-detail {
+  flex: 1;
+  text-align: left;
+}
+
+.shop-detail h2 {
+  margin: 0;
+  font-size: 18px;
+  font-weight: bold;
+}
+
+.shop-detail p {
+  margin: 4px 0 0;
+  font-size: 12px;
+  color: #999;
+}
+
+.table-card {
+  padding: 30px 20px;
+  text-align: center;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+}
+
+.table-card h3 {
+  margin: 16px 0 8px;
+  font-size: 24px;
+  color: #333;
+}
+
+.table-card .hint {
+  font-size: 14px;
+  color: #999;
+  margin-bottom: 16px;
+}
+
+.mode-tag {
+  margin-top: 8px;
+}
+
+.start-btn {
+  margin-top: 10px;
+}
+
+.login-hint {
+  margin-top: 20px;
+  font-size: 14px;
+  color: #999;
+}
+
+.login-hint .link {
+  color: #1989fa;
+  cursor: pointer;
+}
+</style>