# LINE订餐系统重构方案 ## 📋 项目概述 将现有的 uniapp 订餐系统重构为基于 LINE LIFF 的 Web 应用 - **原项目**: `/orderApp/online-order-uniapp` (Vue 3 + uniapp) - **新项目**: `/orderApp/line-order-app` (Vue 3 + LIFF + Vite) - **技术栈**: Vue 3 + Vite + Pinia + Vue Router + Axios + Socket.IO + Vant 4 + LIFF SDK --- ## 🎯 核心技术选型 | 模块 | 技术方案 | 说明 | |------|---------|------| | 构建工具 | Vite 5.x | 快速、现代化 | | HTTP请求 | Axios | 成熟稳定,拦截器逻辑可复用 | | WebSocket | Socket.IO Client | 自动重连、更好的错误处理 | | UI组件库 | Vant 4 | 移动端优化,完善的组件 | | 状态管理 | Pinia 2.x | 从原项目100%迁移 | | 国际化 | vue-i18n 9.x | 从原项目100%迁移 | | 路由 | Vue Router 4 | SPA标准路由 | | LINE集成 | @line/liff | LINE官方SDK | | 工具库 | @vueuse/core、dayjs | 现代化工具集 | --- ## 📁 项目结构设计 ``` line-order-app/ ├── public/ # 静态资源 │ ├── favicon.ico │ └── liff-starter.html # LIFF启动页 │ ├── src/ │ ├── api/ # API接口层 [从uniapp迁移80%] │ │ ├── request.js # Axios封装 │ │ ├── address.js │ │ ├── auth.js │ │ ├── coupon.js │ │ ├── goods.js │ │ ├── market.js │ │ ├── merchant.js │ │ ├── order.js │ │ ├── score.js │ │ ├── user.js │ │ └── index.js │ │ │ ├── assets/ # 资源文件 │ │ ├── images/ │ │ └── styles/ │ │ ├── reset.css │ │ ├── variables.css │ │ └── common.css │ │ │ ├── components/ # 公共组件 [重写] │ │ ├── common/ │ │ │ ├── YImage.vue # 图片组件 │ │ │ ├── YNavBar.vue # 导航栏 │ │ │ └── YTabBar.vue # 底部Tab │ │ └── business/ # 业务组件 │ │ │ ├── composables/ # 组合式函数 [从uniapp改造] │ │ ├── useAuth.js # 认证逻辑 │ │ ├── useLiff.js # LIFF相关 │ │ ├── useSocket.js # Socket.IO封装 │ │ └── useCart.js # 购物车逻辑 │ │ │ ├── config/ # 配置文件 [从uniapp迁移] │ │ ├── index.js # 全局配置 │ │ └── constants.js # 常量定义 │ │ │ ├── locale/ # 国际化 [从uniapp复用100%] │ │ ├── index.js │ │ ├── zh-Hans.json # 简体中文 │ │ ├── zh-Hant.json # 繁体中文 │ │ ├── ja.json # 日语 │ │ └── en.json # 英语 │ │ │ ├── router/ # 路由配置 [新建] │ │ ├── index.js │ │ ├── routes.js │ │ └── guards.js # 路由守卫 │ │ │ ├── store/ # Pinia状态管理 [从uniapp迁移90%] │ │ ├── index.js │ │ ├── modules/ │ │ │ ├── app.js # 应用状态 │ │ │ ├── user.js # 用户状态 │ │ │ ├── cart.js # 购物车 │ │ │ └── order.js # 订单状态 │ │ └── plugins/ │ │ └── persist.js # 持久化配置 │ │ │ ├── utils/ # 工具函数 [从uniapp迁移90%] │ │ ├── index.js │ │ ├── auth.js # 认证工具 │ │ ├── storage.js # 存储工具 │ │ ├── format.js # 格式化工具 │ │ ├── validate.js # 验证工具 │ │ ├── image.js # 图片处理 │ │ ├── price.js # 价格计算 │ │ └── logger.js # 日志工具 │ │ │ ├── views/ # 页面视图 [重写] │ │ ├── index/ # 首页 │ │ │ └── index.vue │ │ ├── menu/ # 菜单 │ │ │ ├── menu.vue │ │ │ └── detail.vue │ │ ├── order/ # 订单 │ │ │ ├── order.vue │ │ │ └── detail.vue │ │ ├── mine/ # 我的 │ │ │ ├── mine.vue │ │ │ └── userinfo.vue │ │ ├── cart/ # 购物车 │ │ │ └── cart.vue │ │ ├── login/ # 登录 │ │ │ └── login.vue │ │ └── payment/ # 支付 │ │ └── pay.vue │ │ │ ├── App.vue # 根组件 │ └── main.js # 入口文件 │ ├── .env.development # 开发环境变量 ├── .env.production # 生产环境变量 ├── .gitignore ├── index.html ├── package.json ├── vite.config.js # Vite配置 └── REFACTOR_PLAN.md # 本文档 ``` --- ## 🚀 重构步骤 ### 阶段 0: 环境准备 ✅ **目标**: 初始化项目基础结构 #### Step 0.1: 创建 Vite 项目 ```bash cd /Users/lidefan/Develop/orderApp/line-order-app npm create vite@latest . -- --template vue ``` #### Step 0.2: 安装核心依赖 ```bash # 核心框架 npm install vue@latest vue-router@latest pinia@latest # HTTP & WebSocket npm install axios socket.io-client # UI组件库 npm install vant @vant/area-data npm install @vant/auto-import-resolver unplugin-vue-components -D # LINE SDK npm install @line/liff # 国际化 npm install vue-i18n@latest # 工具库 npm install pinia-plugin-persistedstate npm install dayjs npm install @vueuse/core ``` #### Step 0.3: 创建目录结构 ```bash mkdir -p src/{api,assets/{images,styles},components/{common,business},composables,config,locale,router,store/{modules,plugins},utils,views/{index,menu,order,mine,cart,login,payment}} ``` #### Step 0.4: 配置文件 - 创建 `vite.config.js` - 创建 `.env.development` 和 `.env.production` - 配置 Vant 自动导入 **完成标志**: ✅ 项目可以运行 `npm run dev` --- ### 阶段 1: 核心配置迁移 (第1天) **目标**: 完成基础架构搭建 #### Step 1.1: 环境配置 - [ ] 创建 `src/config/index.js` - [ ] 从 uniapp 迁移配置常量 - [ ] 配置 API_URL、WS_URL、LIFF_ID 等 #### Step 1.2: 工具函数迁移 - [ ] 迁移 `utils/format.js` - 时间、价格格式化 - [ ] 迁移 `utils/validate.js` - 表单验证 - [ ] 迁移 `utils/image.js` - 图片处理 - [ ] 创建 `utils/storage.js` - localStorage封装 - [ ] 创建 `utils/logger.js` - 日志工具 **代码示例**: ```javascript // utils/storage.js export const storage = { get(key, defaultValue = null) { const value = localStorage.getItem(key) try { return value ? JSON.parse(value) : defaultValue } catch { return value || defaultValue } }, set(key, value) { localStorage.setItem(key, JSON.stringify(value)) }, remove(key) { localStorage.removeItem(key) } } ``` #### Step 1.3: 国际化配置 - [ ] 复制 `locale/*.json` 文件(100%复用) - [ ] 改造 `locale/index.js` - [ ] 替换 uni API 为标准 API #### Step 1.4: LIFF SDK 初始化 - [ ] 创建 `composables/useLiff.js` - [ ] 实现 LIFF 初始化逻辑 - [ ] 实现用户登录检测 **完成标志**: ✅ 工具函数可用,国际化正常切换,LIFF可初始化 --- ### 阶段 2: 状态管理迁移 (第2天) **目标**: 完成 Pinia Store 迁移 #### Step 2.1: Pinia 配置 - [ ] 创建 `store/index.js` - [ ] 配置持久化插件(localStorage) #### Step 2.2: Store 模块迁移 - [ ] 迁移 `store/modules/app.js` - 应用全局状态 - [ ] 迁移 `store/modules/user.js` - 用户信息 - [ ] 创建 `store/modules/cart.js` - 购物车 - [ ] 创建 `store/modules/order.js` - 订单状态 **代码示例**: ```javascript // store/modules/user.js (从 uniapp store/store.js 改造) import { defineStore } from 'pinia' import { storage } from '@/utils/storage' export const useUserStore = defineStore('user', { state: () => ({ member: {}, token: '', openid: '', isMer: 0, merchartShop: {} }), getters: { isLogin: (state) => Object.keys(state.member).length > 0 }, actions: { setMember(member) { this.member = member }, setToken(token) { this.token = token storage.set('accessToken', token) }, logout() { this.member = {} this.token = '' storage.remove('accessToken') } }, persist: { enabled: true, strategies: [ { storage: localStorage, paths: ['member', 'token'] } ] } }) ``` **完成标志**: ✅ Store 可以正常存取数据,持久化生效 --- ### 阶段 3: API 层重构 (第3天) **目标**: 完成 HTTP 请求层迁移 #### Step 3.1: Axios 封装 - [ ] 创建 `api/request.js` - [ ] 配置请求/响应拦截器 - [ ] 处理 token 注入 - [ ] 处理错误统一处理 **代码示例**: ```javascript // api/request.js import axios from 'axios' import { storage } from '@/utils/storage' import { showToast } from 'vant' import router from '@/router' const request = axios.create({ baseURL: import.meta.env.VITE_API_URL, timeout: 10000, headers: { 'Content-Type': 'application/json', 'tenant-id': import.meta.env.VITE_TENANT_ID } }) // 请求拦截器 request.interceptors.request.use( config => { const token = storage.get('accessToken') if (token) { config.headers.Authorization = `Bearer ${token}` } return config }, error => Promise.reject(error) ) // 响应拦截器 request.interceptors.response.use( response => { const { data } = response if (data.code === 401) { showToast('未登录') storage.remove('accessToken') router.push('/login') return Promise.reject(new Error('未登录')) } if (data.code !== 0) { showToast(data.msg || '请求失败') return Promise.reject(new Error(data.msg)) } return data.data }, error => { showToast('网络错误') return Promise.reject(error) } ) export default request ``` #### Step 3.2: API 模块迁移 - [ ] 迁移 `api/auth.js` - 认证接口 - [ ] 迁移 `api/user.js` - 用户接口 - [ ] 迁移 `api/goods.js` - 商品接口 - [ ] 迁移 `api/order.js` - 订单接口 - [ ] 迁移 `api/address.js` - 地址接口 - [ ] 迁移 `api/coupon.js` - 优惠券接口 - [ ] 迁移 `api/merchant.js` - 商家接口 - [ ] 迁移其他 API 模块 **完成标志**: ✅ API 可以正常调用,错误处理正确 --- ### 阶段 4: 路由系统搭建 (第4天) **目标**: 完成路由配置 #### Step 4.1: 路由配置 - [ ] 创建 `router/routes.js` - [ ] 从 `pages.json` 迁移路由配置 - [ ] 配置路由元信息 **代码示例**: ```javascript // router/routes.js export default [ { path: '/', redirect: '/index' }, { path: '/index', name: 'Index', component: () => import('@/views/index/index.vue'), meta: { title: 'index.home', keepAlive: true } }, { path: '/menu', name: 'Menu', component: () => import('@/views/menu/menu.vue'), meta: { title: 'menu.title', keepAlive: true } }, { path: '/order', name: 'Order', component: () => import('@/views/order/order.vue'), meta: { title: 'order.title', requiresAuth: true } }, { path: '/mine', name: 'Mine', component: () => import('@/views/mine/mine.vue'), meta: { title: 'mine.title' } }, { path: '/login', name: 'Login', component: () => import('@/views/login/login.vue'), meta: { title: 'login.title' } } ] ``` #### Step 4.2: 路由守卫 - [ ] 创建 `router/guards.js` - [ ] 实现登录验证 - [ ] 实现标题设置 #### Step 4.3: 路由入口 - [ ] 创建 `router/index.js` - [ ] 集成路由守卫 **完成标志**: ✅ 路由可以正常跳转,守卫生效 --- ### 阶段 5: WebSocket 重构 (第5天) **目标**: 完成 Socket.IO 集成 #### Step 5.1: Socket.IO 封装 - [ ] 创建 `composables/useSocket.js` - [ ] 从 `hooks/useWebSocket.js` 改造 - [ ] 实现自动重连 - [ ] 实现心跳检测 **代码示例**: ```javascript // composables/useSocket.js import { ref, onBeforeUnmount } from 'vue' import { io } from 'socket.io-client' import { storage } from '@/utils/storage' export function useSocket(options = {}) { const socket = ref(null) const connected = ref(false) const connect = () => { const token = storage.get('accessToken') socket.value = io(import.meta.env.VITE_WS_URL, { auth: { token }, transports: ['websocket'], reconnection: true, reconnectionDelay: 3000, reconnectionAttempts: 5, ...options }) socket.value.on('connect', () => { console.log('Socket connected') connected.value = true options.onConnected?.() }) socket.value.on('disconnect', () => { console.log('Socket disconnected') connected.value = false options.onDisconnected?.() }) socket.value.on('message', (data) => { options.onMessage?.(data) }) } const disconnect = () => { if (socket.value) { socket.value.disconnect() socket.value = null } } const emit = (event, data) => { if (socket.value?.connected) { socket.value.emit(event, data) } } onBeforeUnmount(() => { disconnect() }) return { socket, connected, connect, disconnect, emit } } ``` **完成标志**: ✅ WebSocket 可以正常连接和通信 --- ### 阶段 6: UI 组件开发 (第6-10天) **目标**: 使用 Vant 重构所有页面 #### Step 6.1: 公共组件 - [ ] 创建 `components/common/YImage.vue` - 图片组件 - [ ] 创建 `components/common/YNavBar.vue` - 导航栏 - [ ] 创建 `components/common/YTabBar.vue` - 底部Tab #### Step 6.2: 主要页面 - [ ] 开发 `views/index/index.vue` - 首页 - [ ] 开发 `views/menu/menu.vue` - 菜单列表 - [ ] 开发 `views/menu/detail.vue` - 菜单详情 - [ ] 开发 `views/cart/cart.vue` - 购物车 - [ ] 开发 `views/order/order.vue` - 订单列表 - [ ] 开发 `views/order/detail.vue` - 订单详情 - [ ] 开发 `views/mine/mine.vue` - 我的 - [ ] 开发 `views/login/login.vue` - 登录页 #### Step 6.3: Vant 组件映射 | uniapp组件 | Vant组件 | 说明 | |-----------|----------|------| | uv-button | van-button | 按钮 | | uv-popup | van-popup | 弹出层 | | uv-picker | van-picker | 选择器 | | uv-tabs | van-tabs | 标签页 | | uv-list | van-list | 列表 | | uv-cell | van-cell | 单元格 | | uv-image | van-image | 图片 | | uv-icon | van-icon | 图标 | | uv-navbar | van-nav-bar | 导航栏 | | uv-tabbar | van-tabbar | 标签栏 | **完成标志**: ✅ 所有页面开发完成,UI符合设计 --- ### 阶段 7: LIFF 功能集成 (第11-12天) **目标**: 集成 LINE 特有功能 #### Step 7.1: LIFF 初始化 - [ ] 在 `main.js` 初始化 LIFF - [ ] 实现登录检测 - [ ] 获取 LINE 用户信息 #### Step 7.2: LINE 功能 - [ ] 实现 LINE 分享功能 - [ ] 实现 LINE Pay 支付(可选) - [ ] 实现扫码功能(使用 LIFF scanCode) - [ ] 实现发送消息到 LINE **代码示例**: ```javascript // composables/useLiff.js import { ref } from 'vue' import liff from '@line/liff' export function useLiff() { const isReady = ref(false) const isLoggedIn = ref(false) const profile = ref(null) const init = async () => { try { await liff.init({ liffId: import.meta.env.VITE_LIFF_ID }) isReady.value = true if (liff.isLoggedIn()) { isLoggedIn.value = true profile.value = await liff.getProfile() } } catch (error) { console.error('LIFF init error:', error) } } const login = () => { if (!liff.isLoggedIn()) { liff.login() } } const logout = () => { liff.logout() isLoggedIn.value = false profile.value = null } const shareTargetPicker = (messages) => { if (liff.isApiAvailable('shareTargetPicker')) { return liff.shareTargetPicker(messages) } } return { isReady, isLoggedIn, profile, init, login, logout, shareTargetPicker } } ``` **完成标志**: ✅ LIFF 功能正常工作 --- ### 阶段 8: 测试与优化 (第13-14天) **目标**: 测试和性能优化 #### Step 8.1: 功能测试 - [ ] 登录流程测试 - [ ] 下单流程测试 - [ ] 支付流程测试 - [ ] WebSocket 通信测试 - [ ] 多语言切换测试 #### Step 8.2: 兼容性测试 - [ ] LINE 内置浏览器测试 - [ ] iOS LINE 测试 - [ ] Android LINE 测试 #### Step 8.3: 性能优化 - [ ] 路由懒加载优化 - [ ] 图片懒加载 - [ ] 组件按需加载 - [ ] 打包体积优化 #### Step 8.4: 错误处理 - [ ] 全局错误捕获 - [ ] 日志上报 - [ ] 用户友好的错误提示 **完成标志**: ✅ 所有功能测试通过,性能达标 --- ## 📊 迁移检查清单 ### 配置文件 - [ ] 环境变量配置 - [ ] Vite 配置完成 - [ ] Vant 自动导入配置 ### 工具函数 - [ ] ✅ 时间格式化 (formatDateTime, formatPast) - [ ] ✅ 价格计算 (utils/price.js) - [ ] ✅ 表单验证 (validatePhoneNumber, isValidBankCard) - [ ] ✅ 图片处理 (utils/image.js) - [ ] ✅ Storage 封装 ### 国际化 - [ ] ✅ zh-Hans.json - [ ] ✅ zh-Hant.json - [ ] ✅ ja.json - [ ] ✅ en.json - [ ] ✅ i18n 配置 ### API 接口 - [ ] ✅ Axios 封装 - [ ] ✅ auth.js - 认证 - [ ] ✅ user.js - 用户 - [ ] ✅ goods.js - 商品 - [ ] ✅ order.js - 订单 - [ ] ✅ address.js - 地址 - [ ] ✅ coupon.js - 优惠券 - [ ] ✅ merchant.js - 商家 - [ ] ✅ market.js - 营销 - [ ] ✅ score.js - 积分 ### 状态管理 - [ ] ✅ Pinia 配置 - [ ] ✅ app store - 应用状态 - [ ] ✅ user store - 用户状态 - [ ] ✅ cart store - 购物车 - [ ] ✅ order store - 订单 ### 路由 - [ ] ✅ 路由配置 - [ ] ✅ 路由守卫 - [ ] ✅ 页面权限控制 ### 页面开发 - [ ] 首页 - [ ] 菜单列表 - [ ] 菜单详情 - [ ] 购物车 - [ ] 订单列表 - [ ] 订单详情 - [ ] 我的页面 - [ ] 登录页面 - [ ] 支付页面 - [ ] 地址管理 - [ ] 优惠券列表 - [ ] 商家中心 ### LIFF 集成 - [ ] LIFF SDK 初始化 - [ ] LINE 登录 - [ ] 用户信息获取 - [ ] 分享功能 - [ ] LINE Pay 支付(可选) ### WebSocket - [ ] Socket.IO 连接 - [ ] 心跳检测 - [ ] 自动重连 - [ ] 消息推送 --- ## 🔧 开发命令 ```bash # 安装依赖 npm install # 开发模式 npm run dev # 构建生产 npm run build # 预览构建结果 npm run preview # 代码检查 npm run lint # 代码格式化 npm run format ``` --- ## 📝 环境变量说明 ### .env.development ```env # API地址 VITE_API_URL=https://api-dev.example.com # WebSocket地址 VITE_WS_URL=wss://ws-dev.example.com # LIFF ID VITE_LIFF_ID=your-liff-id-dev # 租户ID VITE_TENANT_ID=1 ``` ### .env.production ```env # API地址 VITE_API_URL=https://api.example.com # WebSocket地址 VITE_WS_URL=wss://ws.example.com # LIFF ID VITE_LIFF_ID=your-liff-id-prod # 租户ID VITE_TENANT_ID=1 ``` --- ## 🎯 关键注意事项 ### 1. uni API 替换对照表 | uniapp API | Web 标准 API | 说明 | |-----------|-------------|------| | uni.getStorageSync() | localStorage.getItem() | 本地存储 | | uni.setStorageSync() | localStorage.setItem() | 本地存储 | | uni.showToast() | showToast() (Vant) | 提示 | | uni.showLoading() | showLoadingToast() (Vant) | 加载 | | uni.navigateTo() | router.push() | 路由跳转 | | uni.redirectTo() | router.replace() | 路由替换 | | uni.switchTab() | router.replace() | Tab切换 | | uni.request() | axios() | 网络请求 | | uni.connectSocket() | io() (Socket.IO) | WebSocket | ### 2. 样式适配 - 使用 `vh`、`vw` 替代 `rpx` - 使用 postcss-px-to-viewport 做移动端适配 - 注意 LINE 浏览器的安全区域 ### 3. LIFF 限制 - 某些 API 仅在 LINE 内可用 - 需要处理非 LINE 环境的降级方案 - 注意 LIFF 版本兼容性 ### 4. 性能优化 - 路由懒加载 - 图片懒加载 - 组件按需引入 - 打包分析和优化 --- ## 📅 时间规划 | 阶段 | 时间 | 任务 | |------|------|------| | 阶段0 | 0.5天 | 环境准备 | | 阶段1 | 1天 | 核心配置迁移 | | 阶段2 | 1天 | 状态管理迁移 | | 阶段3 | 1天 | API层重构 | | 阶段4 | 1天 | 路由系统搭建 | | 阶段5 | 1天 | WebSocket重构 | | 阶段6 | 5天 | UI组件开发 | | 阶段7 | 2天 | LIFF功能集成 | | 阶段8 | 2天 | 测试与优化 | | **总计** | **14.5天** | **约3周** | --- ## 🎓 学习资源 - [Vite 官方文档](https://cn.vitejs.dev/) - [Vue 3 文档](https://cn.vuejs.org/) - [Vant 4 文档](https://vant-ui.github.io/vant/) - [LINE LIFF 文档](https://developers.line.biz/en/docs/liff/) - [Socket.IO 文档](https://socket.io/docs/v4/) - [Pinia 文档](https://pinia.vuejs.org/zh/) --- ## 📞 支持 如遇问题,请查阅本文档或相关技术文档。 **开始重构**: 准备好后,请从 `阶段 0` 开始执行!