小程序跨平台架构技术方案
约 4369 字大约 15 分钟
2026-04-18
一、背景与问题
1.1 旧架构的痛点
在旧架构中,我们面临着以下严重问题:
1.2 典型问题案例
问题 1:Store 滥用
// 旧架构:一个页面几十个 store 变量,难以维护
computed: {
...mapState(['start', 'end', 'driverInfo', 'orderStatus', 'priceInfo',
'showDialog1', 'showDialog2', 'dialogData1', 'dialogData2', ...])
// 很难知道哪些是真正需要的,哪些已经废弃
}问题 2:数据解析与 Store 耦合
// 旧架构:数据解析逻辑直接写在 Store 中
actions: {
async getRecommendStop() {
const res = await request.post({...})
// 复杂的数据处理逻辑混在 Store 中
const start = this.formatStart(res.data)
this.start = start
// 难以复用,难以测试
}
}问题 3:页面间传值混乱
// 旧架构:所有数据都放到 Store 中
// 场站下车点、场站上车点等多个变量都存在 Store
state: {
transStationStart: null,
transStationEnd: null,
normalStart: null,
normalEnd: null,
// ... 更多的全局变量
}问题 4:弹窗管理地狱
// 旧架构:打开关闭弹窗需要多个变量
data: {
showCancelDialog: false,
cancelDialogData: {},
showPaymentDialog: false,
paymentDialogData: {},
}
// 复杂的弹窗优先级判断
if (showCancelDialog && showPaymentDialog && needShowGuide) {
// 各种复杂的 if 判断
}问题 5:页面逻辑臃肿
// 旧架构:单页面几千行代码
// home/index/index.mpx 3000+ 行
// 所有逻辑都写在一个文件中二、新架构设计理念
2.1 核心设计原则
2.2 整体架构对比
三、核心改进点详解
3.1 Store 规范化
改进前:
// 旧架构:通过 mapState 映射,难以追踪来源
computed: {
...mapState(['start', 'end', 'driverInfo', 'orderStatus', ... 20+ 个变量])
}
// 使用时
this.start // 不知道从哪个 store 来的改进后:
// 新架构:直接使用 store.xxx,来源清晰
import { useUserStore, useDrivingStore } from '@/cross/store'
const userStore = useUserStore()
const drivingStore = useDrivingStore()
// 使用时
userStore.userInfo // 明确知道来自 userStore
drivingStore.orderStatus // 明确知道来自 drivingStore优势:
- 代码可读性大幅提升:一眼就能看出变量的来源
- 便于维护:删除无用的 store 变量时,可以全局搜索
store.xxx找到所有使用点 - 减少冗余:不会出现几十个 store 变量的情况
- IDE 友好:可以轻松跳转到 store 定义
3.2 Store 职责严格把关
改进前:
// 旧架构:Store 是大杂烩
state: {
// 真正需要共享的
userInfo: {},
orderStatus: '',
// 不应该共享的
homeStartPoint: null, // 只有首页用
homeEndPoint: null, // 只有首页用
searchStartPoint: null, // 只有搜索页用
searchEndPoint: null, // 只有搜索页用
showDialog1: false, // 只有某个弹窗用
showDialog2: false, // 只有某个弹窗用
// ... 几十个变量
}改进后:
// 新架构:Store 只存真正需要共享的数据
export const useUserStore = defineStore('user-store', {
state: () => ({
cuid: '', // 全局唯一标识
bduss: '', // 登录凭证(全局)
userInfo: {}, // 用户信息(全局)
userLocation: null, // 用户位置(全局)
homeCompanyInfo: null, // 家公司信息(全局)
capsuleInfo: null, // 实时订单胶囊(全局)
})
})
// 页面特定的数据不放在 Store 中
const noticeStore = useNoticeStore()
noticeStore.setTransmitInfo('home', {
start: startPoint, // 页面间传值,用完即销毁
end: endPoint
})Store 使用规范:
优势:
- Store 精简:只存真正需要的全局状态
- 减少耦合:页面间不再通过 Store 共享不必要的数据
- 易于理解:Store 的职责清晰明确
3.3 Logic 文件夹 - 数据解析解耦
改进前:
// 旧架构:数据解析与 Store 耦合
actions: {
async getRecommendStop() {
const res = await request.post({...})
// 复杂的数据处理逻辑混在 Store 中
const start = {}
const markers = []
const polygons = []
res.data.recommendstops.forEach(item => {
// 各种数据处理逻辑
const gcjPoi = coordsTrans.bd09MCtoGcj02LL({...})
const isLeft = coordsTrans.isLeftOrRight(centerPoi, item)
// ... 100+ 行处理逻辑
})
this.start = start
this.markers = markers
}
}改进后:
// Logic 层:只负责数据处理,不涉及 Store
// cross/logic/getRecommandStop.js
export default {
async getRecommandStop({ point, start_loc_type }) {
// 1. 发起请求
const res = await request.post({...})
// 2. 处理数据
const { start, markers, polygons, options } =
await this.processRecommendData(res.data, point)
// 3. 返回处理后的数据
return { start, markers, polygons, options }
},
async processRecommendData(data, point) {
// 纯数据处理逻辑,不涉及任何 Store
const start = this.formatStart(data)
const markers = this.generateMarkers(data)
const polygons = this.generatePolygons(data)
return { start, markers, polygons }
}
}
// 页面中使用
async requestRecommendStop({ point, start_loc_type }) {
const { start, markers, polygons, options } =
await RecommendStopLogic.getRecommandStop({ point, start_loc_type })
this.start = start // 页面自己管理状态
this.polygons = polygons
}架构分层:
优势:
- 职责清晰:Logic 只管数据处理,不管状态管理
- 易于测试:纯函数,方便单元测试
- 易于复用:Logic 可以在任何地方复用
- 降低耦合:业务逻辑不依赖 Store
3.4 页面间传值 - NoticeStore
改进前:
// 旧架构:所有数据都放到 Store
state: {
// 首页 → 搜索页
homeToSearchStart: null,
homeToSearchEnd: null,
// 搜索页 → 首页
searchToHomeStart: null,
// 场站相关
transStationStart: null,
transStationEnd: null,
transStationMarkers: null,
// ... 无数个页面间传值的变量
}改进后:
// cross/store/noticeStore.js
export const useNoticeStore = defineStore('notice-store', {
state: () => ({
home: null,
search: null,
dacheplan: null,
plandetail: null,
mapSearch: null,
}),
actions: {
setTransmitInfo(key, data) {
this[key] = data
},
getTransmitInfo(key) {
const res = JSON.parse(JSON.stringify(this[key]))
this[key] = null // 获取后立即清空
return res
}
}
})
// 首页跳转搜索页
noticeStore.setTransmitInfo('search', {
fromPage: 'home',
pointType: 'start',
start: this.start,
recommendStops: markers
})
mpx.navigateTo({ url: '/page/home/search/search' })
// 搜索页接收
const PageNoticeInfo = noticeStore.getTransmitInfo('search')
// 获取后自动清空,不会有残留传值流程:
优势:
- 自动清理:获取后立即清空,不会有数据残留
- 职责清晰:专门用于页面间传值
- 简化 Store:不会因为页面传值而导致 Store 臃肿
- 易于追踪:每个页面都有独立的 key,易于调试
3.5 页面逻辑拆分 - 组件职责清晰
改进前:
// 旧架构:单页面几千行代码
// home/index/index.mpx 3000+ 行
<template>
<view class="home">
<!-- 所有的 UI 都在这里 -->
<map></map>
<search-box></search-box>
<call-comp></call-comp>
<price-info></price-info>
<!-- ... 几十个组件 -->
</view>
</template>
<script>
export default {
data: {...},
methods: {
// 几十个方法,2000+ 行
handleMapMove() {...},
handleSearch() {...},
handleCall() {...},
handlePayment() {...},
// ... 所有逻辑混在一起
}
}
</script>改进后:
// 新架构:组件职责清晰
// page/home/index/components/page.mpx
// 只负责页面整体布局和组件协调
<template>
<view class="index">
<page-map></page-map>
<slide-panel>
<search-box></search-box>
<home-company></home-company>
</slide-panel>
<bottom-dialog></bottom-dialog>
</view>
</template>
<script>
// 只负责页面级逻辑
export default {
methods: {
handleGoToSearch() {
// 页面跳转逻辑
}
}
}
</script>
// call-comp.mpx - 发单组件
// 只负责发单相关的逻辑
export default {
methods: {
async handleCall() {
// 发单逻辑,完全不依赖父组件的数据
}
}
}
// search-box.mpx - 搜索框组件
// 只负责搜索相关的逻辑组件职责划分:
优势:
- 职责单一:每个组件只负责一个功能
- 易于维护:修改某个功能只需要修改对应组件
- 易于测试:组件之间耦合度低,容易单元测试
- 代码精简:单个文件代码量大幅减少
3.6 弹窗管理 - DrawerStore
改进前:
// 旧架构:每个弹窗需要多个变量
data: {
showCancelDialog: false,
cancelDialogData: {},
showPaymentDialog: false,
paymentDialogData: {},
showGuideDialog: false,
guideDialogData: {},
showModifyEndDialog: false,
modifyEndDialogData: {},
// ... 10+ 个弹窗 = 20+ 个变量
}
methods: {
// 打开弹窗需要设置两个变量
openCancelDialog(data) {
this.showCancelDialog = true
this.cancelDialogData = data
},
// 关闭弹窗
closeCancelDialog() {
this.showCancelDialog = false
this.cancelDialogData = {}
},
// 复杂的弹窗优先级判断
checkDialogPriority() {
if (this.showCancelDialog && this.showPaymentDialog) {
// 各种 if 判断
}
}
}改进后:
// cross/store/drawerStore.js
export const useDrawerStore = defineStore('drawer-store', {
state: () => ({
waitCancelDrawer: false,
waitCancelData: {},
normalCancelDrawer: false,
normalCancelData: {},
selectCarListDrawer: false,
selectCarListData: {},
// ... 所有弹窗的状态和数据
openedDrawer: {}, // 已打开的弹窗列表
}),
actions: {
openDrawer(type, data = {}) {
// 1. 优先级判断
let canShow = this.checkPriority(type)
if (!canShow) return
// 2. 打开弹窗
const dataKey = type.replace('Drawer', 'Data')
this[type] = true
this[dataKey] = data
this.openedDrawer[type] = true
},
closeDrawer(type) {
const dataKey = type.replace('Drawer', 'Data')
this[type] = false
this.openedDrawer[type] = false
this[dataKey] = {}
},
checkPriority(type) {
// 统一的优先级逻辑
const isOpenedDrawer = Object.keys(this.openedDrawer).length > 0
switch (type) {
case 'waitCancelDrawer':
// 高优先级,关闭其他弹窗
this.closeAllDrawer()
return true
case 'selectCarListDrawer':
// 普通优先级,互斥
return !isOpenedDrawer
// ... 其他弹窗的优先级逻辑
}
}
}
})
// 页面中使用
const drawerStore = useDrawerStore()
// 打开弹窗 - 一行代码
drawerStore.openDrawer('waitCancelDrawer', { orderNo: 'xxx' })
// 关闭弹窗
drawerStore.closeDrawer('waitCancelDrawer')弹窗管理流程:
优势:
- 代码精简:从 20+ 个变量减少到统一的管理
- 优先级清晰:统一的优先级逻辑,易于维护
- 易于扩展:新增弹窗只需要添加一个状态
- 避免冲突:内置优先级判断,不会出现多个弹窗同时显示
3.7 跨平台支持
架构设计:
代码示例:
// 核心组件
// page/driving/orderProcess/components/page.mpx
<template>
<view class="order-process">
<map></map>
<driver-info></driver-info>
<price-info></price-info>
<drawer-main></drawer-main>
</view>
</template>
<script>
// 通用逻辑
export default {
created() {
this.initOrder()
},
methods: {
initOrder() {
// 所有平台通用的逻辑
}
}
}
</script>
// 平台特定文件
// page.wx.mpx - 微信特定
if (__mpx_mode__ === 'wx') {
// 微信特定代码
wx.showToast(...)
}
// page.ali.mpx - 支付宝特定
if (__mpx_mode__ === 'ali') {
// 支付宝特定代码
my.showToast(...)
}
// page.tt.mpx - 字节跳动特定
if (__mpx_mode__ === 'tt') {
// 抖音特定代码
tt.showToast(...)
}跨平台目录结构:
src/
├── cross/ # 跨平台通用模块(三个平台共用)
│ ├── logic/
│ ├── store/
│ └── utils/
│
└── page/
└── driving/
└── orderProcess/
└── components/
├── page.mpx # 核心组件
├── page.wx.mpx # 微信特定
├── page.ali.mpx # 支付宝特定
└── page.tt.mpx # 抖音特定优势:
- 代码复用:核心逻辑只需写一次
- 维护成本低:修改核心逻辑,所有平台同步更新
- 易于扩展:新增平台只需要添加平台特定文件
- 平台优化:可以针对不同平台做特定优化
四、模块复用方案
4.1 同系列小程序复用
场景:多个小程序账号体系相同,只需要复用模块
实施步骤:
- 复制
cross/目录(通用模块) - 选择需要的页面模块(如
driving/) - 添加自定义模块(如
business/) - 配置路由
4.2 跨系列小程序复用
场景:账号体系不同,只需要某个功能模块(如 driving)
依赖分析流程:
// 1. 分析 driving 模块使用的 store
import { useUserStore, useDrivingStore, useOrderStore } from '@/cross/store'
// 2. 确认依赖
// - userStore: 用户系统(需要复刻)
// - drivingStore: 行程状态(可以直接用)
// - orderStore: 订单状态(可以直接用)
// 3. 复刻用户系统
// 新小程序实现用户登录、用户信息等功能
// 4. 复制模块
// - driving/ 模块
// - cross/logic/ 相关逻辑
// - cross/utils/ 相关工具
// 5. 完成!复用示例:
// 步骤 1:分析依赖
// driving 模块使用了这些 Store
// userStore - 需要复刻(用户系统不同)
// drivingStore - 直接使用
// orderStore - 直接使用
// drawerStore - 直接使用
// 步骤 2:复制模块
新项目/
├── cross/ # 复制 cross 目录
│ ├── logic/
│ ├── store/
│ └── utils/
└── page/
└── driving/ # 复制 driving 模块
└── orderProcess/
// 步骤 3:复刻用户系统
// 在新项目中实现 userStore 的功能
// 步骤 4:配置路由
// 添加 driving 相关的路由
// 步骤 5:测试
// 测试 driving 模块功能五、新旧架构对比总结
5.1 代码量对比
| 指标 | 旧架构 | 新架构 | 改进 |
|---|---|---|---|
| 单文件最大行数 | 3000+ 行 | 500 行 | ⬇️ 83% |
| Store 变量数量 | 50+ 个 | 5-10 个 | ⬇️ 80% |
| 弹窗变量数量 | 20+ 个 | 0 个(统一管理) | ⬇️ 100% |
| 页面间传值变量 | 15+ 个 | 0 个(NoticeStore) | ⬇️ 100% |
| 代码复用率 | 30% | 80% | ⬆️ 167% |
5.2 可维护性对比
5.3 开发效率对比
| 场景 | 旧架构 | 新架构 | 提升幅度 |
|---|---|---|---|
| 新增页面 | 需要写 2000+ 行 | 只需写 300-500 行 | ⬆️ 300% |
| 新增弹窗 | 需要修改 3+ 个文件 | 只需添加 drawer 状态 | ⬆️ 500% |
| 修复 Bug | 需要搜索整个项目 | 定位精确到模块 | ⬆️ 200% |
| 新增平台 | 需要重写项目 | 只需添加平台文件 | ⬆️ 800% |
| 模块复用 | 几乎不可行 | 极其简单 | ⬆️ 1000% |
六、实际收益
6.1 开发效率提升
- 新功能开发时间:从 3 天缩短到 1 天
- Bug 修复时间:从 2 小时缩短到 30 分钟
- 代码审查时间:从 1 小时缩短到 15 分钟
6.2 代码质量提升
- 代码可读性:⭐⭐ → ⭐⭐⭐⭐⭐
- 代码复用率:30% → 80%
- 测试覆盖率:0% → 60%+(Logic 层可测试)
6.3 维护成本降低
- Bug 数量:每月 20+ 个 → 5 个以内
- 重构频率:每 2 个月一次 → 半年一次
- 新人上手时间:2 周 → 3 天
七、总结
7.1 核心优势
7.2 关键成果
- Store 规范化:从大杂烩到职责清晰
- Logic 解耦:数据处理与状态管理分离
- 组件化:页面逻辑拆分,职责单一
- 弹窗统一管理:优先级清晰,易于扩展
- 跨平台支持:写一次代码,支持三个平台
- 模块复用:轻松复用到其他小程序