Przeglądaj źródła

feat: Add customer registration page and refactor login component styling to use new theme variables.

FanLide 1 tydzień temu
rodzic
commit
3167495a8c

+ 7 - 1
index.html

@@ -1,8 +1,14 @@
-<!DOCTYPE html>
+<!doctype html>
 <html lang="ja">
   <head>
     <meta charset="UTF-8" />
     <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
       name="viewport"
       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 {
   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']
     VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
     VanCouponList: typeof import('vant/es')['CouponList']
+    VanDatePicker: typeof import('vant/es')['DatePicker']
     VanDivider: typeof import('vant/es')['Divider']
     VanDropdownItem: typeof import('vant/es')['DropdownItem']
     VanDropdownMenu: typeof import('vant/es')['DropdownMenu']

+ 19 - 0
src/locale/en.json

@@ -248,6 +248,7 @@
     "enterCaptcha": "Enter OTP",
     "getCaptcha": "Get OTP",
     "loginNow": "Login Now",
+    "employee": "Staff",
     "invalidPhone": "Invalid phone format",
     "checkAgreement": "Please check the agreement",
     "autoCreateAccount": "New phone numbers will be automatically registered",
@@ -264,6 +265,24 @@
     "privacyPolicy": "Privacy Policy",
     "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": {
     "tabbar": {
       "home": "Home",

+ 19 - 0
src/locale/ja.json

@@ -248,6 +248,7 @@
     "enterCaptcha": "認証コードを入力してください",
     "getCaptcha": "認証コードを取得",
     "loginNow": "今すぐログイン",
+    "employee": "スタッフ",
     "invalidPhone": "電話番号が正しくありません",
     "checkAgreement": "規約に同意してください",
     "autoCreateAccount": "未登録の電話番号は認証後に自動でアカウントが作成されます",
@@ -264,6 +265,24 @@
     "privacyPolicy": "プライバシーポリシー",
     "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": {
     "tabbar": {
       "home": "ホーム",

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

@@ -248,6 +248,7 @@
     "enterCaptcha": "请输入验证码",
     "getCaptcha": "获取验证码",
     "loginNow": "立即登录",
+    "employee": "员工",
     "invalidPhone": "手机号码格式不对",
     "checkAgreement": "请勾选下面协议",
     "autoCreateAccount": "未注册的手机号验证后自动创建账号",
@@ -264,6 +265,24 @@
     "privacyPolicy": "隐私政策",
     "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": {
     "tabbar": {
       "home": "首页",

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

@@ -248,6 +248,7 @@
     "enterCaptcha": "請輸入驗證碼",
     "getCaptcha": "獲取驗證碼",
     "loginNow": "立即登錄",
+    "employee": "員工",
     "invalidPhone": "手機號碼格式不對",
     "checkAgreement": "請勾選下面協議",
     "autoCreateAccount": "未註冊的手機號驗證後自動創建賬號",
@@ -264,6 +265,24 @@
     "privacyPolicy": "隱私政策",
     "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": {
     "tabbar": {
       "home": "首頁",

+ 1 - 0
src/main.ts

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

+ 8 - 0
src/router/routes.ts

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

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

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

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

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

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

@@ -247,18 +247,18 @@ onUnmounted(() => {
   .phone-input-group {
     display: flex;
     align-items: flex-end;
-    gap: 12px;
-    margin-bottom: 20px;
+    gap: var(--van-padding-sm);
+    margin-bottom: var(--van-padding-lg);
   }
 
   .area-code-field {
     width: 100px;
     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;
-    transition: background 0.3s;
+    transition: background 0.2s;
 
     &:active {
       background: #ebedf0;
@@ -266,7 +266,7 @@ onUnmounted(() => {
 
     .area-code-label {
       font-size: 12px;
-      color: #646566;
+      color: var(--van-text-color-2);
       margin-bottom: 4px;
     }
 
@@ -275,7 +275,7 @@ onUnmounted(() => {
       align-items: center;
       justify-content: space-between;
       font-size: 14px;
-      color: #323233;
+      color: var(--van-text-color);
       font-weight: 500;
     }
   }
@@ -284,7 +284,13 @@ onUnmounted(() => {
     flex: 1;
 
     :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;
     gap: 6px;
     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 {
     display: flex;
     align-items: center;
-    gap: 10px;
-    margin-bottom: 30px;
+    gap: var(--van-padding-sm);
+    margin-bottom: var(--van-padding-xl);
 
     :deep(.van-field) {
       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 {
       flex-shrink: 0;
+      height: 44px;
+      border-radius: var(--van-radius-md);
     }
   }
 
   .login-btn {
-    height: 44px;
+    height: 48px;
     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>

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

@@ -1,248 +1,626 @@
 <template>
   <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>
-
-      <!-- 登录方式切换 -->
-      <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 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 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>
+    
+    <!-- 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, onMounted } from 'vue'
+import { ref, onMounted, computed } from 'vue'
 import { useRouter, useRoute } from 'vue-router'
 import { useI18n } from 'vue-i18n'
 import { showToast } from 'vant'
 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 { 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 route = useRoute()
 const userStore = useUserStore()
 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 () => {
   await initLiff()
-
-  // 如果已经登录,直接跳转
   if (userStore.isLogin) {
     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) {
-    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>
 
 <style scoped lang="scss">
 .login-page {
   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;
-  }
+    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;
-  }
+    
+    &: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>

+ 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 }
 ])
 
-// 商品列表
-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,
     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]
 
   switch (sortType.value) {
@@ -236,7 +247,7 @@ const sortGoods = (goods) => {
 }
 
 // 筛选商品
-const filterGoods = (goods) => {
+const filterGoods = (goods: GoodsItem[]) => {
   let filtered = [...goods]
 
   switch (filterType.value) {
@@ -377,7 +388,7 @@ const goToCheckout = () => {
 onMounted(() => {
   // 从路由参数获取分类ID
   if (route.query.categoryId) {
-    activeCategory.value = parseInt(route.query.categoryId)
+    activeCategory.value = parseInt(route.query.categoryId as string)
   }
   loadGoodsList(true)
 })
@@ -386,8 +397,9 @@ onMounted(() => {
 <style scoped>
 .menu-page {
   min-height: 100vh;
-  background: #f5f5f5;
+  background: var(--van-background-color);
   padding-bottom: 110px;
+  font-family: var(--font-body);
 }
 
 .menu-content {
@@ -403,6 +415,7 @@ onMounted(() => {
 .category-sidebar {
   width: 90px;
   flex-shrink: 0;
+  background-color: #f7f8fa; /* Keep default sidebar bg or var(--van-background-color) */
 }
 
 .category-item-custom {
@@ -418,6 +431,7 @@ onMounted(() => {
   display: flex;
   align-items: center;
   justify-content: center;
+  color: var(--van-text-color-2);
 }
 
 .category-badge {
@@ -429,43 +443,57 @@ onMounted(() => {
 
 .category-item-custom span {
   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 {
   flex: 1;
   overflow-y: auto;
-  background: #f5f5f5;
+  background: var(--van-background-2);
 }
 
 /* 筛选栏 */
 .filter-bar {
-  background: white;
+  background: var(--van-background-2);
   position: sticky;
   top: 0;
   z-index: 10;
+  border-bottom: 1px solid #ebedf0;
 }
 
 /* 骨架屏 */
 .skeleton-wrapper {
-  padding: 12px;
+  padding: var(--van-padding-md);
   display: flex;
   flex-direction: column;
-  gap: 12px;
+  gap: var(--van-padding-md);
 }
 
 .skeleton-item {
   width: 100%;
   height: 100px;
-  border-radius: 8px;
+  border-radius: var(--van-radius-md);
 }
 
 .goods-list {
-  padding: 12px;
+  padding: var(--van-padding-sm);
   display: flex;
   flex-direction: column;
-  gap: 12px;
-  background: white;
+  gap: var(--van-padding-md);
+  background: var(--van-background-2);
 }
 
 /* 购物车栏 */
@@ -475,12 +503,12 @@ onMounted(() => {
   left: 0;
   right: 0;
   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;
   align-items: center;
-  padding: 0 16px;
+  padding: 0 var(--van-padding-md);
   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;
   transition: all 0.3s;
 }
@@ -491,7 +519,7 @@ onMounted(() => {
 
 .cart-icon-wrapper {
   position: relative;
-  margin-right: 12px;
+  margin-right: var(--van-padding-sm);
 }
 
 .cart-icon-wrapper .van-icon {
@@ -503,6 +531,7 @@ onMounted(() => {
   position: absolute;
   top: -4px;
   right: -8px;
+  background-color: var(--van-danger-color);
 }
 
 .cart-info {
@@ -514,17 +543,21 @@ onMounted(() => {
   font-weight: 600;
   color: white;
   line-height: 1.3;
+  font-family: var(--font-heading);
 }
 
 .cart-text {
   font-size: 11px;
-  color: rgba(255, 255, 255, 0.6);
+  color: rgba(255, 255, 255, 0.7);
   margin-top: 2px;
 }
 
 .checkout-btn {
-  padding: 0 28px;
-  height: 40px;
+  padding: 0 24px;
+  height: 36px;
   font-weight: 500;
+  margin-left: var(--van-padding-sm);
+  background-color: var(--van-primary-color);
+  border-color: var(--van-primary-color);
 }
 </style>