فهرست منبع

feat: Implement dynamic theme switching with multiple theme options and dark mode support.

FanLide 1 هفته پیش
والد
کامیت
9164d53bd0

+ 402 - 0
doc/UI_DESIGN_BRIEF.md

@@ -0,0 +1,402 @@
+# UI Design Brief - Restaurant Management System
+
+**Project Name:** LINE Order App  
+**Target Platform:** Mobile Web (iOS/Android)  
+**Design Tool:** Stitch AI  
+**Version:** 1.0  
+**Date:** 2026-01-15
+
+---
+
+## 1. Project Overview
+
+A multi-mode restaurant ordering and management system built as a Progressive Web App, supporting 4 distinct operational modes with seamless switching between them.
+
+### Core Modes
+
+1. **Platform Mode (店铺聚合)** - Marketplace homepage with shop discovery
+2. **Customer Mode (顾客点餐)** - Menu browsing and ordering for dine-in or takeaway
+3. **POS Mode (店员端)** - Staff interface for table and order management
+4. **Owner Mode (店长后台)** - Management dashboard for shop owners
+
+---
+
+## 2. Design Requirements
+
+### 2.1 Visual Style
+
+- **Design Language:** Modern, clean, and friendly
+- **Color Palette:**
+  - Primary: Fresh orange/green for CTAs (order button, add to cart)
+  - Secondary: Warm neutrals (beige, light gray) for backgrounds
+  - Accent: Status colors (green for available, yellow for occupied, red for alerts)
+- **Typography:**
+  - Headings: Bold, clear sans-serif
+  - Body: Readable, 14-16px base
+  - Support for multilingual (Japanese, English, Chinese)
+- **Iconography:** Simple, recognizable icons for food categories and actions
+- **Spacing:** Generous padding for touch targets (min 44px)
+
+### 2.3 Theme System (New Requirement)
+
+The application must support a dynamic theming system allowing for branded experiences.
+
+- **Theme Sources:**
+  1. **User Setting:** Users can select preferred themes in "Settings" (e.g., Light, Dark, System).
+  2. **Backend Configuration:** Shops can enforce a specific brand theme (Primary Color, Logo, Font) via backend parameters.
+- **Priority Logic:** Backend Shop Theme > User Preference > Default System Theme.
+- **Design Deliverables:**
+  - Mockups should demonstrate the **same screen** in 2-3 different color themes (e.g., "Default Orange", "Sushi Zen Black", "Matcha Green").
+  - "Settings" page must include a specialized **Theme Selector** UI.
+
+### 2.2 Mobile-First Principles
+
+- **Bottom Navigation:** Fixed tab bar for primary navigation
+- **Thumb-Friendly:** Critical actions within reach zone
+- **Card-Based:** Content organized in cards for easy scanning
+- **Swipe Gestures:** Support for common mobile interactions
+
+---
+
+## 3. Page-by-Page Specifications
+
+### 3.1 Platform Mode (首页)
+
+**Route:** `/#/index`
+
+**Purpose:** Shop discovery and selection with location-based services
+
+**Layout:**
+
+```
+┌─────────────────────────┐
+│ [Location] 📍 [Search]  │ ← Top Bar
+├─────────────────────────┤
+│ 🎯 Banner Carousel      │ ← Promotions (3-5 slides)
+├─────────────────────────┤
+│ 🍱 Category Grid        │ ← Food types (4 columns)
+│ [中華] [和食] [洋食] [カフェ]│
+├─────────────────────────┤
+│ 📍 Nearby Shops         │ ← Shop Cards (scrollable)
+│ ┌──────────────────┐   │
+│ │ [Shop Image]     │   │
+│ │ Shop Name        │   │
+│ │ ⭐4.8 | 📏1.2km  │   │
+│ │ ¥¥¥ | 営業中    │   │
+│ └──────────────────┘   │
+├─────────────────────────┤
+│ [Home][Menu][Order][Me] │ ← Bottom Nav
+└─────────────────────────┘
+```
+
+**Key Components:**
+
+- Location picker with current address display
+- Search bar with autocomplete
+- Horizontal scrolling banner
+- Category grid (2x4 or 3x3)
+- Shop card with: photo, name, rating, distance, price range, status badge
+
+---
+
+### 3.2 Customer Mode - Menu Page
+
+**Route:** `/#/menu?shopId=xxx&tableCode=xxx`
+
+**Purpose:** Browse menu and add items to cart
+
+**Layout:**
+
+```
+┌─────────────────────────┐
+│ ← Shop Name      🔍 🛒3 │ ← Nav bar with cart badge
+├──┬──────────────────────┤
+│菜│ 🔥 Recommended       │ ← Sticky category tabs
+│品│ ┌──────────┐         │
+│  │ │[Dish Img]│ ¥1200  │ ← Dish card
+│酒│ │Gyoza     │  [+]   │
+│水│ └──────────┘         │
+│  │ ┌──────────┐         │
+│デ│ │[Dish Img]│ ¥800   │
+│ザ│ │Ramen     │  [+]   │
+│|│ └──────────┘         │
+│ト│                      │
+├──┴──────────────────────┤
+│ 🛒 Cart: ¥2000  [Order]│ ← Sticky bottom bar
+└─────────────────────────┘
+```
+
+**Key Features:**
+
+- **Left Sidebar:** Scrollable category list with active indicator
+- **Main Area:** Dish cards with image, name, price, quick add button
+- **Filter/Sort:** Row of filter chips (辛さ, 新商品, ベジタリアン)
+- **Cart Bar:** Always visible with total and checkout button
+- **Empty State:** Friendly illustration when no items selected
+
+**Interaction:**
+
+- Tap card → Open detail modal with options
+- Tap [+] → Quick add (increment counter)
+- Swipe categories → Scroll to section
+
+---
+
+### 3.3 Customer Mode - Cart & Checkout
+
+**Route:** `/#/cart`
+
+**Layout:**
+
+```
+┌─────────────────────────┐
+│ ← Shopping Cart         │
+├─────────────────────────┤
+│ 📋 Table A01            │ ← Context info
+├─────────────────────────┤
+│ ┌─────────────────────┐│
+│ │ Gyoza x2      ¥2400 ││ ← Cart item
+│ │ [−] 2 [+]     [🗑️]  ││
+│ └─────────────────────┘│
+│ ┌─────────────────────┐│
+│ │ Ramen x1      ¥800  ││
+│ │ [−] 1 [+]     [🗑️]  ││
+│ └─────────────────────┘│
+├─────────────────────────┤
+│ Subtotal:        ¥3200 │
+│ Tax (10%):        ¥320 │
+│ ─────────────────────  │
+│ Total:           ¥3520 │
+├─────────────────────────┤
+│ [Place Order] 🟢       │ ← Large CTA button
+└─────────────────────────┘
+```
+
+---
+
+### 3.4 POS Mode - Table Management
+
+**Route:** `/#/pos/tables`
+
+**Purpose:** Visual table status overview for staff
+
+**Layout:**
+
+```
+┌─────────────────────────┐
+│ POS - Tables    👤 Staff│
+├─────────────────────────┤
+│ [All] [1F] [2F] [VIP]   │ ← Filter tabs
+├─────────────────────────┤
+│ Available: 8  Occupied: 4│ ← Status summary
+├─────────────────────────┤
+│ ┌──────┐ ┌──────┐       │
+│ │ A01  │ │ A02  │       │ ← Table grid
+│ │ 🟢   │ │ 🟡   │       │   Color-coded by duration
+│ │ 4👤  │ │ 2👤  │       │
+│ │      │ │ 35min│       │
+│ └──────┘ └──────┘       │
+│ ┌──────┐ ┌──────┐       │
+│ │ B01  │ │ B02  │       │
+│ │ 🔴   │ │ 🟢   │       │
+│ │ 6👤  │ │ 8👤  │       │
+│ │125min│ │      │       │
+│ └──────┘ └──────┘       │
+└─────────────────────────┘
+```
+
+**Color Coding (Time-based):**
+
+- 🟢 Green: 0-30 min (新入座)
+- 🟡 Yellow: 30-60 min (正常)
+- 🟠 Orange: 60-120 min (偏 long)
+- 🔴 Red: >120 min (预警)
+
+---
+
+### 3.5 Owner Mode - Dashboard
+
+**Route:** `/#/owner/dashboard`
+
+**Purpose:** Sales overview and KPIs
+
+**Layout:**
+
+```
+┌─────────────────────────┐
+│ Dashboard       📅 Today│
+├─────────────────────────┤
+│ ┌─────────┐ ┌─────────┐│
+│ │ ¥125,000│ │  68     ││ ← Big KPI cards
+│ │ Revenue │ │ Orders  ││
+│ │ (+12%)  │ │ (+5%)   ││
+│ └─────────┘ └─────────┘│
+├─────────────────────────┤
+│ 📊 Sales Trend          │
+│ [Line Chart Area]       │ ← Chart component
+├─────────────────────────┤
+│ 🔥 Top Dishes           │
+│ 1. Gyoza      ¥24,000  │
+│ 2. Ramen      ¥18,000  │
+│ 3. Tempura    ¥12,000  │
+├─────────────────────────┤
+│ [Reports][Shops][Menu]  │ ← Quick actions
+└─────────────────────────┘
+```
+
+---
+
+## 4. Component Library
+
+### 4.1 Buttons
+
+- **Primary CTA:** Large, rounded, high contrast (orange/green)
+- **Secondary:** Outlined, gray
+- **Icon Button:** Circular with icon only
+- **States:** Default, Hover, Active, Disabled
+
+### 4.2 Cards
+
+- **Shop Card:** Image top, content below, shadow on hover
+- **Dish Card:** Image left (square), text right, add button
+- **Order Card:** Horizontal layout with status badge
+
+### 4.3 Forms
+
+- **Input Fields:** Rounded corners, clear labels, validation states
+- **Selectors:** Dropdown, radio, checkbox
+- **Stepper:** [−] number [+] for quantity
+
+### 4.4 Navigation
+
+- **Tab Bar:** 4-5 items, icons + labels, active indicator
+- **Nav Bar:** Back button, title, actions
+- **Category Sidebar:** Vertical scrollable list
+
+### 4.5 Feedback
+
+- **Toast:** Temporary message at bottom
+- **Modal:** Centered overlay for details
+- **Badge:** Notification count on cart icon
+- **Status Dot:** Color indicator for tables
+
+---
+
+## 5. User Flows
+
+### Flow 1: QR Code Ordering (Guest)
+
+```
+Scan QR → Menu Page (Guest Mode) → Select Items → Cart → Place Order → Order Confirmation
+```
+
+**Key Screens:**
+
+1. Menu with tableCode in URL
+2. Cart showing table number
+3. Success page with order number
+
+### Flow 2: Table Management (Staff)
+
+```
+POS Login → Table Grid → Select Table → View Orders → Update Status → Clear Table
+```
+
+**Key Screens:**
+
+1. Table overview with color indicators
+2. Table detail with current orders
+3. Action buttons (serve, clear, split bill)
+
+### Flow 3: Shop Discovery (Customer)
+
+```
+Homepage → Browse Shops → Select Shop → Menu → Cart → Checkout
+```
+
+---
+
+## 6. Responsive Breakpoints
+
+- **Mobile:** 375px - 767px (Primary focus)
+- **Tablet:** 768px - 1024px (POS mode optimized)
+- **Desktop:** 1025px+ (Admin/Owner mode)
+
+---
+
+## 7. Accessibility
+
+- **Touch Targets:** Minimum 44x44px
+- **Contrast:** WCAG AA compliant
+- **Text Size:** Scalable, readable
+- **Icons:** Always paired with labels for clarity
+
+---
+
+## 8. Deliverables Needed from Stitch
+
+### Priority 1 (Core Screens)
+
+1. ✅ Platform Homepage (shop discovery)
+2. ✅ Menu Page (customer ordering)
+3. ✅ Cart & Checkout
+4. ✅ POS Table Grid
+5. ✅ Owner Dashboard
+
+### Priority 2 (Support Screens)
+
+6. Login page
+7. Order list
+8. Order detail
+9. Profile page
+10. Table detail (POS)
+
+### Priority 3 (Components)
+
+11. Bottom navigation bar
+12. Shop card component
+13. Dish card component
+14. Modal overlays
+15. Empty states
+
+---
+
+## 9. Design Constraints
+
+- **File Format:** Figma or Sketch compatible
+- **Color Modes:** Light mode (primary), Dark mode (optional)
+- **Languages:** Design should accommodate Japanese text length
+- **Branding:** Modern Japanese restaurant aesthetic
+- **Icons:** Use outlined style, consistent set
+
+---
+
+## 10. Reference Inspiration
+
+**Similar Apps:**
+
+- Uber Eats (for shop discovery)
+- TableCheck (for table management)
+- Square POS (for staff interface)
+- Hot Pepper Gourmet (for Japanese market feel)
+
+**Design Principles:**
+
+- Prioritize speed of ordering
+- Clear visual hierarchy
+- Minimalist but warm
+- Trust-building (reviews, photos, status indicators)
+
+---
+
+## Contact & Questions
+
+For design clarifications or additional requirements, please refer to:
+
+- Technical documentation: `README.md`
+- Product requirements: `餐饮管理系统需求规格说明书 (PRD).md`
+- Project structure: `/src` directory
+
+---
+
+**End of Brief**

+ 14 - 80
src/assets/styles/common.css

@@ -1,85 +1,19 @@
-/* 公共样式 */
-
-/* 通用类 */
-.text-center {
-  text-align: center;
-}
-
-.text-left {
-  text-align: left;
-}
-
-.text-right {
-  text-align: right;
-}
-
-.flex {
-  display: flex;
-}
-
-.flex-center {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-}
-
-.flex-between {
-  display: flex;
-  align-items: center;
-  justify-content: space-between;
-}
-
-.flex-column {
-  display: flex;
-  flex-direction: column;
-}
-
-/* 页面容器 */
-.page {
-  min-height: 100vh;
-  background-color: #f5f5f5;
-}
-
-.page-content {
-  padding: 16px;
-}
-
-/* 安全区域适配 */
-.safe-area-bottom {
-  padding-bottom: constant(safe-area-inset-bottom);
-  padding-bottom: env(safe-area-inset-bottom);
-}
-
-/* 滚动容器 */
-.scroll-container {
-  overflow-y: auto;
-  -webkit-overflow-scrolling: touch;
-}
-
-/* 加载状态 */
-.loading {
-  display: flex;
-  align-items: center;
-  justify-content: center;
-  padding: 20px;
-  color: #999;
-}
-
-/* 空状态 */
-.empty {
-  display: flex;
-  flex-direction: column;
-  align-items: center;
-  justify-content: center;
-  padding: 40px 20px;
-  color: #999;
+:root {
+  /* App Variables */
+  --app-primary-color: #ff9900;
+  --app-secondary-color: #e6a23c;
+  --app-bg-color: #f7f8fa;
+  --app-text-color: #323233;
 }
 
-.empty-icon {
-  font-size: 48px;
-  margin-bottom: 12px;
+body {
+  background-color: var(--app-bg-color);
+  color: var(--app-text-color);
+  transition: background-color 0.3s, color 0.3s;
 }
 
-.empty-text {
-  font-size: 14px;
+/* Dark Mode Defaults */
+.dark body {
+  --van-background: #1a1a1a;
+  --van-text-color: #f5f5f5;
 }

+ 2 - 0
src/components.d.ts

@@ -18,6 +18,7 @@ declare module 'vue' {
     ShopHeader: typeof import('./components/ShopHeader.vue')['default']
     TableFooter: typeof import('./components/TableFooter.vue')['default']
     TableHeader: typeof import('./components/TableHeader.vue')['default']
+    ThemeSwitcher: typeof import('./components/ThemeSwitcher.vue')['default']
     VanActionBar: typeof import('vant/es')['ActionBar']
     VanActionBarButton: typeof import('vant/es')['ActionBarButton']
     VanActionBarIcon: typeof import('vant/es')['ActionBarIcon']
@@ -45,6 +46,7 @@ declare module 'vue' {
     VanList: typeof import('vant/es')['List']
     VanLoading: typeof import('vant/es')['Loading']
     VanNavBar: typeof import('vant/es')['NavBar']
+    VanNoticeBar: typeof import('vant/es')['NoticeBar']
     VanPicker: typeof import('vant/es')['Picker']
     VanPopup: typeof import('vant/es')['Popup']
     VanPullRefresh: typeof import('vant/es')['PullRefresh']

+ 84 - 0
src/components/ThemeSwitcher.vue

@@ -0,0 +1,84 @@
+<template>
+  <div class="theme-switcher">
+    <div class="section-title">{{ $t('mine.themeSettings') }}</div>
+    <div class="theme-grid">
+      <div
+        v-for="theme in themes"
+        :key="theme.id"
+        class="theme-item"
+        :class="{ active: currentThemeId === theme.id }"
+        @click="selectTheme(theme.id)"
+      >
+        <div class="preview-circle" :style="{ background: theme.colors.primary }">
+          <van-icon name="success" v-if="currentThemeId === theme.id" />
+        </div>
+        <span class="theme-name">{{ theme.name }}</span>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed } from 'vue'
+import { useAppStore } from '@/store/modules/app'
+import { themes } from '@/config/theme'
+
+const appStore = useAppStore()
+const currentThemeId = computed(() => appStore.currentThemeId)
+
+const selectTheme = (id: string) => {
+  appStore.setTheme(id)
+}
+</script>
+
+<style scoped>
+.theme-switcher {
+  padding: 16px;
+  background: white;
+  border-radius: 8px;
+  margin: 12px;
+}
+
+.section-title {
+  font-size: 16px;
+  font-weight: bold;
+  margin-bottom: 12px;
+  color: var(--app-text-color);
+}
+
+.theme-grid {
+  display: grid;
+  grid-template-columns: repeat(4, 1fr);
+  gap: 12px;
+}
+
+.theme-item {
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  cursor: pointer;
+}
+
+.preview-circle {
+  width: 40px;
+  height: 40px;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  margin-bottom: 8px;
+  transition: transform 0.2s;
+}
+
+.theme-item.active .preview-circle {
+  transform: scale(1.1);
+  box-shadow: 0 0 0 2px var(--app-text-color);
+}
+
+.theme-name {
+  font-size: 12px;
+  color: #666;
+  text-align: center;
+}
+</style>

+ 54 - 0
src/config/theme.ts

@@ -0,0 +1,54 @@
+import type { ThemeConfig } from '@/types/theme'
+
+export const themes: ThemeConfig[] = [
+  {
+    id: 'default',
+    name: 'Default Orange',
+    colors: {
+      primary: '#ff9900',
+      secondary: '#E6A23C',
+      background: '#f7f8fa',
+      text: '#323233'
+    }
+  },
+  {
+    id: 'ocean',
+    name: 'Ocean Blue',
+    colors: {
+      primary: '#1989fa',
+      secondary: '#409eff',
+      background: '#f0f9eb',
+      text: '#323233',
+      buttonPrimaryBackground: '#1989fa',
+      buttonPrimaryBorderColor: '#1989fa'
+    }
+  },
+  {
+    id: 'forest',
+    name: 'Forest Green',
+    colors: {
+      primary: '#07c160',
+      secondary: '#67c23a',
+      background: '#f0f9eb',
+      text: '#323233',
+      buttonPrimaryBackground: '#07c160',
+      buttonPrimaryBorderColor: '#07c160'
+    }
+  },
+  {
+    id: 'dark',
+    name: 'Dark Mode',
+    colors: {
+      primary: '#ff9900',
+      secondary: '#E6A23C',
+      background: '#1a1a1a',
+      text: '#f5f5f5',
+      buttonPrimaryBackground: '#ff9900',
+      buttonPrimaryBorderColor: '#ff9900'
+    }
+  }
+]
+
+export function getThemeById(id: string): ThemeConfig {
+  return themes.find(t => t.id === id) || themes[0]!
+}

+ 2 - 1
src/locale/en.json

@@ -229,7 +229,8 @@
       "address": "Address",
       "customerService": "Service",
       "feedback": "Feedback"
-    }
+    },
+    "themeSettings": "Theme Settings"
   },
   "login": {
     "title": "Login",

+ 2 - 1
src/locale/ja.json

@@ -229,7 +229,8 @@
       "address": "住所",
       "customerService": "カスタマーサービス",
       "feedback": "フィードバック"
-    }
+    },
+    "themeSettings": "テーマ設定"
   },
   "login": {
     "title": "ログイン",

+ 2 - 1
src/locale/zh-Hans.json

@@ -229,7 +229,8 @@
       "address": "收货地址",
       "customerService": "联系客服",
       "feedback": "意见反馈"
-    }
+    },
+    "themeSettings": "主题设置"
   },
   "login": {
     "title": "登录",

+ 2 - 1
src/locale/zh-Hant.json

@@ -229,7 +229,8 @@
       "address": "收貨地址",
       "customerService": "聯繫客服",
       "feedback": "意見反饋"
-    }
+    },
+    "themeSettings": "主題設置"
   },
   "login": {
     "title": "登錄",

+ 15 - 1
src/store/modules/app.ts

@@ -2,6 +2,8 @@ import { defineStore } from 'pinia'
 import { storage } from '@/utils/storage'
 import { setDayjsLocale } from '@/utils/format'
 import { setLanguage } from '@/locale'
+import { getThemeById } from '@/config/theme'
+import { applyTheme } from '@/utils/theme'
 import type { Shop } from './company'
 
 export type AppMode = 'platform' | 'shop' | 'table'
@@ -19,6 +21,9 @@ export const useAppStore = defineStore('app', {
     // 当前应用模式
     mode: 'platform' as AppMode,
 
+    // 当前主题
+    currentThemeId: 'default',
+
     // 当前上下文信息
     currentShop: null as Shop | null,
     currentTable: null as TableInfo | null,
@@ -137,11 +142,20 @@ export const useAppStore = defineStore('app', {
     },
     setLocation(location: any) {
       this.location = location
+    },
+
+    /**
+     * 设置主题
+     */
+    setTheme(themeId: string) {
+      this.currentThemeId = themeId
+      const theme = getThemeById(themeId)
+      applyTheme(theme)
     }
   },
 
   persist: {
     storage: localStorage,
-    paths: ['lang', 'mode', 'currentShop', 'currentTable', 'store', 'desk', 'isScan', 'orderType']
+    paths: ['lang', 'mode', 'currentShop', 'currentTable', 'store', 'desk', 'isScan', 'orderType', 'currentThemeId']
   }
 })

+ 15 - 0
src/types/theme.ts

@@ -0,0 +1,15 @@
+export interface ThemeColors {
+  primary: string
+  secondary: string
+  background: string
+  text: string
+  // Vant overrides
+  buttonPrimaryBackground?: string
+  buttonPrimaryBorderColor?: string
+}
+
+export interface ThemeConfig {
+  id: string
+  name: string
+  colors: ThemeColors
+}

+ 34 - 0
src/utils/theme.ts

@@ -0,0 +1,34 @@
+import type { ThemeConfig } from '@/types/theme'
+
+/**
+ * 应用主题
+ * @param theme 主题配置
+ */
+export function applyTheme(theme: ThemeConfig) {
+  const root = document.documentElement
+  const colors = theme.colors
+
+  // 设置 Vant 主题变量 (覆盖默认)
+  root.style.setProperty('--van-primary-color', colors.primary)
+  
+  // 如果定义了特定按钮样式
+  if (colors.buttonPrimaryBackground) {
+    root.style.setProperty('--van-button-primary-background', colors.buttonPrimaryBackground)
+  }
+  if (colors.buttonPrimaryBorderColor) {
+    root.style.setProperty('--van-button-primary-border-color', colors.buttonPrimaryBorderColor)
+  }
+
+  // 设置 App 全局变量
+  root.style.setProperty('--app-primary-color', colors.primary)
+  root.style.setProperty('--app-secondary-color', colors.secondary)
+  root.style.setProperty('--app-bg-color', colors.background)
+  root.style.setProperty('--app-text-color', colors.text)
+  
+  // 处理深色模式类名
+  if (theme.id === 'dark') {
+    document.documentElement.classList.add('dark')
+  } else {
+    document.documentElement.classList.remove('dark')
+  }
+}

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

@@ -96,6 +96,9 @@
         </div>
       </div>
 
+      <!-- 主题切换 -->
+      <ThemeSwitcher />
+
       <!-- 我的订单 -->
       <div class="section-card">
         <div class="section-title">{{ $t('mine.my-orders') }}</div>
@@ -171,6 +174,7 @@ import { getUserInfo, getMineServices } from '@/api/user'
 import { useEnv } from '@/composables/useEnv'
 import { useRole } from '@/composables/useRole'
 import YTabBar from '@/components/common/YTabBar.vue'
+import ThemeSwitcher from '@/components/ThemeSwitcher.vue'
 
 const { t } = useI18n()
 const router = useRouter()