2 Incheckningar 39c47a7197 ... 3167495a8c

Upphovsman SHA1 Meddelande Datum
  FanLide 3167495a8c feat: Add customer registration page and refactor login component styling to use new theme variables. 3 veckor sedan
  FanLide 28a69fa981 refactor: Implement comprehensive TypeScript types for merchant store and related components, and add development guidelines. 3 veckor sedan

+ 52 - 0
.agent/skills/codereview/SKILL.md

@@ -0,0 +1,52 @@
+---
+name: Code Review
+description: A comprehensive guide and checklist for performing code reviews on Vue 3 + TypeScript projects.
+---
+
+# Code Review Skill
+
+Use this skill when the user asks you to "review code", "check for bugs", or "perform a code review". This skill ensures consistency and depth in your reviews.
+
+## Review Checklist
+
+### 1. Functional Correctness
+
+- [ ] **Requirements**: Does the code meet the user's requirements?
+- [ ] **Logic**: Are there logical errors, off-by-one errors, or incorrect conditions?
+- [ ] **Edge Cases**: Are edge cases handled (e.g., empty lists, null values, network failures)?
+
+### 2. TypeScript & Type Safety
+
+- [ ] **No `any`**: Avoid using `any` unless absolutely necessary.
+- [ ] **Interfaces/Types**: Are interfaces and types defined clearly?
+- [ ] **Strictness**: Does it satisfy strict null checks?
+
+### 3. Vue 3 Best Practices
+
+- [ ] **Composition API**: proper usage of `ref`, `reactive`, `computed`, and `watch`.
+- [ ] **Reactivity**: Are reactive dependencies lost (e.g., destructuring props without `toRef`)?
+- [ ] **Components**: Component structure, prop definitions, and event emissions.
+- [ ] **Vant UI**: Correct usage of Vant UI components.
+
+### 4. Code Quality & Style
+
+- [ ] **Readability**: Is the code easy to understand?
+- [ ] **Naming**: Are variables/functions named descriptively?
+- [ ] **Modularity**: Is the code broken down into small, reusable functions/components?
+- [ ] **Comments**: Are complex logic parts commented?
+
+### 5. Performance & Security
+
+- [ ] **Performance**: potential bottlenecks (unnecessary re-renders, heavy computations).
+- [ ] **Security**: XSS, input validation, sensitive data exposure.
+
+## Workflow
+
+1.  **Analyze**: Read the files provided by the user or identified in the task.
+2.  **Evaluate**: check the code against the checklist above.
+3.  **Report**:
+    - **Summary**: A high-level overview of the code quality.
+    - **Issues**: List specific issues found, grouped by severity (Critical, Major, Minor). Include line numbers.
+      - _Example_: `src/views/Home.vue:45` - Destructuring `props` causes loss of reactivity. Use `props.propName` or `toRefs(props)`.
+    - **Suggestions**: Improvement suggestions (refactoring, better approaches).
+4.  **Fix (Optional)**: If the user requests, apply the fixes based on the review.

+ 267 - 0
.agent/workflows/ui-ux-pro-max.md

@@ -0,0 +1,267 @@
+---
+description: Plan and implement UI
+auto_execution_mode: 3
+---
+
+# ui-ux-pro-max
+
+Comprehensive design guide for web and mobile applications. Contains 50+ styles, 97 color palettes, 57 font pairings, 99 UX guidelines, and 25 chart types across 9 technology stacks. Searchable database with priority-based recommendations.
+
+## Prerequisites
+
+Check if Python is installed:
+
+```bash
+python3 --version || python --version
+```
+
+If Python is not installed, install it based on user's OS:
+
+**macOS:**
+```bash
+brew install python3
+```
+
+**Ubuntu/Debian:**
+```bash
+sudo apt update && sudo apt install python3
+```
+
+**Windows:**
+```powershell
+winget install Python.Python.3.12
+```
+
+---
+
+## How to Use This Workflow
+
+When user requests UI/UX work (design, build, create, implement, review, fix, improve), follow this workflow:
+
+### Step 1: Analyze User Requirements
+
+Extract key information from user request:
+- **Product type**: SaaS, e-commerce, portfolio, dashboard, landing page, etc.
+- **Style keywords**: minimal, playful, professional, elegant, dark mode, etc.
+- **Industry**: healthcare, fintech, gaming, education, etc.
+- **Stack**: React, Vue, Next.js, or default to `html-tailwind`
+
+### Step 2: Generate Design System (REQUIRED)
+
+**Always start with `--design-system`** to get comprehensive recommendations with reasoning:
+
+```bash
+python3 .shared/ui-ux-pro-max/scripts/search.py "<product_type> <industry> <keywords>" --design-system [-p "Project Name"]
+```
+
+This command:
+1. Searches 5 domains in parallel (product, style, color, landing, typography)
+2. Applies reasoning rules from `ui-reasoning.csv` to select best matches
+3. Returns complete design system: pattern, style, colors, typography, effects
+4. Includes anti-patterns to avoid
+
+**Example:**
+```bash
+python3 .shared/ui-ux-pro-max/scripts/search.py "beauty spa wellness service" --design-system -p "Serenity Spa"
+```
+
+### Step 3: Supplement with Detailed Searches (as needed)
+
+After getting the design system, use domain searches to get additional details:
+
+```bash
+python3 .shared/ui-ux-pro-max/scripts/search.py "<keyword>" --domain <domain> [-n <max_results>]
+```
+
+**When to use detailed searches:**
+
+| Need | Domain | Example |
+|------|--------|---------|
+| More style options | `style` | `--domain style "glassmorphism dark"` |
+| Chart recommendations | `chart` | `--domain chart "real-time dashboard"` |
+| UX best practices | `ux` | `--domain ux "animation accessibility"` |
+| Alternative fonts | `typography` | `--domain typography "elegant luxury"` |
+| Landing structure | `landing` | `--domain landing "hero social-proof"` |
+
+### Step 4: Stack Guidelines (Default: html-tailwind)
+
+Get implementation-specific best practices. If user doesn't specify a stack, **default to `html-tailwind`**.
+
+```bash
+python3 .shared/ui-ux-pro-max/scripts/search.py "<keyword>" --stack html-tailwind
+```
+
+Available stacks: `html-tailwind`, `react`, `nextjs`, `vue`, `svelte`, `swiftui`, `react-native`, `flutter`, `shadcn`
+
+---
+
+## Search Reference
+
+### Available Domains
+
+| Domain | Use For | Example Keywords |
+|--------|---------|------------------|
+| `product` | Product type recommendations | SaaS, e-commerce, portfolio, healthcare, beauty, service |
+| `style` | UI styles, colors, effects | glassmorphism, minimalism, dark mode, brutalism |
+| `typography` | Font pairings, Google Fonts | elegant, playful, professional, modern |
+| `color` | Color palettes by product type | saas, ecommerce, healthcare, beauty, fintech, service |
+| `landing` | Page structure, CTA strategies | hero, hero-centric, testimonial, pricing, social-proof |
+| `chart` | Chart types, library recommendations | trend, comparison, timeline, funnel, pie |
+| `ux` | Best practices, anti-patterns | animation, accessibility, z-index, loading |
+| `react` | React/Next.js performance | waterfall, bundle, suspense, memo, rerender, cache |
+| `web` | Web interface guidelines | aria, focus, keyboard, semantic, virtualize |
+| `prompt` | AI prompts, CSS keywords | (style name) |
+
+### Available Stacks
+
+| Stack | Focus |
+|-------|-------|
+| `html-tailwind` | Tailwind utilities, responsive, a11y (DEFAULT) |
+| `react` | State, hooks, performance, patterns |
+| `nextjs` | SSR, routing, images, API routes |
+| `vue` | Composition API, Pinia, Vue Router |
+| `svelte` | Runes, stores, SvelteKit |
+| `swiftui` | Views, State, Navigation, Animation |
+| `react-native` | Components, Navigation, Lists |
+| `flutter` | Widgets, State, Layout, Theming |
+| `shadcn` | shadcn/ui components, theming, forms, patterns |
+
+---
+
+## Example Workflow
+
+**User request:** "Làm landing page cho dịch vụ chăm sóc da chuyên nghiệp"
+
+### Step 1: Analyze Requirements
+- Product type: Beauty/Spa service
+- Style keywords: elegant, professional, soft
+- Industry: Beauty/Wellness
+- Stack: html-tailwind (default)
+
+### Step 2: Generate Design System (REQUIRED)
+
+```bash
+python3 .shared/ui-ux-pro-max/scripts/search.py "beauty spa wellness service elegant" --design-system -p "Serenity Spa"
+```
+
+**Output:** Complete design system with pattern, style, colors, typography, effects, and anti-patterns.
+
+### Step 3: Supplement with Detailed Searches (as needed)
+
+```bash
+# Get UX guidelines for animation and accessibility
+python3 .shared/ui-ux-pro-max/scripts/search.py "animation accessibility" --domain ux
+
+# Get alternative typography options if needed
+python3 .shared/ui-ux-pro-max/scripts/search.py "elegant luxury serif" --domain typography
+```
+
+### Step 4: Stack Guidelines
+
+```bash
+python3 .shared/ui-ux-pro-max/scripts/search.py "layout responsive form" --stack html-tailwind
+```
+
+**Then:** Synthesize design system + detailed searches and implement the design.
+
+---
+
+## Output Formats
+
+The `--design-system` flag supports two output formats:
+
+```bash
+# ASCII box (default) - best for terminal display
+python3 .shared/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system
+
+# Markdown - best for documentation
+python3 .shared/ui-ux-pro-max/scripts/search.py "fintech crypto" --design-system -f markdown
+```
+
+---
+
+## Tips for Better Results
+
+1. **Be specific with keywords** - "healthcare SaaS dashboard" > "app"
+2. **Search multiple times** - Different keywords reveal different insights
+3. **Combine domains** - Style + Typography + Color = Complete design system
+4. **Always check UX** - Search "animation", "z-index", "accessibility" for common issues
+5. **Use stack flag** - Get implementation-specific best practices
+6. **Iterate** - If first search doesn't match, try different keywords
+
+---
+
+## Common Rules for Professional UI
+
+These are frequently overlooked issues that make UI look unprofessional:
+
+### Icons & Visual Elements
+
+| Rule | Do | Don't |
+|------|----|----- |
+| **No emoji icons** | Use SVG icons (Heroicons, Lucide, Simple Icons) | Use emojis like 🎨 🚀 ⚙️ as UI icons |
+| **Stable hover states** | Use color/opacity transitions on hover | Use scale transforms that shift layout |
+| **Correct brand logos** | Research official SVG from Simple Icons | Guess or use incorrect logo paths |
+| **Consistent icon sizing** | Use fixed viewBox (24x24) with w-6 h-6 | Mix different icon sizes randomly |
+
+### Interaction & Cursor
+
+| Rule | Do | Don't |
+|------|----|----- |
+| **Cursor pointer** | Add `cursor-pointer` to all clickable/hoverable cards | Leave default cursor on interactive elements |
+| **Hover feedback** | Provide visual feedback (color, shadow, border) | No indication element is interactive |
+| **Smooth transitions** | Use `transition-colors duration-200` | Instant state changes or too slow (>500ms) |
+
+### Light/Dark Mode Contrast
+
+| Rule | Do | Don't |
+|------|----|----- |
+| **Glass card light mode** | Use `bg-white/80` or higher opacity | Use `bg-white/10` (too transparent) |
+| **Text contrast light** | Use `#0F172A` (slate-900) for text | Use `#94A3B8` (slate-400) for body text |
+| **Muted text light** | Use `#475569` (slate-600) minimum | Use gray-400 or lighter |
+| **Border visibility** | Use `border-gray-200` in light mode | Use `border-white/10` (invisible) |
+
+### Layout & Spacing
+
+| Rule | Do | Don't |
+|------|----|----- |
+| **Floating navbar** | Add `top-4 left-4 right-4` spacing | Stick navbar to `top-0 left-0 right-0` |
+| **Content padding** | Account for fixed navbar height | Let content hide behind fixed elements |
+| **Consistent max-width** | Use same `max-w-6xl` or `max-w-7xl` | Mix different container widths |
+
+---
+
+## Pre-Delivery Checklist
+
+Before delivering UI code, verify these items:
+
+### Visual Quality
+- [ ] No emojis used as icons (use SVG instead)
+- [ ] All icons from consistent icon set (Heroicons/Lucide)
+- [ ] Brand logos are correct (verified from Simple Icons)
+- [ ] Hover states don't cause layout shift
+- [ ] Use theme colors directly (bg-primary) not var() wrapper
+
+### Interaction
+- [ ] All clickable elements have `cursor-pointer`
+- [ ] Hover states provide clear visual feedback
+- [ ] Transitions are smooth (150-300ms)
+- [ ] Focus states visible for keyboard navigation
+
+### Light/Dark Mode
+- [ ] Light mode text has sufficient contrast (4.5:1 minimum)
+- [ ] Glass/transparent elements visible in light mode
+- [ ] Borders visible in both modes
+- [ ] Test both modes before delivery
+
+### Layout
+- [ ] Floating elements have proper spacing from edges
+- [ ] No content hidden behind fixed navbars
+- [ ] Responsive at 375px, 768px, 1024px, 1440px
+- [ ] No horizontal scroll on mobile
+
+### Accessibility
+- [ ] All images have alt text
+- [ ] Form inputs have labels
+- [ ] Color is not the only indicator
+- [ ] `prefers-reduced-motion` respected

+ 33 - 0
.cursorrules

@@ -0,0 +1,33 @@
+# Customization Rules
+
+These rules are used to guide AI agents and developers to ensure code quality and consistency in this project.
+
+## 1. Pinia Persistence
+-   **Plugin**: Use `pinia-plugin-persistedstate`.
+-   **Syntax**: Use the `persist` option with `paths` array.
+-   **Avoid**: Do NOT use `strategies` (deprecated/wrong plugin syntax).
+    ```typescript
+    // Correct
+    persist: {
+      paths: ['key1', 'key2']
+    }
+    ```
+
+## 2. TypeScript & Type Safety
+-   **Strict Types**: Avoid `any` type whenever possible.
+-   **Interfaces**: Define interfaces in `src/types/` for entities used across multiple files.
+    -   Example: `Product`, `Order`.
+-   **Props**: Use `PropType<T>` for complex Vue component props.
+
+## 3. Internationalization (i18n)
+-   **Strict usage**: Do NOT use hardcoded strings in templates or scripts.
+-   **Function**: Always use `t('key')` or `$t('key')`.
+-   **Locale Files**: Ensure keys exist in `zh-Hans.json`, `en.json`, `ja.json`, and `zh-Hant.json`.
+
+## 4. Vue 3 Composition API
+-   **Setup**: Use `<script setup lang="ts">`.
+-   **Vant UI**: Use Vant UI 4.x components.
+
+## 5. Mock Data
+-   **Labeling**: Clearly mark mock data comments with `// Mock`.
+-   **Separation**: Keep mock generation logic isolated or easily replaceable.

+ 2 - 1
.gitignore

@@ -28,4 +28,5 @@ dist-ssr
 .env.*.local
 .env.*.local
 
 
 .env.local.example
 .env.local.example
-.env.local.backup
+.env.local.backup
+.shared/ui-ux-pro-max/*

+ 7 - 1
index.html

@@ -1,8 +1,14 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="ja">
 <html lang="ja">
   <head>
   <head>
     <meta charset="UTF-8" />
     <meta charset="UTF-8" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
     <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+    <link rel="preconnect" href="https://fonts.googleapis.com" />
+    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
+    <link
+      href="https://fonts.googleapis.com/css2?family=Noto+Sans+JP:wght@300;400;500;700&family=Noto+Serif+JP:wght@400;500;600;700&display=swap"
+      rel="stylesheet"
+    />
     <meta
     <meta
       name="viewport"
       name="viewport"
       content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"
       content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"

+ 1 - 7
src/assets/styles/common.css

@@ -1,10 +1,4 @@
-:root {
-  /* App Variables */
-  --app-primary-color: #ff9900;
-  --app-secondary-color: #e6a23c;
-  --app-bg-color: #f7f8fa;
-  --app-text-color: #323233;
-}
+/* :root variables moved to theme.css */
 
 
 body {
 body {
   background-color: var(--app-bg-color);
   background-color: var(--app-bg-color);

+ 52 - 0
src/assets/styles/theme.css

@@ -0,0 +1,52 @@
+:root {
+  /* Brand Colors */
+  --van-primary-color: #f2930d; /* Fresh Orange */
+  --van-success-color: #07c160; /* Green */
+  --van-warning-color: #f2930d; /* Orange for warning/primary align */
+  --van-danger-color: #ee0a24;
+  
+  /* Backgrounds */
+  --van-background-color: #f7f8fa;
+  --van-background-2: #ffffff;
+  
+  /* Text */
+  --van-text-color: #323233;
+  --van-text-color-2: #646566;
+  --van-text-color-3: #969799;
+  
+  /* Typography */
+  --font-heading: 'Noto Serif JP', serif;
+  --font-body: 'Noto Sans JP', sans-serif;
+  
+  /* Spacing */
+  --van-padding-base: 4px;
+  --van-padding-xs: 8px;
+  --van-padding-sm: 12px;
+  --van-padding-md: 16px;
+  --van-padding-lg: 24px;
+  --van-padding-xl: 32px;
+  
+  /* BorderRadius */
+  --van-radius-sm: 4px;
+  --van-radius-md: 8px;
+  --van-radius-lg: 12px;
+  --van-radius-max: 999px;
+  
+  /* Shadows */
+  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
+  --shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08);
+  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);
+}
+
+body {
+  font-family: var(--font-body);
+  background-color: var(--van-background-color);
+  color: var(--van-text-color);
+  -webkit-font-smoothing: antialiased;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  font-family: var(--font-heading);
+  font-weight: 700;
+  margin: 0;
+}

+ 1 - 0
src/components.d.ts

@@ -33,6 +33,7 @@ declare module 'vue' {
     VanCheckbox: typeof import('vant/es')['Checkbox']
     VanCheckbox: typeof import('vant/es')['Checkbox']
     VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
     VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
     VanCouponList: typeof import('vant/es')['CouponList']
     VanCouponList: typeof import('vant/es')['CouponList']
+    VanDatePicker: typeof import('vant/es')['DatePicker']
     VanDivider: typeof import('vant/es')['Divider']
     VanDivider: typeof import('vant/es')['Divider']
     VanDropdownItem: typeof import('vant/es')['DropdownItem']
     VanDropdownItem: typeof import('vant/es')['DropdownItem']
     VanDropdownMenu: typeof import('vant/es')['DropdownMenu']
     VanDropdownMenu: typeof import('vant/es')['DropdownMenu']

+ 19 - 0
src/locale/en.json

@@ -248,6 +248,7 @@
     "enterCaptcha": "Enter OTP",
     "enterCaptcha": "Enter OTP",
     "getCaptcha": "Get OTP",
     "getCaptcha": "Get OTP",
     "loginNow": "Login Now",
     "loginNow": "Login Now",
+    "employee": "Staff",
     "invalidPhone": "Invalid phone format",
     "invalidPhone": "Invalid phone format",
     "checkAgreement": "Please check the agreement",
     "checkAgreement": "Please check the agreement",
     "autoCreateAccount": "New phone numbers will be automatically registered",
     "autoCreateAccount": "New phone numbers will be automatically registered",
@@ -264,6 +265,24 @@
     "privacyPolicy": "Privacy Policy",
     "privacyPolicy": "Privacy Policy",
     "success": "Login Success"
     "success": "Login Success"
   },
   },
+  "register": {
+    "title": "Customer Registration",
+    "subtitle": "Join LINE Order to get started",
+    "nickname": "Nickname",
+    "enterNickname": "Enter nickname",
+    "phoneticName": "Phonetic Name",
+    "enterPhonetic": "Katakana or Hiragana",
+    "mobileOrEmail": "Mobile Number or Email",
+    "enterContact": "e.g. 09012345678 or email@example.com",
+    "birthday": "Birthday",
+    "selectBirthday": "mm/dd/yyyy",
+    "referral": "Referral Code",
+    "optional": "Optional",
+    "favoriteFood": "Favorite Food",
+    "submit": "Register",
+    "orRegisterWith": "Or register with",
+    "hasAccount": "Already have an account?"
+  },
   "merchant": {
   "merchant": {
     "tabbar": {
     "tabbar": {
       "home": "Home",
       "home": "Home",

+ 19 - 0
src/locale/ja.json

@@ -248,6 +248,7 @@
     "enterCaptcha": "認証コードを入力してください",
     "enterCaptcha": "認証コードを入力してください",
     "getCaptcha": "認証コードを取得",
     "getCaptcha": "認証コードを取得",
     "loginNow": "今すぐログイン",
     "loginNow": "今すぐログイン",
+    "employee": "スタッフ",
     "invalidPhone": "電話番号が正しくありません",
     "invalidPhone": "電話番号が正しくありません",
     "checkAgreement": "規約に同意してください",
     "checkAgreement": "規約に同意してください",
     "autoCreateAccount": "未登録の電話番号は認証後に自動でアカウントが作成されます",
     "autoCreateAccount": "未登録の電話番号は認証後に自動でアカウントが作成されます",
@@ -264,6 +265,24 @@
     "privacyPolicy": "プライバシーポリシー",
     "privacyPolicy": "プライバシーポリシー",
     "success": "ログイン成功"
     "success": "ログイン成功"
   },
   },
+  "register": {
+    "title": "会員登録",
+    "subtitle": "LINE Orderへようこそ",
+    "nickname": "ニックネーム",
+    "enterNickname": "ニックネームを入力してください",
+    "phoneticName": "フリガナ",
+    "enterPhonetic": "カタカナまたはひらがな",
+    "mobileOrEmail": "電話番号またはメール",
+    "enterContact": "例: 09012345678 または email@example.com",
+    "birthday": "誕生日",
+    "selectBirthday": "mm/dd/yyyy",
+    "referral": "紹介コード",
+    "optional": "任意",
+    "favoriteFood": "好きな料理",
+    "submit": "登録する",
+    "orRegisterWith": "または以下で登録",
+    "hasAccount": "アカウントをお持ちですか?"
+  },
   "merchant": {
   "merchant": {
     "tabbar": {
     "tabbar": {
       "home": "ホーム",
       "home": "ホーム",

+ 19 - 0
src/locale/zh-Hans.json

@@ -248,6 +248,7 @@
     "enterCaptcha": "请输入验证码",
     "enterCaptcha": "请输入验证码",
     "getCaptcha": "获取验证码",
     "getCaptcha": "获取验证码",
     "loginNow": "立即登录",
     "loginNow": "立即登录",
+    "employee": "员工",
     "invalidPhone": "手机号码格式不对",
     "invalidPhone": "手机号码格式不对",
     "checkAgreement": "请勾选下面协议",
     "checkAgreement": "请勾选下面协议",
     "autoCreateAccount": "未注册的手机号验证后自动创建账号",
     "autoCreateAccount": "未注册的手机号验证后自动创建账号",
@@ -264,6 +265,24 @@
     "privacyPolicy": "隐私政策",
     "privacyPolicy": "隐私政策",
     "success": "登录成功"
     "success": "登录成功"
   },
   },
+  "register": {
+    "title": "会员注册",
+    "subtitle": "欢迎加入LINE Order",
+    "nickname": "昵称",
+    "enterNickname": "请输入昵称",
+    "phoneticName": "拼音名称",
+    "enterPhonetic": "片假名或平假名",
+    "mobileOrEmail": "手机号或邮箱",
+    "enterContact": "例如:09012345678 或 email@example.com",
+    "birthday": "生日",
+    "selectBirthday": "mm/dd/yyyy",
+    "referral": "推荐码",
+    "optional": "可选",
+    "favoriteFood": "喜欢的食物",
+    "submit": "注册",
+    "orRegisterWith": "或使用以下方式注册",
+    "hasAccount": "已有账号?"
+  },
   "merchant": {
   "merchant": {
     "tabbar": {
     "tabbar": {
       "home": "首页",
       "home": "首页",

+ 19 - 0
src/locale/zh-Hant.json

@@ -248,6 +248,7 @@
     "enterCaptcha": "請輸入驗證碼",
     "enterCaptcha": "請輸入驗證碼",
     "getCaptcha": "獲取驗證碼",
     "getCaptcha": "獲取驗證碼",
     "loginNow": "立即登錄",
     "loginNow": "立即登錄",
+    "employee": "員工",
     "invalidPhone": "手機號碼格式不對",
     "invalidPhone": "手機號碼格式不對",
     "checkAgreement": "請勾選下面協議",
     "checkAgreement": "請勾選下面協議",
     "autoCreateAccount": "未註冊的手機號驗證後自動創建賬號",
     "autoCreateAccount": "未註冊的手機號驗證後自動創建賬號",
@@ -264,6 +265,24 @@
     "privacyPolicy": "隱私政策",
     "privacyPolicy": "隱私政策",
     "success": "登錄成功"
     "success": "登錄成功"
   },
   },
+  "register": {
+    "title": "會員註冊",
+    "subtitle": "歡迎加入LINE Order",
+    "nickname": "暱稱",
+    "enterNickname": "請輸入暱稱",
+    "phoneticName": "拼音名稱",
+    "enterPhonetic": "片假名或平假名",
+    "mobileOrEmail": "手機號或郵箱",
+    "enterContact": "例如:09012345678 或 email@example.com",
+    "birthday": "生日",
+    "selectBirthday": "mm/dd/yyyy",
+    "referral": "推薦碼",
+    "optional": "可選",
+    "favoriteFood": "喜歡的食物",
+    "submit": "註冊",
+    "orRegisterWith": "或使用以下方式註冊",
+    "hasAccount": "已有賬號?"
+  },
   "merchant": {
   "merchant": {
     "tabbar": {
     "tabbar": {
       "home": "首頁",
       "home": "首頁",

+ 1 - 0
src/main.ts

@@ -7,6 +7,7 @@ import App from './App.vue'
 
 
 // 导入全局样式
 // 导入全局样式
 import './assets/styles/reset.css'
 import './assets/styles/reset.css'
+import './assets/styles/theme.css'
 import './assets/styles/common.css'
 import './assets/styles/common.css'
 import 'vant/lib/index.css'
 import 'vant/lib/index.css'
 import Vant from 'vant'
 import Vant from 'vant'

+ 8 - 0
src/router/routes.ts

@@ -79,6 +79,14 @@ export default [
       requiresAuth: true
       requiresAuth: true
     }
     }
   },
   },
+  {
+    path: '/register',
+    name: 'Register',
+    component: () => import('@/views/login/register.vue'),
+    meta: {
+      title: 'register.title'
+    }
+  },
   {
   {
     path: '/login',
     path: '/login',
     name: 'Login',
     name: 'Login',

+ 12 - 18
src/store/modules/merchant.ts

@@ -1,7 +1,8 @@
 import { defineStore } from 'pinia'
 import { defineStore } from 'pinia'
+import type { MerchantState, Product, Order } from '@/types/merchant'
 
 
 export const useMerchantStore = defineStore('merchant', {
 export const useMerchantStore = defineStore('merchant', {
-  state: () => ({
+  state: (): MerchantState => ({
     shopInfo: {
     shopInfo: {
       name: '',
       name: '',
       isOpen: true,
       isOpen: true,
@@ -20,8 +21,8 @@ export const useMerchantStore = defineStore('merchant', {
       completed: 0
       completed: 0
     },
     },
     pendingOrderCount: 0,
     pendingOrderCount: 0,
-    orders: [] as any[],
-    products: [] as any[]
+    orders: [],
+    products: []
   }),
   }),
 
 
   getters: {
   getters: {
@@ -62,9 +63,9 @@ export const useMerchantStore = defineStore('merchant', {
           createTime: '2024-01-01 12:30',
           createTime: '2024-01-01 12:30',
           amount: 128.5,
           amount: 128.5,
           status: 'pending',
           status: 'pending',
-          items: [{ name: '招牌拉面', quantity: 2 }]
+          items: [{ name: '招牌拉面', quantity: 2, price: 64.25 }] // Adjusted to match OrderItem type
         }
         }
-      ]
+      ] as Order[]
       return { list: this.orders, total: 1 }
       return { list: this.orders, total: 1 }
     },
     },
 
 
@@ -78,7 +79,6 @@ export const useMerchantStore = defineStore('merchant', {
             price: 38,
             price: 38,
             category: '面食',
             category: '面食',
             description: '这是招牌拉面',
             description: '这是招牌拉面',
-            image: '',
             status: true,
             status: true,
             spec: '大碗'
             spec: '大碗'
           },
           },
@@ -88,7 +88,6 @@ export const useMerchantStore = defineStore('merchant', {
             price: 28,
             price: 28,
             category: '小吃',
             category: '小吃',
             description: '酥脆炸猪排',
             description: '酥脆炸猪排',
-            image: '',
             status: true,
             status: true,
             spec: ''
             spec: ''
           }
           }
@@ -97,8 +96,8 @@ export const useMerchantStore = defineStore('merchant', {
       return this.products
       return this.products
     },
     },
 
 
-    async addProduct(product: any) {
-      const newProduct = {
+    async addProduct(product: Omit<Product, 'id'>) {
+      const newProduct: Product = {
         ...product,
         ...product,
         id: Date.now().toString(),
         id: Date.now().toString(),
         status: true
         status: true
@@ -107,24 +106,19 @@ export const useMerchantStore = defineStore('merchant', {
       return newProduct
       return newProduct
     },
     },
 
 
-    async updateProduct(id: string, product: any) {
-      const index = this.products.findIndex((p: any) => p.id === id)
+    async updateProduct(id: string, product: Partial<Product>) {
+      const index = this.products.findIndex((p) => p.id === id)
       if (index > -1) {
       if (index > -1) {
         this.products[index] = { ...this.products[index], ...product }
         this.products[index] = { ...this.products[index], ...product }
       }
       }
     },
     },
 
 
     async deleteProduct(id: string) {
     async deleteProduct(id: string) {
-      this.products = this.products.filter((p: any) => p.id !== id)
+      this.products = this.products.filter((p) => p.id !== id)
     }
     }
   },
   },
 
 
   persist: {
   persist: {
-    strategies: [
-      {
-        storage: localStorage,
-        paths: ['shopInfo', 'products']
-      }
-    ]
+    paths: ['shopInfo', 'products']
   }
   }
 })
 })

+ 57 - 0
src/types/merchant.ts

@@ -0,0 +1,57 @@
+export interface Product {
+  id: string;
+  name: string;
+  price: number;
+  category: string;
+  description: string;
+  image?: string;
+  status: boolean;
+  spec?: string;
+}
+
+export interface OrderItem {
+    id?: string;
+    name: string;
+    spec?: string;
+    quantity: number;
+    price: number; // Added price to match component usage
+}
+
+export interface Order {
+    id: string;
+    orderNo: string;
+    tableName?: string; // Optional based on context
+    guestCount?: number;
+    createTime: string | number; // Support both format and timestamp
+    startTime?: number;
+    amount: number;
+    subtotal?: number;
+    serviceCharge?: number;
+    status: 'pending' | 'preparing' | 'delivering' | 'completed' | 'cancelled';
+    items: OrderItem[];
+}
+
+export interface ShopInfo {
+    name: string;
+    isOpen: boolean;
+    hours: string;
+}
+
+export interface MerchantState {
+    shopInfo: ShopInfo;
+    todayStats: {
+        revenue: number;
+        orderCount: number;
+        takeoutCount: number;
+        dineInCount: number;
+    };
+    orderStats: {
+        pending: number;
+        preparing: number;
+        delivering: number;
+        completed: number;
+    };
+    pendingOrderCount: number;
+    orders: Order[];
+    products: Product[];
+}

+ 34 - 26
src/views/index/index.vue

@@ -317,7 +317,8 @@ onMounted(async () => {
 <style scoped>
 <style scoped>
 .index-page {
 .index-page {
   min-height: 100vh;
   min-height: 100vh;
-  background: #f5f5f5;
+  background: var(--van-background-color);
+  font-family: var(--font-body);
 }
 }
 
 
 .top-bar {
 .top-bar {
@@ -325,20 +326,20 @@ onMounted(async () => {
   top: 0;
   top: 0;
   left: 0;
   left: 0;
   right: 0;
   right: 0;
-  background: #fff;
-  padding: 12px 16px;
+  background: var(--van-background-2);
+  padding: var(--van-padding-sm) var(--van-padding-md);
   z-index: 100;
   z-index: 100;
-  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+  box-shadow: var(--shadow-sm);
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
-  gap: 12px;
+  gap: var(--van-padding-sm);
 }
 }
 
 
 .top-row {
 .top-row {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: space-between;
   justify-content: space-between;
-  gap: 12px;
+  gap: var(--van-padding-sm);
 }
 }
 
 
 .location-bar {
 .location-bar {
@@ -347,13 +348,13 @@ onMounted(async () => {
   gap: 4px;
   gap: 4px;
   flex: 1;
   flex: 1;
   cursor: pointer;
   cursor: pointer;
+  color: var(--van-text-color);
 }
 }
 
 
 .location-text {
 .location-text {
   flex: 1;
   flex: 1;
   font-size: 14px;
   font-size: 14px;
   font-weight: 500;
   font-weight: 500;
-  color: #333;
   overflow: hidden;
   overflow: hidden;
   text-overflow: ellipsis;
   text-overflow: ellipsis;
   white-space: nowrap;
   white-space: nowrap;
@@ -365,13 +366,14 @@ onMounted(async () => {
 }
 }
 
 
 .banner-swipe {
 .banner-swipe {
-  margin: 0 12px 12px;
-  border-radius: 12px;
+  margin: 0 var(--van-padding-sm) var(--van-padding-sm);
+  border-radius: var(--van-radius-lg);
   overflow: hidden;
   overflow: hidden;
+  box-shadow: var(--shadow-sm);
 }
 }
 
 
 .banner-item {
 .banner-item {
-  background: #f5f5f5;
+  background: var(--van-gray-1); /* Fallback or define in theme */
   cursor: pointer;
   cursor: pointer;
 }
 }
 
 
@@ -380,27 +382,29 @@ onMounted(async () => {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
-  color: #ddd;
+  color: var(--van-text-color-3);
+  background-color: #e0e0e0;
 }
 }
 
 
 .category-section {
 .category-section {
-  background: #fff;
-  margin: 0 12px 12px;
-  border-radius: 12px;
-  padding: 16px;
+  background: var(--van-background-2);
+  margin: 0 var(--van-padding-sm) var(--van-padding-sm);
+  border-radius: var(--van-radius-lg);
+  padding: var(--van-padding-md);
+  box-shadow: var(--shadow-sm);
 }
 }
 
 
 .category-grid {
 .category-grid {
   display: grid;
   display: grid;
   grid-template-columns: repeat(4, 1fr);
   grid-template-columns: repeat(4, 1fr);
-  gap: 16px;
+  gap: var(--van-padding-md);
 }
 }
 
 
 .category-item {
 .category-item {
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
   align-items: center;
   align-items: center;
-  gap: 8px;
+  gap: var(--van-padding-xs);
   cursor: pointer;
   cursor: pointer;
 }
 }
 
 
@@ -412,45 +416,49 @@ onMounted(async () => {
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
   transition: transform 0.3s;
   transition: transform 0.3s;
+  /* Icon background handled inline with opacity */
 }
 }
 
 
 .category-item:active .category-icon {
 .category-item:active .category-icon {
-  transform: scale(0.9);
+  transform: scale(0.95);
 }
 }
 
 
 .category-name {
 .category-name {
   font-size: 12px;
   font-size: 12px;
-  color: #666;
+  color: var(--van-text-color-2);
+  font-weight: 500;
 }
 }
 
 
 .recommend-section {
 .recommend-section {
-  margin: 0 12px;
+  margin: 0 var(--van-padding-sm);
 }
 }
 
 
 .section-header {
 .section-header {
   display: flex;
   display: flex;
   justify-content: space-between;
   justify-content: space-between;
   align-items: center;
   align-items: center;
-  margin-bottom: 12px;
+  margin-bottom: var(--van-padding-sm);
 }
 }
 
 
 .section-header h3 {
 .section-header h3 {
   margin: 0;
   margin: 0;
   font-size: 18px;
   font-size: 18px;
-  font-weight: bold;
-  color: #333;
+  font-family: var(--font-heading);
+  color: var(--van-text-color);
 }
 }
 
 
 .more {
 .more {
   font-size: 14px;
   font-size: 14px;
-  color: #1989fa;
+  color: var(--van-primary-color);
   cursor: pointer;
   cursor: pointer;
+  display: flex;
+  align-items: center;
 }
 }
 
 
 .shop-list {
 .shop-list {
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
-  gap: 12px;
+  gap: var(--van-padding-sm);
 }
 }
 
 
 .loading-more {
 .loading-more {
@@ -459,7 +467,7 @@ onMounted(async () => {
   justify-content: center;
   justify-content: center;
   gap: 8px;
   gap: 8px;
   padding: 20px;
   padding: 20px;
-  color: #999;
+  color: var(--van-text-color-3);
   font-size: 14px;
   font-size: 14px;
 }
 }
 </style>
 </style>

+ 42 - 19
src/views/login/components/PasswordLogin.vue

@@ -198,18 +198,18 @@ const handleRegister = () => {
   .phone-input-group {
   .phone-input-group {
     display: flex;
     display: flex;
     align-items: flex-end;
     align-items: flex-end;
-    gap: 12px;
-    margin-bottom: 20px;
+    gap: var(--van-padding-sm);
+    margin-bottom: var(--van-padding-lg);
   }
   }
 
 
   .area-code-field {
   .area-code-field {
     width: 100px;
     width: 100px;
     flex-shrink: 0;
     flex-shrink: 0;
-    padding: 10px 16px;
-    background: #f7f8fa;
-    border-radius: 4px;
+    padding: 10px var(--van-padding-md);
+    background: var(--van-background-color);
+    border-radius: var(--van-radius-md);
     cursor: pointer;
     cursor: pointer;
-    transition: background 0.3s;
+    transition: background 0.2s;
 
 
     &:active {
     &:active {
       background: #ebedf0;
       background: #ebedf0;
@@ -217,7 +217,7 @@ const handleRegister = () => {
 
 
     .area-code-label {
     .area-code-label {
       font-size: 12px;
       font-size: 12px;
-      color: #646566;
+      color: var(--van-text-color-2);
       margin-bottom: 4px;
       margin-bottom: 4px;
     }
     }
 
 
@@ -226,7 +226,7 @@ const handleRegister = () => {
       align-items: center;
       align-items: center;
       justify-content: space-between;
       justify-content: space-between;
       font-size: 14px;
       font-size: 14px;
-      color: #323233;
+      color: var(--van-text-color);
       font-weight: 500;
       font-weight: 500;
     }
     }
   }
   }
@@ -235,43 +235,66 @@ const handleRegister = () => {
     flex: 1;
     flex: 1;
 
 
     :deep(.van-cell) {
     :deep(.van-cell) {
-      padding: 10px 16px;
+      padding: 10px var(--van-padding-md);
+      border-radius: var(--van-radius-md);
+      background: var(--van-background-color);
+    }
+    
+    :deep(.van-cell:after) {
+      border-bottom: none;
     }
     }
   }
   }
 
 
   .input-group {
   .input-group {
-    margin-bottom: 20px;
+    margin-bottom: var(--van-padding-md);
+    
+    :deep(.van-cell) {
+      padding: 12px var(--van-padding-md);
+      border-radius: var(--van-radius-md);
+      background: var(--van-background-color);
+    }
+    
+    :deep(.van-cell:after) {
+      border-bottom: none;
+    }
   }
   }
 
 
   .forgot-password {
   .forgot-password {
     text-align: right;
     text-align: right;
-    margin-bottom: 30px;
-    padding-right: 16px;
+    margin-bottom: var(--van-padding-xl);
+    padding-right: var(--van-padding-xs);
 
 
     span {
     span {
       font-size: 12px;
       font-size: 12px;
-      color: #09b4f1;
+      color: var(--van-text-color-2);
       cursor: pointer;
       cursor: pointer;
+      
+      &:hover {
+        color: var(--van-primary-color);
+      }
     }
     }
   }
   }
 
 
   .login-btn {
   .login-btn {
-    height: 44px;
+    height: 48px;
     font-size: 16px;
     font-size: 16px;
-    background-color: #09b4f1;
-    border-color: #09b4f1;
+    font-weight: 600;
+    background-color: var(--van-primary-color);
+    border-color: var(--van-primary-color);
+    box-shadow: var(--shadow-sm);
   }
   }
 
 
   .register-tip {
   .register-tip {
-    margin-top: 20px;
+    margin-top: var(--van-padding-lg);
     text-align: center;
     text-align: center;
     font-size: 12px;
     font-size: 12px;
-    color: #969799;
+    color: var(--van-text-color-2);
 
 
     .link {
     .link {
-      color: #09b4f1;
+      color: var(--van-primary-color);
       margin-left: 8px;
       margin-left: 8px;
       cursor: pointer;
       cursor: pointer;
+      font-weight: 500;
     }
     }
   }
   }
 }
 }

+ 38 - 17
src/views/login/components/PhoneCodeLogin.vue

@@ -247,18 +247,18 @@ onUnmounted(() => {
   .phone-input-group {
   .phone-input-group {
     display: flex;
     display: flex;
     align-items: flex-end;
     align-items: flex-end;
-    gap: 12px;
-    margin-bottom: 20px;
+    gap: var(--van-padding-sm);
+    margin-bottom: var(--van-padding-lg);
   }
   }
 
 
   .area-code-field {
   .area-code-field {
     width: 100px;
     width: 100px;
     flex-shrink: 0;
     flex-shrink: 0;
-    padding: 10px 16px;
-    background: #f7f8fa;
-    border-radius: 4px;
+    padding: 10px var(--van-padding-md);
+    background: var(--van-background-color);
+    border-radius: var(--van-radius-md);
     cursor: pointer;
     cursor: pointer;
-    transition: background 0.3s;
+    transition: background 0.2s;
 
 
     &:active {
     &:active {
       background: #ebedf0;
       background: #ebedf0;
@@ -266,7 +266,7 @@ onUnmounted(() => {
 
 
     .area-code-label {
     .area-code-label {
       font-size: 12px;
       font-size: 12px;
-      color: #646566;
+      color: var(--van-text-color-2);
       margin-bottom: 4px;
       margin-bottom: 4px;
     }
     }
 
 
@@ -275,7 +275,7 @@ onUnmounted(() => {
       align-items: center;
       align-items: center;
       justify-content: space-between;
       justify-content: space-between;
       font-size: 14px;
       font-size: 14px;
-      color: #323233;
+      color: var(--van-text-color);
       font-weight: 500;
       font-weight: 500;
     }
     }
   }
   }
@@ -284,7 +284,13 @@ onUnmounted(() => {
     flex: 1;
     flex: 1;
 
 
     :deep(.van-cell) {
     :deep(.van-cell) {
-      padding: 10px 16px;
+      padding: 10px var(--van-padding-md);
+      border-radius: var(--van-radius-md);
+      background: var(--van-background-color);
+    }
+    
+    :deep(.van-cell:after) {
+      border-bottom: none;
     }
     }
   }
   }
 
 
@@ -293,31 +299,46 @@ onUnmounted(() => {
     align-items: center;
     align-items: center;
     gap: 6px;
     gap: 6px;
     font-size: 12px;
     font-size: 12px;
-    color: #969799;
-    margin-bottom: 30px;
-    padding: 0 16px;
+    color: var(--van-text-color-3);
+    margin-bottom: var(--van-padding-lg);
+    padding: 0 var(--van-padding-xs);
+    
+    .van-icon {
+        color: var(--van-primary-color) !important;
+    }
   }
   }
 
 
   .captcha-group {
   .captcha-group {
     display: flex;
     display: flex;
     align-items: center;
     align-items: center;
-    gap: 10px;
-    margin-bottom: 30px;
+    gap: var(--van-padding-sm);
+    margin-bottom: var(--van-padding-xl);
 
 
     :deep(.van-field) {
     :deep(.van-field) {
       flex: 1;
       flex: 1;
+      border-radius: var(--van-radius-md);
+      background: var(--van-background-color);
+      padding: 10px var(--van-padding-md);
+    }
+    
+    :deep(.van-field:after) {
+      border-bottom: none;
     }
     }
 
 
     .van-button {
     .van-button {
       flex-shrink: 0;
       flex-shrink: 0;
+      height: 44px;
+      border-radius: var(--van-radius-md);
     }
     }
   }
   }
 
 
   .login-btn {
   .login-btn {
-    height: 44px;
+    height: 48px;
     font-size: 16px;
     font-size: 16px;
-    background-color: #09b4f1;
-    border-color: #09b4f1;
+    font-weight: 600;
+    background-color: var(--van-primary-color);
+    border-color: var(--van-primary-color);
+    box-shadow: var(--shadow-sm);
   }
   }
 }
 }
 </style>
 </style>

+ 548 - 170
src/views/login/login.vue

@@ -1,248 +1,626 @@
 <template>
 <template>
   <div class="login-page">
   <div class="login-page">
-    <div class="login-container">
-      <!-- 标题 -->
-      <div class="title">
-        {{ $t('login.welcome') }}
+    <!-- Top Bar -->
+    <div class="login-top-bar">
+      <div class="employee-entry" @click="handleEmployeeLogin">
+        <van-icon name="friends-o" size="24" color="#181511" />
+        <span class="employee-text">{{ $t('login.employee') || 'Staff' }}</span>
       </div>
       </div>
-
-      <!-- 登录方式切换 -->
-      <van-tabs v-model:active="loginType" class="login-tabs" animated>
-        <!-- 手机验证码登录 -->
-        <van-tab :title="$t('login.phoneLogin')" name="phone">
-          <PhoneCodeLogin
-            :agreed="agreedToTerms"
-            @login-success="handleLoginSuccess"
-          />
-        </van-tab>
-
-        <!-- 密码登录 -->
-        <van-tab :title="$t('login.passwordLogin')" name="password">
-          <PasswordLogin
-            :agreed="agreedToTerms"
-            @login-success="handleLoginSuccess"
-          />
-        </van-tab>
-      </van-tabs>
-
-      <!-- 注册提示 -->
-      <div class="register-hint">
-        {{ $t('login.noAccount') }} 
-        <span class="link" @click="loginType = 'phone'">{{ $t('login.registerNow') }}</span>
-      </div>
-
-      <!-- LINE登录 (仅在LINE环境中显示) -->
-      <div v-if="shouldShowLineFeatures" class="third-party-login">
-        <van-divider>{{ $t('login.orLoginWith') }}</van-divider>
-        <van-button
-          type="success"
-          round
-          block
-          @click="handleLineLogin"
-          class="line-login-btn"
-        >
-          <van-icon name="chat-o" />
-          LINE {{ $t('login.quickLogin') }}
-        </van-button>
+      <div class="lang-selector" @click="showLangAction = true">
+        <van-icon name="globe-o" size="18" class="mr-1" />
+        <span class="lang-text">{{ currentLangText }}</span>
+        <van-icon name="arrow-down" size="14" />
       </div>
       </div>
+    </div>
 
 
-      <!-- 环境提示 (仅在开发模式显示) -->
-      <div v-if="isDev" class="env-tip">
-        <van-tag :type="isLineEnvironment ? 'success' : 'primary'" size="medium">
-          {{ $t('login.currentEnvironment') }}: {{ environmentName }}
-        </van-tag>
+    <div class="login-card">
+      <!-- Header Area with Pattern -->
+      <div class="card-header">
+        <div class="pattern-bg"></div>
+        <div class="header-icon-wrapper">
+          <div class="header-icon">
+            <van-icon name="shop-o" size="40" color="#f2930d" />
+          </div>
+        </div>
       </div>
       </div>
 
 
-      <!-- 用户协议 -->
-      <div class="agreement">
-        <van-checkbox v-model="agreedToTerms" icon-size="14px">
-          {{ $t('login.agreementPrefix') }}
-          <span class="link" @click.stop="showAgreement('user')">
-            《{{ $t('login.userAgreement') }}》
-          </span>
-          {{ $t('login.and') }}
-          <span class="link" @click.stop="showAgreement('privacy')">
-            《{{ $t('login.privacyPolicy') }}》
-          </span>
-        </van-checkbox>
+      <!-- Content Area -->
+      <div class="card-content">
+        <div class="text-center mb-8">
+          <h2 class="welcome-title">{{ $t('login.welcome') || 'Welcome to LINE Order' }}</h2>
+          <p class="welcome-subtitle">Order your favorites with ease.</p>
+        </div>
+
+        <!-- Phone Login Mode -->
+        <div v-if="loginMode === 'phone'" class="form-section">
+          <div class="form-group">
+            <label class="form-label">{{ $t('login.phone') || 'Mobile Number' }}</label>
+            <div class="phone-input-pill">
+              <div class="flag-prefix">
+                <span class="flag">🇯🇵</span>
+                <span class="prefix">+81</span>
+              </div>
+              <van-field
+                v-model="mobile"
+                type="tel"
+                class="pill-field"
+                :placeholder="$t('login.enterPhone') || '(555) 000-0000'"
+                :border="false"
+                clearable
+              />
+            </div>
+          </div>
+          
+          <div v-if="showCaptchaInput" class="form-group mt-4">
+            <label class="form-label">{{ $t('login.captcha') }}</label>
+            <div class="input-pill">
+                <van-field
+                    v-model="captcha"
+                    type="digit"
+                    class="pill-field"
+                    :placeholder="$t('login.enterCaptcha')"
+                    :border="false"
+                    clearable
+                />
+            </div>
+          </div>
+
+          <van-button 
+            type="primary"
+            round
+            block
+            class="action-btn"
+            :loading="loading"
+            @click="handlePhoneAction"
+          >
+            {{ showCaptchaInput ? $t('login.loginNow') : $t('login.getCaptcha') }}
+            <template #icon v-if="!loading">
+               <van-icon name="arrow" />
+            </template>
+          </van-button>
+
+          <div class="text-center pt-4">
+            <div class="text-link" @click="loginMode = 'password'">
+              {{ $t('login.passwordLogin') || 'Log in with Password instead' }}
+            </div>
+          </div>
+        </div>
+
+        <!-- Password Login Mode -->
+        <div v-else class="form-section">
+          <div class="form-group">
+            <label class="form-label">{{ $t('login.phone') }}</label>
+            <div class="phone-input-pill">
+              <div class="flag-prefix">
+                <span class="flag">🇯🇵</span>
+                <span class="prefix">+81</span>
+              </div>
+              <van-field
+                v-model="mobile"
+                type="tel"
+                class="pill-field"
+                :placeholder="$t('login.enterPhone')"
+                :border="false"
+                clearable
+              />
+            </div>
+          </div>
+
+          <div class="form-group mt-4">
+             <label class="form-label">{{ $t('login.password') }}</label>
+             <div class="input-pill">
+               <van-field
+                 v-model="password"
+                 :type="showPassword ? 'text' : 'password'"
+                 class="pill-field"
+                 :placeholder="$t('login.enterPassword')" 
+                 :border="false"
+                 :right-icon="showPassword ? 'eye-o' : 'closed-eye'"
+                 @click-right-icon="showPassword = !showPassword"
+               />
+             </div>
+          </div>
+
+          <van-button 
+            type="primary"
+            round
+            block
+            class="action-btn" 
+            :loading="loading"
+            @click="handlePasswordLogin"
+          >
+            {{ $t('login.loginNow') }}
+             <template #icon v-if="!loading">
+               <van-icon name="arrow" />
+            </template>
+          </van-button>
+
+          <div class="text-center pt-4">
+            <div class="text-link" @click="loginMode = 'phone'">
+              {{ $t('login.phoneLogin') || 'Log in with Phone instead' }}
+            </div>
+          </div>
+        </div>
+
+        <!-- Divider -->
+        <div class="divider-section">
+          <div class="divider-line"></div>
+          <div class="divider-text">{{ $t('login.orLoginWith') || 'Or continue with' }}</div>
+        </div>
+
+        <!-- Social Buttons -->
+        <div class="social-grid">
+          <van-button round class="social-btn line" @click="handleLineLogin">
+            <svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2.5C6.6 2.5 2.5 6.2 2.5 10.8c0 2.6 1.3 4.9 3.4 6.4-.2.7-.8 2.5-.9 2.9-.1.3 0 .6.3.6.1 0 .2 0 .3-.1 3.5-2 3.9-2.2 4.2-2.3.4.1.8.1 1.2.1 5.4 0 9.5-3.7 9.5-8.3S17.4 2.5 12 2.5z" fill="white"></path></svg>
+          </van-button>
+          <van-button round class="social-btn" @click="showToast('Google Login')">
+             <svg class="w-6 h-6" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"></path><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"></path><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"></path><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"></path></svg>
+          </van-button>
+          <van-button round class="social-btn" @click="showToast('Apple Login')">
+              <svg class="w-6 h-6" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.74 1.18 0 2.45-1.02 3.93-.84 1.16.14 2.29.58 3.09 1.54-.1.06-2.09 1.23-2.03 3.77.06 2.65 2.44 3.59 2.5 3.63-.03.11-.38 1.34-1.27 2.68-.82 1.23-1.68 2.45-3.03 2.45h-.26zM13.03 4.98c.67-.84 1.15-2.02.97-3.18-1.02.04-2.29.69-3.02 1.56-.63.74-1.19 1.95-.94 3.08 1.13.08 2.29-.61 2.99-1.46z"></path></svg>
+          </van-button>
+        </div>
+
+        <div class="mt-auto text-center pt-8">
+            <p class="footer-text">
+                {{ $t('login.newToApp') || 'New to LINE Order?' }}
+                <a class="register-link" @click="router.push('/register')">
+                    {{ $t('login.registerNow') || 'Register Now' }}
+                </a>
+            </p>
+        </div>
       </div>
       </div>
     </div>
     </div>
+    
+    <!-- Language Action Sheet -->
+    <van-action-sheet
+      v-model:show="showLangAction"
+      :actions="langActions"
+      @select="onSelectLang"
+      :cancel-text="$t('common.cancel') || 'Cancel'"
+      close-on-click-action
+    />
   </div>
   </div>
 </template>
 </template>
 
 
 <script setup lang="ts">
 <script setup lang="ts">
-import { ref, onMounted } from 'vue'
+import { ref, onMounted, computed } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import { useRouter, useRoute } from 'vue-router'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
 import { showToast } from 'vant'
 import { showToast } from 'vant'
 import { useUserStore } from '@/store/modules/user'
 import { useUserStore } from '@/store/modules/user'
-import { lineLogin as apiLineLogin } from '@/api/auth'
+import { login as apiLogin, sendSmsCode, lineLogin as apiLineLogin } from '@/api/auth'
 import { useLiff } from '@/composables/useLiff'
 import { useLiff } from '@/composables/useLiff'
-import { useEnv } from '@/composables/useEnv'
-import PhoneCodeLogin from './components/PhoneCodeLogin.vue'
-import PasswordLogin from './components/PasswordLogin.vue'
 
 
-const { t } = useI18n()
+// TypeScript Interfaces
+interface LangAction {
+  name: string
+  value: string
+}
+
+interface LoginResponse {
+  userInfo: any
+  accessToken: string
+}
+
+const { t, locale } = useI18n()
 const router = useRouter()
 const router = useRouter()
 const route = useRoute()
 const route = useRoute()
 const userStore = useUserStore()
 const userStore = useUserStore()
 const { init: initLiff, login: liffLogin, getAccessToken, isLoggedIn } = useLiff()
 const { init: initLiff, login: liffLogin, getAccessToken, isLoggedIn } = useLiff()
 
 
-// 环境检测
-const {
-  shouldShowLineFeatures,
-  isLineEnvironment,
-  environmentName
-} = useEnv()
-
-// 是否为开发模式
-const isDev = ref(import.meta.env.DEV)
+// Logic State
+const loginMode = ref<'phone' | 'password'>('phone')
+const mobile = ref('')
+const captcha = ref('')
+const password = ref('')
+const showPassword = ref(false)
+const showCaptchaInput = ref(false) // Whether code has been sent
+const loading = ref(false)
+const showLangAction = ref(false)
+
+// Language Actions
+const langActions: LangAction[] = [
+  { name: '日本語', value: 'ja' },
+  { name: 'English', value: 'en' },
+  { name: '简体中文', value: 'zh-Hans' },
+  { name: '繁體中文', value: 'zh-Hant' }
+]
+
+const currentLangText = computed(() => {
+  const current = langActions.find(item => item.value === locale.value)
+  return current ? current.name : 'Language'
+})
 
 
-// 登录方式
-const loginType = ref<'phone' | 'password'>('phone')
+const onSelectLang = (item: LangAction) => {
+  locale.value = item.value
+  showLangAction.value = false
+  localStorage.setItem('locale', item.value)
+}
 
 
-// 用户协议
-const agreedToTerms = ref(false)
+const handleEmployeeLogin = () => {
+    // Navigate to employee/merchant login if exists, else show toast
+    showToast(t('common.featureInDevelopment'))
+}
 
 
-// 初始化LIFF
 onMounted(async () => {
 onMounted(async () => {
   await initLiff()
   await initLiff()
-
-  // 如果已经登录,直接跳转
   if (userStore.isLogin) {
   if (userStore.isLogin) {
     router.replace((route.query.redirect as string) || '/index')
     router.replace((route.query.redirect as string) || '/index')
   }
   }
 })
 })
 
 
-// 登录成功处理
-const handleLoginSuccess = (data: any) => {
-  userStore.setMember(data.userInfo)
-  userStore.setToken(data.accessToken)
-
-  showToast(t('login.success'))
+// Phone Flow
+const handlePhoneAction = async () => {
+    if (!mobile.value) {
+        showToast(t('login.enterPhone'))
+        return
+    }
+    
+    // Step 1: Send Code
+    if (!showCaptchaInput.value) {
+        await handleSendCode()
+        return
+    }
 
 
-  setTimeout(() => {
-    const redirect = (route.query.redirect as string) || '/index'
-    router.replace(redirect)
-  }, 1000)
+    // Step 2: Login with Code
+    await handleLogin()
 }
 }
 
 
-// LINE登录
-const handleLineLogin = async () => {
-  if (!agreedToTerms.value) {
-    showToast(t('login.checkAgreement'))
-    return
-  }
-
-  try {
-    if (!isLoggedIn.value) {
-      await liffLogin()
+const handleSendCode = async () => {
+    loading.value = true
+    try {
+        const fullPhone = '81' + mobile.value.replace(/^0+/, '') // Mock JP code
+        await sendSmsCode({ mobile: fullPhone, scene: 1 })
+        showToast(t('login.captchaSent'))
+        showCaptchaInput.value = true
+        // Start countdown (simplified for design view)
+    } catch (error) {
+        console.error(error)
+        showToast(t('login.sendFailed'))
+    } finally {
+        loading.value = false
     }
     }
+}
 
 
-    const accessToken = await getAccessToken()
-    if (!accessToken) {
-      showToast('获取LINE认证信息失败')
-      return
+const handleLogin = async () => {
+    if (!captcha.value) {
+        showToast(t('login.enterCaptcha'))
+        return
     }
     }
+    loading.value = true
+    try {
+        const fullPhone = '81' + mobile.value.replace(/^0+/, '')
+        const res = await apiLogin({ mobile: fullPhone, code: captcha.value })
+        if (res) handleLoginSuccess(res)
+    } catch(err) {
+        console.error(err)
+    } finally {
+        loading.value = false
+    }
+}
 
 
-    const res = await apiLineLogin({ token: accessToken })
-
-    if (res) {
-      handleLoginSuccess(res)
+const handlePasswordLogin = async () => {
+    if (!mobile.value || !password.value) return showToast(t('login.error'))
+    loading.value = true
+    try {
+        const fullPhone = '81' + mobile.value.replace(/^0+/, '')
+        const res = await apiLogin({ mobile: fullPhone, password: password.value })
+        if (res) handleLoginSuccess(res)
+    } finally {
+        loading.value = false
     }
     }
+}
+
+const handleLineLogin = async () => {
+  try {
+    if (!isLoggedIn.value) await liffLogin()
+    const accessToken = await getAccessToken()
+    if (!accessToken) return showToast('LINE Login Error')
+    const res = await apiLineLogin(accessToken)
+    if (res) handleLoginSuccess(res)
   } catch (error) {
   } catch (error) {
-    console.error('LINE登录失败:', error)
-    showToast('LINE登录失败')
+    console.error(error)
+    showToast('LINE Login Failed')
   }
   }
 }
 }
 
 
-// 显示协议
-const showAgreement = (type: string) => {
-  showToast(t('common.featureInDevelopment'))
+const handleLoginSuccess = (data: LoginResponse) => {
+  userStore.setMember(data.userInfo)
+  userStore.setToken(data.accessToken)
+  showToast(t('login.success'))
+  
+  const REDIRECT_DELAY = 1000
+  setTimeout(() => {
+    router.replace((route.query.redirect as string) || '/index')
+  }, REDIRECT_DELAY)
 }
 }
 </script>
 </script>
 
 
 <style scoped lang="scss">
 <style scoped lang="scss">
 .login-page {
 .login-page {
   min-height: 100vh;
   min-height: 100vh;
-  background: #fff;
-  padding: 20px;
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+  font-family: var(--font-body);
+  position: relative;
 }
 }
 
 
-.login-container {
-  max-width: 500px;
-  margin: 0 auto;
-  padding-top: 40px;
+/* Top Bar Styles */
+.login-top-bar {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  padding: 16px 20px;
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  z-index: 100;
 }
 }
 
 
-.title {
-  font-size: 32px;
-  font-weight: 500;
-  color: #323233;
-  margin-bottom: 40px;
+.lang-selector {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  padding: 8px 12px;
+  background: rgba(255, 255, 255, 0.8);
+  border-radius: 20px;
+  cursor: pointer;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+  
+  .lang-text {
+    font-size: 14px;
+    font-weight: 500;
+    color: #181511;
+  }
 }
 }
 
 
-.login-tabs {
-  margin-bottom: 20px;
+.employee-entry {
+  display: flex;
+  flex-direction: column; 
+  align-items: center;
+  gap: 2px;
+  cursor: pointer;
 
 
-  :deep(.van-tab) {
-    font-size: 15px;
+  .employee-text {
+    font-size: 10px;
+    font-weight: 500;
+    color: #181511;
   }
   }
+}
 
 
-  :deep(.van-tabs__content) {
-    padding-top: 20px;
-  }
+.login-card {
+    width: 100%;
+    height: 100vh;
+    background: #fff;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
 }
 }
 
 
-.register-hint {
-  text-align: center;
-  font-size: 14px;
-  color: #666;
-  margin-bottom: 20px;
-  
-  .link {
-    color: #1989fa;
-    cursor: pointer;
+    /* Header Pattern */
+    .card-header {
+        height: 240px;
+        position: relative;
+        background-color: rgba(242, 147, 13, 0.08); /* Primary tint */
+        display: flex;
+        align-items: center;
+        justify-content: center;
+    }
+    
+    .pattern-bg {
+        position: absolute;
+        inset: 0;
+        opacity: 0.2;
+        background-image: radial-gradient(#f2930d 1px, transparent 1px);
+        background-size: 20px 20px;
+    }
+
+    .header-icon-wrapper {
+        position: relative;
+        z-index: 50;
+    }
+    
+    .header-icon {
+        width: 80px;
+        height: 80px;
+        border-radius: 50%;
+        background: #fff;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+        box-shadow: 0 4px 12px rgba(0,0,0,0.08);
+    }
+    
+    /* Content */
+    .card-content {
+        padding: 32px 24px;
+        flex: 1;
+        display: flex;
+        flex-direction: column;
+    }
+    
+    .welcome-title {
+        font-size: 24px;
+        font-weight: 700;
+        color: #181511;
+        margin-bottom: 4px;
+        font-family: var(--font-heading);
+    }
+    
+    .welcome-subtitle {
+        font-size: 14px;
+        color: #8a7960;
+    }
+    
+    .form-label {
+        display: block;
+        font-size: 14px;
+        font-weight: 500;
+        color: #181511;
+        margin-bottom: 8px;
+        padding-left: 4px;
+    }
+    
+    /* Custom Input Pills */
+    .phone-input-pill {
+        display: flex;
+        align-items: center;
+        background: #f9fafb; /* gray-50 equivalent */
+        border-radius: 999px;
+        padding: 2px;
+        transition: all 0.3s;
+        border: 1px solid transparent;
+    
+        &:focus-within {
+            border-color: rgba(242, 147, 13, 0.5);
+            background: #fff;
+            box-shadow: 0 0 0 2px rgba(242, 147, 13, 0.2);
+        }
+    }
+    
+    .input-pill {
+        display: flex;
+        align-items: center;
+        background: #f9fafb;
+        border-radius: 999px;
+        border: 1px solid transparent;
+        transition: all 0.3s;
+        
+        &:focus-within {
+            border-color: rgba(242, 147, 13, 0.5);
+            background: #fff;
+            box-shadow: 0 0 0 2px rgba(242, 147, 13, 0.2);
+        }
+    }
+    
+    /* Vant Field Overrides for Pill */
+    .pill-field {
+        flex: 1;
+        background: transparent;
+        padding: 10px 16px;
+        /* Ensure radius matches container if needed, but transparent bg makes it okay */
+        --van-field-input-text-color: #181511;
+    }
+    
+    .flag-prefix {
+        display: flex;
+        align-items: center;
+        padding: 14px 16px;
+        gap: 6px;
+        border-right: 1px solid #e5e7eb;
+        height: 24px;
+        margin: 12px 0;
+    }
+    
+    .flag { font-size: 18px; }
+    .prefix { 
+        font-size: 15px; 
+        font-weight: 500; 
+        color: #181511; 
+    }
+    
+    /* Action Button */
+    .action-btn {
+        width: 100%;
+        margin-top: 24px;
+        height: 52px; /* Taller button */
+        font-weight: 700;
+        font-size: 16px;
+        box-shadow: 0 4px 12px rgba(242, 147, 13, 0.3);
+        
+        :deep(.van-button__text) {
+            display: flex;
+            align-items: center;
+            gap: 8px;
+        }
+    }
+
+.text-link {
+    font-size: 14px;
     font-weight: 500;
     font-weight: 500;
-  }
+    color: #8a7960;
+    display: inline-block;
+    cursor: pointer;
+    
+    &:hover {
+        color: var(--van-primary-color);
+    }
 }
 }
 
 
-.third-party-login {
-  margin-top: 30px;
-  margin-bottom: 20px;
+/* Divider & Social */
+.divider-section {
+    position: relative;
+    margin: 32px 0;
+    text-align: center;
+}
 
 
-  .van-divider {
-    font-size: 12px;
-    color: #969799;
-    margin: 20px 0;
-  }
+.divider-line {
+    position: absolute;
+    top: 50%;
+    left: 0;
+    width: 100%;
+    height: 1px;
+    background: #f1f1f1;
 }
 }
 
 
-.line-login-btn {
-  height: 44px;
-  font-size: 16px;
-  background-color: #06c755;
-  border-color: #06c755;
+.divider-text {
+    position: relative;
+    background: #fff;
+    padding: 0 16px;
+    color: #8a7960;
+    font-size: 14px;
+    display: inline-block;
+}
 
 
-  :deep(.van-icon) {
-    margin-right: 8px;
-  }
+.social-grid {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 16px;
+    margin-bottom: 24px;
 }
 }
 
 
-.env-tip {
-  margin-bottom: 30px;
-  text-align: center;
+.social-btn {
+    height: 56px;
+    width: 100%;
+    background: #fff;
+    border: 1px solid #e5e7eb;
+    padding: 0;
+    
+    :deep(.van-button__content) {
+        width: 100%;
+        justify-content: center;
+    }
+
+    &.line {
+        background: #06c755;
+        border-color: #06c755;
+    }
 }
 }
 
 
-.agreement {
-  font-size: 12px;
-  color: #969799;
-  text-align: center;
-  margin-top: 20px;
+.footer-text {
+    font-size: 14px;
+    color: #8a7960;
+}
 
 
-  .link {
-    color: #f9ae3d;
+.register-link {
+    color: var(--van-primary-color);
+    font-weight: 700;
     cursor: pointer;
     cursor: pointer;
-  }
+    
+    &:hover {
+        text-decoration: underline;
+    }
 }
 }
+
+/* Utilities */
+.text-center { text-align: center; }
+.mb-8 { margin-bottom: 32px; }
+.mt-4 { margin-top: 16px; }
+.pl-4 { padding-left: 16px; }
+.pt-4 { padding-top: 16px; }
+.pt-8 { padding-top: 32px; }
+.mr-1 { margin-right: 4px; }
 </style>
 </style>

+ 532 - 0
src/views/login/register.vue

@@ -0,0 +1,532 @@
+<template>
+  <div class="register-page">
+    <!-- Top Bar -->
+    <div class="register-top-bar">
+      <div class="lang-selector" @click="showLangAction = true">
+        <van-icon name="globe-o" size="18" class="mr-1" />
+        <span class="lang-text">{{ currentLangText }}</span>
+        <van-icon name="arrow-down" size="14" />
+      </div>
+    </div>
+
+    <div class="register-card">
+      <!-- Header Area with Pattern -->
+      <div class="card-header">
+        <div class="pattern-bg"></div>
+        <div class="header-icon-wrapper">
+          <div class="header-icon">
+            <!-- Replacing shop-o with user-plus or similar for registration -->
+            <van-icon name="user-plus" size="40" color="#f2930d" />
+          </div>
+        </div>
+      </div>
+
+      <!-- Content Area -->
+      <div class="card-content">
+        <div class="text-center mb-6">
+          <h2 class="welcome-title">{{ $t('register.title') || 'Detailed Customer Registration' }}</h2>
+          <p class="welcome-subtitle">{{ $t('register.subtitle') || 'Join LINE Order to get started' }}</p>
+        </div>
+
+        <div class="form-section">
+          
+          <!-- Nickname -->
+          <div class="form-group">
+            <label class="form-label">
+                {{ $t('register.nickname') || 'Nickname' }} <span class="required">*</span>
+            </label>
+            <div class="input-pill">
+              <van-field
+                v-model="form.nickname"
+                class="pill-field"
+                :placeholder="$t('register.enterNickname') || 'Enter nickname'"
+                :border="false"
+              />
+            </div>
+          </div>
+
+          <!-- Phonetic Name -->
+          <div class="form-group mt-4">
+            <label class="form-label">
+                {{ $t('register.phoneticName') || 'Phonetic Name' }} <span class="optional">(Optional)</span>
+            </label>
+            <div class="input-pill">
+              <van-field
+                v-model="form.phoneticName"
+                class="pill-field"
+                :placeholder="$t('register.enterPhonetic') || 'Katakana or Hiragana'"
+                :border="false"
+              />
+            </div>
+          </div>
+
+          <!-- Mobile or Email -->
+          <div class="form-group mt-4">
+            <label class="form-label">
+                {{ $t('register.mobileOrEmail') || 'Mobile Number or Email' }} <span class="required">*</span>
+            </label>
+            <div class="input-pill">
+              <van-field
+                v-model="form.contact"
+                class="pill-field"
+                :placeholder="$t('register.enterContact') || 'e.g. 09012345678 or email@example.com'"
+                :border="false"
+              />
+            </div>
+          </div>
+
+          <!-- Birthday & Referral Code (2 cols) -->
+          <div class="form-row mt-4">
+            <div class="form-group half">
+                 <label class="form-label">{{ $t('register.birthday') || 'Birthday' }}</label>
+                 <div class="input-pill clickable" @click="showDatePicker = true">
+                    <van-field
+                        v-model="form.birthday"
+                        class="pill-field"
+                        :placeholder="$t('register.selectBirthday') || 'mm/dd/yyyy'"
+                        :border="false"
+                        readonly
+                        right-icon="calendar-o"
+                    />
+                 </div>
+            </div>
+             <div class="form-group half">
+                 <label class="form-label">{{ $t('register.referral') || 'Referral Code' }}</label>
+                 <div class="input-pill">
+                    <van-field
+                        v-model="form.referralCode"
+                        class="pill-field"
+                        :placeholder="$t('register.optional') || 'Optional'"
+                        :border="false"
+                    />
+                 </div>
+            </div>
+          </div>
+
+          <!-- Favorite Food -->
+          <div class="form-group mt-4">
+            <label class="form-label">{{ $t('register.favoriteFood') || 'Favorite Food' }}</label>
+            <div class="tags-container">
+                <div 
+                    v-for="food in foodOptions" 
+                    :key="food" 
+                    class="food-tag"
+                    :class="{ active: form.favoriteFoods.includes(food) }"
+                    @click="toggleFood(food)"
+                >
+                    {{ food }}
+                </div>
+            </div>
+          </div>
+
+          <!-- Register Button -->
+          <van-button 
+            type="primary"
+            round
+            block
+            class="action-btn" 
+            :loading="loading"
+            @click="handleRegister"
+          >
+            {{ $t('register.submit') || 'Register' }}
+             <template #icon v-if="!loading">
+               <van-icon name="arrow" />
+            </template>
+          </van-button>
+
+          <!-- Social Register -->
+          <div class="divider-section">
+             <div class="divider-line"></div>
+             <div class="divider-text">{{ $t('register.orRegisterWith') || 'Or register with' }}</div>
+          </div>
+
+          <div class="social-grid">
+            <van-button round class="social-btn line" @click="handleSocialRegister('line')">
+                <svg class="w-6 h-6 fill-current" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M12 2.5C6.6 2.5 2.5 6.2 2.5 10.8c0 2.6 1.3 4.9 3.4 6.4-.2.7-.8 2.5-.9 2.9-.1.3 0 .6.3.6.1 0 .2 0 .3-.1 3.5-2 3.9-2.2 4.2-2.3.4.1.8.1 1.2.1 5.4 0 9.5-3.7 9.5-8.3S17.4 2.5 12 2.5z" fill="white"></path></svg>
+            </van-button>
+            <van-button round class="social-btn" @click="handleSocialRegister('google')">
+                <svg class="w-6 h-6" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4"></path><path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853"></path><path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05"></path><path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335"></path></svg>
+            </van-button>
+            <van-button round class="social-btn" @click="handleSocialRegister('apple')">
+                <svg class="w-6 h-6" viewBox="0 0 24 24" width="24" height="24" xmlns="http://www.w3.org/2000/svg"><path d="M17.05 20.28c-.98.95-2.05.88-3.08.4-1.09-.5-2.08-.48-3.24 0-1.44.62-2.2.44-3.06-.4C2.79 15.25 3.51 7.59 9.05 7.31c1.35.07 2.29.74 3.08.74 1.18 0 2.45-1.02 3.93-.84 1.16.14 2.29.58 3.09 1.54-.1.06-2.09 1.23-2.03 3.77.06 2.65 2.44 3.59 2.5 3.63-.03.11-.38 1.34-1.27 2.68-.82 1.23-1.68 2.45-3.03 2.45h-.26zM13.03 4.98c.67-.84 1.15-2.02.97-3.18-1.02.04-2.29.69-3.02 1.56-.63.74-1.19 1.95-.94 3.08 1.13.08 2.29-.61 2.99-1.46z"></path></svg>
+            </van-button>
+          </div>
+
+          <div class="mt-auto text-center pt-6 pb-4">
+            <p class="footer-text">
+                {{ $t('register.hasAccount') || 'Already have an account?' }}
+                <a class="login-link" @click="router.push('/login')">
+                    {{ $t('login.title') || 'Log In' }}
+                </a>
+            </p>
+          </div>
+
+        </div>
+      </div>
+    </div>
+    
+    <!-- Date Picker Popup -->
+    <van-popup v-model:show="showDatePicker" position="bottom" round>
+      <van-date-picker
+        v-model="currentDate"
+        title="Select Birthday"
+        :min-date="minDate"
+        :max-date="maxDate"
+        @confirm="onConfirmDate"
+        @cancel="showDatePicker = false"
+      />
+    </van-popup>
+
+    <!-- Language Action Sheet -->
+    <van-action-sheet
+      v-model:show="showLangAction"
+      :actions="langActions"
+      @select="onSelectLang"
+      :cancel-text="$t('common.cancel') || 'Cancel'"
+      close-on-click-action
+    />
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref, reactive, computed } from 'vue'
+import { useRouter } from 'vue-router'
+import { showToast } from 'vant'
+import { useI18n } from 'vue-i18n'
+
+const { t, locale } = useI18n()
+const router = useRouter()
+const loading = ref(false)
+const showDatePicker = ref(false)
+const showLangAction = ref(false)
+
+const currentDate = ref(['2000', '01', '01'])
+const minDate = new Date(1900, 0, 1)
+const maxDate = new Date()
+
+// Language Actions
+const langActions = [
+  { name: '日本語', value: 'ja' },
+  { name: 'English', value: 'en' },
+  { name: '简体中文', value: 'zh-Hans' },
+  { name: '繁體中文', value: 'zh-Hant' }
+]
+
+const currentLangText = computed(() => {
+  const current = langActions.find(item => item.value === locale.value)
+  return current ? current.name : 'Language'
+})
+
+const onSelectLang = (item: any) => {
+  locale.value = item.value
+  showLangAction.value = false
+  localStorage.setItem('locale', item.value)
+}
+
+const form = reactive({
+    nickname: '',
+    phoneticName: '',
+    contact: '',
+    birthday: '',
+    referralCode: '',
+    favoriteFoods: [] as string[]
+})
+
+const foodOptions = ['Sushi', 'Ramen', 'Gyoza', 'Tempura', 'Udon', 'Sashimi', 'Yakitori']
+
+const onConfirmDate = ({ selectedValues }: { selectedValues: string[] }) => {
+    form.birthday = selectedValues.join('/')
+    showDatePicker.value = false
+}
+
+const toggleFood = (food: string) => {
+    const index = form.favoriteFoods.indexOf(food)
+    if (index > -1) {
+        form.favoriteFoods.splice(index, 1)
+    } else {
+        form.favoriteFoods.push(food)
+    }
+}
+
+const handleRegister = async () => {
+    if (!form.nickname || !form.contact) {
+        showToast('Please fill in required fields')
+        return
+    }
+    
+    loading.value = true
+    setTimeout(() => {
+        loading.value = false
+        showToast('Registration Successful (Mock)')
+        router.push('/index')
+    }, 1500)
+}
+
+const handleSocialRegister = (provider: string) => {
+    showToast(`${provider} registration not implemented`)
+}
+</script>
+
+<style scoped lang="scss">
+.register-page {
+  min-height: 100vh;
+  background-color: #fff;
+  display: flex;
+  flex-direction: column;
+  font-family: var(--font-body);
+  position: relative;
+}
+
+/* Top Bar Styles */
+.register-top-bar {
+  position: absolute;
+  top: 0;
+  left: 0;
+  width: 100%;
+  padding: 16px 20px;
+  display: flex;
+  justify-content: flex-end;
+  align-items: center;
+  z-index: 100;
+}
+
+.lang-selector {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  padding: 8px 12px;
+  background: rgba(255, 255, 255, 0.8);
+  border-radius: 20px;
+  cursor: pointer;
+  box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+  
+  .lang-text {
+    font-size: 14px;
+    font-weight: 500;
+    color: #181511;
+  }
+}
+
+.register-card {
+    width: 100%;
+    height: 100vh;
+    background: #fff;
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+}
+
+/* Header Pattern */
+.card-header {
+    height: 200px;
+    position: relative;
+    background-color: rgba(242, 147, 13, 0.08); /* Primary tint */
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+
+.pattern-bg {
+    position: absolute;
+    inset: 0;
+    opacity: 0.2;
+    background-image: radial-gradient(#f2930d 1px, transparent 1px);
+    background-size: 20px 20px;
+}
+
+.header-icon-wrapper {
+    position: relative;
+    z-index: 50;
+}
+
+.header-icon {
+    width: 72px;
+    height: 72px;
+    border-radius: 50%;
+    background: #fff;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    box-shadow: 0 4px 12px rgba(0,0,0,0.08);
+}
+
+/* Content */
+.card-content {
+    padding: 24px 24px 40px;
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+}
+
+.welcome-title {
+    font-size: 22px;
+    font-weight: 700;
+    color: #181511;
+    margin-bottom: 4px;
+    font-family: var(--font-heading);
+}
+
+.welcome-subtitle {
+    font-size: 14px;
+    color: #8a7960;
+}
+
+.form-label {
+    display: block;
+    font-size: 14px;
+    font-weight: 600;
+    color: #181511;
+    margin-bottom: 6px;
+    padding-left: 2px;
+}
+
+.required { color: var(--van-danger-color); }
+.optional { color: #999; font-weight: 400; font-size: 12px; }
+
+/* Custom Input Pills */
+.input-pill {
+    display: flex;
+    align-items: center;
+    background: #f9fafb;
+    border-radius: 999px;
+    border: 1px solid transparent;
+    transition: all 0.3s;
+    
+    &:focus-within {
+        border-color: rgba(242, 147, 13, 0.5);
+        background: #fff;
+        box-shadow: 0 0 0 2px rgba(242, 147, 13, 0.2);
+    }
+    
+    &.clickable {
+        cursor: pointer;
+        &:active { background: #f0f0f0; }
+    }
+}
+
+.pill-field {
+    flex: 1;
+    background: transparent;
+    padding: 10px 16px;
+    --van-field-input-text-color: #181511;
+}
+
+.form-row {
+    display: flex;
+    gap: 12px;
+}
+.half { flex: 1; }
+
+.tags-container {
+    display: flex;
+    flex-wrap: wrap;
+    gap: 8px;
+}
+
+.food-tag {
+    padding: 6px 16px;
+    border-radius: 999px;
+    border: 1px solid #e5e7eb;
+    background: #fff;
+    color: #646566;
+    font-size: 13px;
+    cursor: pointer;
+    transition: all 0.2s;
+    
+    &.active {
+        border-color: var(--van-primary-color);
+        background: rgba(242, 147, 13, 0.1);
+        color: var(--van-primary-color);
+        font-weight: 600;
+    }
+}
+
+/* Action Button */
+.action-btn {
+    width: 100%;
+    margin-top: 24px;
+    height: 52px;
+    font-weight: 700;
+    font-size: 16px;
+    box-shadow: 0 4px 12px rgba(242, 147, 13, 0.3);
+    
+    :deep(.van-button__text) {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+    }
+}
+
+/* Divider & Social */
+.divider-section {
+    position: relative;
+    margin: 24px 0 16px;
+    text-align: center;
+}
+
+.divider-line {
+    position: absolute;
+    top: 50%;
+    left: 0;
+    width: 100%;
+    height: 1px;
+    background: #f1f1f1;
+}
+
+.divider-text {
+    position: relative;
+    background: #fff;
+    padding: 0 16px;
+    color: #8a7960;
+    font-size: 12px;
+    display: inline-block;
+}
+
+.social-grid {
+    display: grid;
+    grid-template-columns: repeat(3, 1fr);
+    gap: 16px;
+    margin-bottom: 8px;
+}
+
+.social-btn {
+    height: 48px;
+    width: 100%;
+    background: #fff;
+    border: 1px solid #e5e7eb;
+    padding: 0;
+    
+    :deep(.van-button__content) {
+        width: 100%;
+        justify-content: center;
+    }
+
+    &.line {
+        background: #06c755;
+        border-color: #06c755;
+    }
+}
+
+.footer-text {
+    font-size: 14px;
+    color: #8a7960;
+}
+
+.login-link {
+    color: var(--van-primary-color);
+    font-weight: 700;
+    cursor: pointer;
+    
+    &:hover {
+        text-decoration: underline;
+    }
+}
+
+/* Utilities */
+.text-center { text-align: center; }
+.mb-6 { margin-bottom: 24px; }
+.mt-4 { margin-top: 16px; }
+.pt-6 { padding-top: 24px; }
+.pb-4 { padding-bottom: 16px; }
+.mr-1 { margin-right: 4px; }
+</style>

+ 62 - 29
src/views/menu/menu.vue

@@ -157,17 +157,19 @@ const filterOptions = computed(() => [
   { text: t('menu.filter.popular'), value: 2 }
   { text: t('menu.filter.popular'), value: 2 }
 ])
 ])
 
 
-// 商品列表
-const goodsList = ref([])
-const loading = ref(false)
-const initialLoading = ref(true)
-const finished = ref(false)
-const refreshing = ref(false)
-const page = ref(1)
-const pageSize = ref(10)
-
 // 模拟商品数据
 // 模拟商品数据
-const mockGoods = [
+interface GoodsItem {
+  id: number
+  name: string
+  desc?: string
+  image: string
+  price: string
+  originalPrice?: string
+  tag?: string
+  sales?: number
+}
+
+const mockGoods: GoodsItem[] = [
   {
   {
     id: 1,
     id: 1,
     name: '招牌咖啡',
     name: '招牌咖啡',
@@ -214,8 +216,17 @@ const mockGoods = [
   }
   }
 ]
 ]
 
 
+// 商品列表
+const goodsList = ref<GoodsItem[]>([])
+const loading = ref(false)
+const initialLoading = ref(true)
+const finished = ref(false)
+const refreshing = ref(false)
+const page = ref(1)
+const pageSize = ref(10)
+
 // 排序商品
 // 排序商品
-const sortGoods = (goods) => {
+const sortGoods = (goods: GoodsItem[]) => {
   const sorted = [...goods]
   const sorted = [...goods]
 
 
   switch (sortType.value) {
   switch (sortType.value) {
@@ -236,7 +247,7 @@ const sortGoods = (goods) => {
 }
 }
 
 
 // 筛选商品
 // 筛选商品
-const filterGoods = (goods) => {
+const filterGoods = (goods: GoodsItem[]) => {
   let filtered = [...goods]
   let filtered = [...goods]
 
 
   switch (filterType.value) {
   switch (filterType.value) {
@@ -377,7 +388,7 @@ const goToCheckout = () => {
 onMounted(() => {
 onMounted(() => {
   // 从路由参数获取分类ID
   // 从路由参数获取分类ID
   if (route.query.categoryId) {
   if (route.query.categoryId) {
-    activeCategory.value = parseInt(route.query.categoryId)
+    activeCategory.value = parseInt(route.query.categoryId as string)
   }
   }
   loadGoodsList(true)
   loadGoodsList(true)
 })
 })
@@ -386,8 +397,9 @@ onMounted(() => {
 <style scoped>
 <style scoped>
 .menu-page {
 .menu-page {
   min-height: 100vh;
   min-height: 100vh;
-  background: #f5f5f5;
+  background: var(--van-background-color);
   padding-bottom: 110px;
   padding-bottom: 110px;
+  font-family: var(--font-body);
 }
 }
 
 
 .menu-content {
 .menu-content {
@@ -403,6 +415,7 @@ onMounted(() => {
 .category-sidebar {
 .category-sidebar {
   width: 90px;
   width: 90px;
   flex-shrink: 0;
   flex-shrink: 0;
+  background-color: #f7f8fa; /* Keep default sidebar bg or var(--van-background-color) */
 }
 }
 
 
 .category-item-custom {
 .category-item-custom {
@@ -418,6 +431,7 @@ onMounted(() => {
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
   justify-content: center;
   justify-content: center;
+  color: var(--van-text-color-2);
 }
 }
 
 
 .category-badge {
 .category-badge {
@@ -429,43 +443,57 @@ onMounted(() => {
 
 
 .category-item-custom span {
 .category-item-custom span {
   font-size: 12px;
   font-size: 12px;
+  color: var(--van-text-color-2);
+}
+
+/* Selected state for Sidebar Item (Vant override might be needed specifically) */
+:deep(.van-sidebar-item--select) {
+  border-color: var(--van-primary-color);
+}
+:deep(.van-sidebar-item--select) .category-icon-wrapper {
+  color: var(--van-primary-color);
+}
+:deep(.van-sidebar-item--select) span {
+  color: var(--van-text-color);
+  font-weight: 500;
 }
 }
 
 
 /* 商品容器 */
 /* 商品容器 */
 .goods-container {
 .goods-container {
   flex: 1;
   flex: 1;
   overflow-y: auto;
   overflow-y: auto;
-  background: #f5f5f5;
+  background: var(--van-background-2);
 }
 }
 
 
 /* 筛选栏 */
 /* 筛选栏 */
 .filter-bar {
 .filter-bar {
-  background: white;
+  background: var(--van-background-2);
   position: sticky;
   position: sticky;
   top: 0;
   top: 0;
   z-index: 10;
   z-index: 10;
+  border-bottom: 1px solid #ebedf0;
 }
 }
 
 
 /* 骨架屏 */
 /* 骨架屏 */
 .skeleton-wrapper {
 .skeleton-wrapper {
-  padding: 12px;
+  padding: var(--van-padding-md);
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
-  gap: 12px;
+  gap: var(--van-padding-md);
 }
 }
 
 
 .skeleton-item {
 .skeleton-item {
   width: 100%;
   width: 100%;
   height: 100px;
   height: 100px;
-  border-radius: 8px;
+  border-radius: var(--van-radius-md);
 }
 }
 
 
 .goods-list {
 .goods-list {
-  padding: 12px;
+  padding: var(--van-padding-sm);
   display: flex;
   display: flex;
   flex-direction: column;
   flex-direction: column;
-  gap: 12px;
-  background: white;
+  gap: var(--van-padding-md);
+  background: var(--van-background-2);
 }
 }
 
 
 /* 购物车栏 */
 /* 购物车栏 */
@@ -475,12 +503,12 @@ onMounted(() => {
   left: 0;
   left: 0;
   right: 0;
   right: 0;
   height: 56px;
   height: 56px;
-  background: linear-gradient(90deg, #323233 0%, #424242 100%);
+  background: #323233; /* Dark cart background as per standard design, can use var if defined */
   display: flex;
   display: flex;
   align-items: center;
   align-items: center;
-  padding: 0 16px;
+  padding: 0 var(--van-padding-md);
   z-index: 999;
   z-index: 999;
-  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.08);
+  box-shadow: 0 -2px 12px rgba(0, 0, 0, 0.1);
   cursor: pointer;
   cursor: pointer;
   transition: all 0.3s;
   transition: all 0.3s;
 }
 }
@@ -491,7 +519,7 @@ onMounted(() => {
 
 
 .cart-icon-wrapper {
 .cart-icon-wrapper {
   position: relative;
   position: relative;
-  margin-right: 12px;
+  margin-right: var(--van-padding-sm);
 }
 }
 
 
 .cart-icon-wrapper .van-icon {
 .cart-icon-wrapper .van-icon {
@@ -503,6 +531,7 @@ onMounted(() => {
   position: absolute;
   position: absolute;
   top: -4px;
   top: -4px;
   right: -8px;
   right: -8px;
+  background-color: var(--van-danger-color);
 }
 }
 
 
 .cart-info {
 .cart-info {
@@ -514,17 +543,21 @@ onMounted(() => {
   font-weight: 600;
   font-weight: 600;
   color: white;
   color: white;
   line-height: 1.3;
   line-height: 1.3;
+  font-family: var(--font-heading);
 }
 }
 
 
 .cart-text {
 .cart-text {
   font-size: 11px;
   font-size: 11px;
-  color: rgba(255, 255, 255, 0.6);
+  color: rgba(255, 255, 255, 0.7);
   margin-top: 2px;
   margin-top: 2px;
 }
 }
 
 
 .checkout-btn {
 .checkout-btn {
-  padding: 0 28px;
-  height: 40px;
+  padding: 0 24px;
+  height: 36px;
   font-weight: 500;
   font-weight: 500;
+  margin-left: var(--van-padding-sm);
+  background-color: var(--van-primary-color);
+  border-color: var(--van-primary-color);
 }
 }
 </style>
 </style>

+ 4 - 3
src/views/merchant/products/edit.vue

@@ -105,6 +105,7 @@ import { useRoute, useRouter } from 'vue-router'
 import { showToast, showDialog } from 'vant'
 import { showToast, showDialog } from 'vant'
 import { useMerchantStore } from '@/store/modules/merchant'
 import { useMerchantStore } from '@/store/modules/merchant'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
+import type { Product } from '@/types/merchant'
 
 
 const { t } = useI18n()
 const { t } = useI18n()
 const route = useRoute()
 const route = useRoute()
@@ -120,10 +121,10 @@ const categories = [
   { text: '饮料', value: '饮料' }
   { text: '饮料', value: '饮料' }
 ]
 ]
 
 
-const form = ref({
+const form = ref<Product>({
   id: '',
   id: '',
   name: '',
   name: '',
-  price: '',
+  price: 0,
   category: '',
   category: '',
   description: '',
   description: '',
   status: true,
   status: true,
@@ -136,7 +137,7 @@ const isEdit = computed(() => !!route.params.id)
 onMounted(async () => {
 onMounted(async () => {
   if (isEdit.value) {
   if (isEdit.value) {
     const products = await merchantStore.getProducts()
     const products = await merchantStore.getProducts()
-    const product = products.find((p: any) => p.id === route.params.id)
+    const product = products.find((p) => p.id === route.params.id)
     if (product) {
     if (product) {
       form.value = { ...product }
       form.value = { ...product }
     } else {
     } else {

+ 4 - 3
src/views/merchant/products/list.vue

@@ -50,12 +50,13 @@ import { useRouter } from 'vue-router'
 import { showToast, showDialog } from 'vant'
 import { showToast, showDialog } from 'vant'
 import { useMerchantStore } from '@/store/modules/merchant'
 import { useMerchantStore } from '@/store/modules/merchant'
 import { useI18n } from 'vue-i18n'
 import { useI18n } from 'vue-i18n'
+import type { Product } from '@/types/merchant'
 
 
 const { t } = useI18n()
 const { t } = useI18n()
 const router = useRouter()
 const router = useRouter()
 const merchantStore = useMerchantStore()
 const merchantStore = useMerchantStore()
 
 
-const list = ref<any[]>([])
+const list = ref<Product[]>([])
 const loading = ref(false)
 const loading = ref(false)
 const finished = ref(false)
 const finished = ref(false)
 const refreshing = ref(false)
 const refreshing = ref(false)
@@ -87,11 +88,11 @@ const goToCreate = () => {
   router.push('/merchant/products/create')
   router.push('/merchant/products/create')
 }
 }
 
 
-const goToEdit = (item: any) => {
+const goToEdit = (item: Product) => {
   router.push(`/merchant/products/${item.id}/edit`)
   router.push(`/merchant/products/${item.id}/edit`)
 }
 }
 
 
-const confirmDelete = (item: any) => {
+const confirmDelete = (item: Product) => {
   showDialog({
   showDialog({
     title: t('merchant.product.confirmDelete'),
     title: t('merchant.product.confirmDelete'),
     message: t('merchant.product.confirmDeleteMessage', { name: item.name }),
     message: t('merchant.product.confirmDeleteMessage', { name: item.name }),

+ 1 - 22
src/views/pos/order-detail.vue

@@ -95,32 +95,11 @@
 import { ref, onMounted } from 'vue'
 import { ref, onMounted } from 'vue'
 import { useRoute, useRouter } from 'vue-router'
 import { useRoute, useRouter } from 'vue-router'
 import { showToast } from 'vant'
 import { showToast } from 'vant'
+import type { Order } from '@/types/merchant'
 
 
 const route = useRoute()
 const route = useRoute()
 const router = useRouter()
 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>({
 const order = ref<Order>({
   id: route.params.id as string,
   id: route.params.id as string,
   orderNo: '2024011201',
   orderNo: '2024011201',