本文主要从以下几个方面入手:
-
API请求、
-
状态管理、
-
工具函数
-
路由
-
包管理工具
一、整体设计:分层解耦,复用优先
核心思路是 "业务逻辑抽离为公共层,两端仅保留平台特有逻辑" ,整体架构分为 5 层,从下到上分别为:
bash
├─ 1. 基础工具层(utils):纯函数工具,两端完全复用
├─ 2. API 通信层(api):统一请求逻辑,适配两端差异
├─ 3. 状态管理层(store):核心业务状态,两端共享
├─ 4. 业务逻辑层(services):封装业务方法,两端复用
└─ 5. 视图层(components/views):平台特有组件/页面,差异化实现
核心原则
- 公共逻辑 "上提" :API、状态、工具函数等无平台依赖的逻辑,抽离到独立包 / 目录,避免两端重复编写;
- 平台差异 "下沉" :UI 组件、路由、原生能力(如扫码、推送)等平台特有逻辑,在两端单独实现,通过 "适配层" 对接公共逻辑;
- 依赖统一管理:统一包管理工具(如 npm),公共依赖(如 axios、lodash)在两端共享版本,避免兼容性问题。
二、具体技术方案:分层实现复用
1. 基础工具层(utils):100% 复用,纯函数设计
核心目标
提供无副作用的纯函数工具,覆盖格式化、验证、计算等通用能力,两端完全复用。
实现方案
-
目录结构 :在项目根目录创建
packages/utils(或独立 npm 包),按功能分类:bashutils/ ├─ format/:时间格式化(dateFormat)、金额格式化(moneyFormat) ├─ validate/:手机号验证(isPhone)、邮箱验证(isEmail)、表单规则(formRules) ├─ compute/:分页计算(calcPageInfo)、权限判断(hasPermission) └─ common/:深拷贝(deepClone)、防抖节流(debounce/throttle) -
代码示例(时间格式化工具):
javascript// packages/utils/format/dateFormat.js export const dateFormat = (date, format = 'YYYY-MM-DD HH:mm:ss') => { // 纯函数实现,无平台依赖 const dt = new Date(date); const options = { 'YYYY': dt.getFullYear(), 'MM': String(dt.getMonth() + 1).padStart(2, '0'), // ... 其他格式化逻辑 }; return Object.entries(options).reduce((res, [key, val]) => res.replace(key, val), format); }; -
复用方式 :两端通过
import { dateFormat } from '@/utils/format/dateFormat'直接引入,无需修改。
2. API 通信层(api):统一请求逻辑,适配两端差异
核心目标
封装 API 请求逻辑(请求拦截、响应拦截、错误处理),统一接口调用方式,仅适配两端的请求库差异(Vue 用 axios,uni-app 用 uni.request)。
实现方案
-
分层设计:分为 "基础请求适配层" 和 "业务接口层",前者处理平台差异,后者纯业务逻辑复用:
vbscriptapi/ ├─ request/:基础请求适配(区分 Vue/uni-app) │ ├─ index.js:入口文件(根据环境导出对应请求实例) │ ├─ webRequest.js:Vue 端(基于 axios) │ └─ uniRequest.js:uni-app 端(基于 uni.request) └─ modules/:业务接口(两端完全复用) ├─ user.js:用户相关(登录、权限) ├─ order.js:订单相关(列表、详情) └─ goods.js:商品相关(新增、编辑) -
关键实现:请求适配层(处理平台差异):
javascript// api/request/webRequest.js(Vue 端) import axios from 'axios'; const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, // PC 端环境变量 timeout: 10000 }); // 请求拦截:添加 token service.interceptors.request.use(config => { config.headers.token = localStorage.getItem('token'); return config; }); // 响应拦截:统一错误处理 service.interceptors.response.use( res => res.data, err => { /* 统一错误提示(如 Element Plus Message) */ } ); export default service; // api/request/uniRequest.js(uni-app 端) export default function uniRequest(config) { return new Promise((resolve, reject) => { uni.request({ url: config.baseURL || import.meta.env.VITE_API_BASE_URL, // 移动端环境变量 method: config.method || 'GET', data: config.data, header: { token: uni.getStorageSync('token') }, // uni-app 存储 API success: res => resolve(res.data), fail: err => { /* 统一错误提示(如 uni.showToast) */ } }); }); } // api/request/index.js(入口:自动适配环境) let request; if (process.env.VUE_APP_PLATFORM === 'web') { request = import('./webRequest').then(m => m.default); } else { request = import('./uniRequest').then(m => m.default); } export default request; -
业务接口层(纯逻辑复用,无平台依赖):
javascript// api/modules/user.js(两端完全复用) import request from '../request'; // 登录接口 export const login = (params) => request({ url: '/api/user/login', method: 'POST', data: params }); // 获取用户权限列表 export const getUserPermissions = () => request({ url: '/api/user/permissions', method: 'GET' });
3. 状态管理层(store):核心业务状态共享
核心目标
用 Vuex/Pinia 管理全局状态(如用户信息、权限、全局配置),两端共享状态定义和 mutations/actions,仅适配平台特有存储(如 token 存储)。
实现方案
-
技术选型:优先用 Pinia(Vue 3 推荐,支持 TypeScript,更轻量,如果项目的搭建时不要求ts,推荐vuex),两端共用 Pinia 实例。
-
目录结构:
store/ ├─ index.js:Pinia 实例创建(适配两端存储) ├─ modules/ │ ├─ userStore.js:用户状态(登录、权限) │ ├─ appStore.js:应用配置(主题、语言) │ └─ orderStore.js:订单状态(待办数量、筛选条件) -
关键实现:适配两端存储:
javascript// store/index.js(Pinia 实例创建) import { createPinia } from 'pinia'; import { createPersistedState } from 'pinia-plugin-persistedstate'; // 持久化插件 const pinia = createPinia(); // 适配两端持久化存储:Vue 用 localStorage,uni-app 用 uniStorage const storage = process.env.VUE_APP_PLATFORM === 'web' ? window.localStorage : { getItem: uni.getStorageSync, setItem: uni.setStorageSync, removeItem: uni.removeStorageSync }; // 安装持久化插件,指定存储方式 pinia.use(createPersistedState({ storage: { getItem: (key) => storage.getItem(key), setItem: (key, value) => storage.setItem(key, value), removeItem: (key) => storage.removeItem(key) } })); export default pinia; -
业务状态示例(用户状态,两端复用):
javascript// store/modules/userStore.js import { defineStore } from 'pinia'; import { login, getUserPermissions } from '@/api/modules/user'; import { hasPermission } from '@/utils/compute/permission'; export const useUserStore = defineStore('user', { state: () => ({ token: '', info: {}, // 用户信息 permissions: [] // 权限列表 }), actions: { // 登录:业务逻辑完全复用 async loginAction(params) { const res = await login(params); this.token = res.token; await this.getPermissionsAction(); // 登录后获取权限 }, // 获取权限:业务逻辑完全复用 async getPermissionsAction() { const res = await getUserPermissions(); this.permissions = res.list; }, // 权限判断:复用工具函数 hasPerm(perm) { return hasPermission(this.permissions, perm); } }, persist: true // 持久化状态(适配两端存储) });
4. 业务逻辑层(services):封装复杂业务流程
核心目标
将跨组件的复杂业务流程(如表单提交、数据导出、批量操作)抽离为 service 方法,两端复用业务逻辑,仅调用平台特有 UI 交互(如弹窗、加载提示)。
实现方案
-
目录结构:按业务模块分类,每个 service 方法接收 "平台适配回调" 处理 UI 差异:
plaintext
services/ ├─ userService.js:用户相关(密码重置、信息修改) ├─ orderService.js:订单相关(批量审核、导出订单) └─ formService.js:表单相关(复杂表单提交、数据校验) -
代码示例(订单批量审核,适配两端 UI):
javascript// services/orderService.js import { useOrderStore } from '@/store/modules/orderStore'; import { batchAuditOrder } from '@/api/modules/order'; /** * 批量审核订单 * @param {Array} orderIds - 订单ID列表 * @param {Function} loadingCallback - 平台加载提示(如 Vue 的 ElLoading、uni 的 showLoading) * @param {Function} successCallback - 平台成功提示(如 ElMessage、uni.showToast) */ export const batchAuditOrderService = async (orderIds, loadingCallback, successCallback) => { const orderStore = useOrderStore(); // 1. 调用平台加载提示(通过回调适配) const closeLoading = loadingCallback(); try { // 2. 业务逻辑:调用 API + 更新状态(两端复用) await batchAuditOrder({ ids: orderIds }); await orderStore.getOrderListAction(); // 重新获取订单列表 // 3. 调用平台成功提示(通过回调适配) successCallback('审核成功'); } catch (err) { throw err; // 抛错让调用方处理(如统一错误提示) } finally { closeLoading(); // 关闭加载提示 } }; -
两端调用示例:
javascript// Vue PC 端调用 import { batchAuditOrderService } from '@/services/orderService'; import { ElLoading, ElMessage } from 'element-plus'; const handleBatchAudit = async () => { await batchAuditOrderService( selectedIds, () => ElLoading.service({ text: '审核中...' }), // PC 加载提示 (msg) => ElMessage.success(msg) // PC 成功提示 ); }; // uni-app 移动端调用 import { batchAuditOrderService } from '@/services/orderService'; const handleBatchAudit = async () => { await batchAuditOrderService( selectedIds, () => { // 移动端加载提示 uni.showLoading({ title: '审核中...' }); return () => uni.hideLoading(); // 返回关闭方法 }, (msg) => uni.showToast({ title: msg, icon: 'success' }) // 移动端成功提示 ); };
5. 视图层:差异化实现,复用公共组件
核心目标
UI 组件和页面因平台交互差异(PC 用鼠标 / 键盘,移动端用触摸)需单独实现,但可抽离 "无交互纯展示组件"(如数据卡片、空状态)两端复用。
实现方案
-
公共 UI 组件 :在
components/common目录创建纯展示组件(无平台依赖),如:csscomponents/ ├─ common/:两端复用组件 │ ├─ EmptyState.vue:空状态(无数据提示) │ ├─ DataCard.vue:数据卡片(展示统计数据) │ └─ TablePagination.vue:分页控件(适配两端表格) ├─ web/:Vue PC 特有组件(如 ElTable 封装) └─ uni/:uni-app 特有组件(如 uni-table 封装) -
页面差异化:两端页面目录分开,但复用公共业务逻辑:
ruby// Vue PC 端页面 views/web/order/OrderList.vue:用 Element Plus 组件,调用 orderService // uni-app 移动端页面 pages/uni/order/OrderList.vue:用 uni-app 组件,调用 same orderService
三、工程化保障:统一规范,降低维护成本
1. 环境配置统一
-
用
.env文件统一管理环境变量(API 地址、环境标识),两端共享变量名:ini// Vue PC 端 .env.development VITE_API_BASE_URL = 'https://pc-api.xxx.com' VITE_APP_PLATFORM = 'web' // uni-app 端 .env.development VITE_API_BASE_URL = 'https://mobile-api.xxx.com' VITE_APP_PLATFORM = 'uni'
2. 代码规范统一
- 用 ESLint + Prettier 统一代码风格,两端共用配置文件(
.eslintrc.js、.prettierrc); - 用 Husky + lint-staged 做提交校验,避免不规范代码提交。
3. 构建部署统一
- Vue PC 端:用 Vite 构建,部署到 Web 服务器(如 Nginx);
- uni-app 端:用 HBuilderX 或 CLI 构建,发布为微信小程序 / APP;
- 公共层(utils/api/store)可打包为 npm 私有包,两端通过 npm 安装,避免代码复制。