REFACTOR_PLAN.md 21 KB

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 项目

cd /Users/lidefan/Develop/orderApp/line-order-app
npm create vite@latest . -- --template vue

Step 0.2: 安装核心依赖

# 核心框架
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: 创建目录结构

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 - 日志工具

代码示例:

// 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 - 订单状态

代码示例:

// 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 注入
  • 处理错误统一处理

代码示例:

// 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 迁移路由配置
  • 配置路由元信息

代码示例:

// 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 改造
  • 实现自动重连
  • 实现心跳检测

代码示例:

// 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

代码示例:

// 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 连接
  • 心跳检测
  • 自动重连
  • 消息推送

🔧 开发命令

# 安装依赖
npm install

# 开发模式
npm run dev

# 构建生产
npm run build

# 预览构建结果
npm run preview

# 代码检查
npm run lint

# 代码格式化
npm run format

📝 环境变量说明

.env.development

# 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

# 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. 样式适配

  • 使用 vhvw 替代 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周

🎓 学习资源


📞 支持

如遇问题,请查阅本文档或相关技术文档。

开始重构: 准备好后,请从 阶段 0 开始执行!