微信小程序一键迁移 uni-app 跨端实践

117 阅读6分钟

微信小程序一键迁移 uni-app 跨端实践:36个页面从 WXML 到 Vue 的完整改造记录 记录了一个 36+ 页面的微信小程序原生项目完整迁移到 uni-app(Vue 3 + Vite)的全过程,涵盖 wx→uni API 替换、模板语法转换、平台差异处理等实战经验。

  • uni-app
  • 小程序
  • Vue
  • 跨端开发
  • 前端架构

背景

最近接手了一个民宿旅居预订平台的微信小程序项目——"中游链",36+ 个页面,80+ 个后端接口,完整的预订、优惠券、分销、电子名片系统。项目原生微信小程序(WXML + WXSS + JS)编写的,仅支持微信端。

需求很明确:App 端也要上,而且后续可能还要 H5。这意味着需要从原生小程序迁移到跨端方案。

选型上最终选择了 uni-app(Vue 3 + Vite),原因有三:

  1. API 高度兼容:wx.* 到 uni.* 的映射几乎是 1:1
  2. 条件编译:平台差异化代码通过 #ifdef 处理,不需要维护多套代码
  3. HBuilderX 生态:发布、打包、云服务一体化

迁移策略:不破不立

项目有一半的逻辑层代码(utils + services + app.js)是纯 JS,理论上可以复用,但 WXML 模板和 WXSS 样式必须重写。

最终决定新建 uni-app 项目,逐步迁移。原项目保留不动,新项目从零搭建基础设施。

整体路线

第一阶段:基础设施搭建
  ├── 项目脚手架(package.json / vite.config.js / manifest.json)
  ├── App 入口迁移(app.js 582行 → App.vue)
  ├── 全局样式迁移(app.wxss 415行 → App.vue style)
  ├── 工具函数迁移(12个 utils,wx→uni API 替换)
  └── API 层迁移(services/api.js 80+接口)

第二阶段:页面迁移
  ├── 核心页面:首页(831行JS + 333行WXML + 731行WXSS)
  ├── 复杂页面:详情页 + 预订 + 订单 + 个人中心 + 登录
  ├── 业务页面:优惠券 + 分销 + 搜索 + 分类
  └── 信息页面:帮助 + 协议 + 隐私 + 名片(约25页)

关键迁移技巧

1. wx → uni 的批量替换

这个最简单但也最繁琐。纯文本替换搞定 90%:

微信 APIuni-app API
wx.requestuni.request
wx.navigateTouni.navigateTo
wx.showToastuni.showToast
wx.getStorageSyncuni.getStorageSync
wx.loginuni.login
wx.uploadFileuni.uploadFile

踩坑提示:wx.getMenuButtonBoundingClientRect() 只能在微信小程序端使用,App 端没有这个概念。需要条件编译处理自定义导航栏的高度计算。

2. WXML → Vue Template 语法对照

这是工作量最大的部分。36 个页面逐个改:

<!-- 微信 WXML -->
<view wx:if="{{show}}">
  <text wx:for="{{list}}" wx:key="id" bindtap="onTap">{{item.name}}</text>
</view>
<!-- Vue -->
<view v-if="show">
  <text v-for="item in list" :key="item.id" @tap="onTap">{{ item.name }}</text>
</view>

核心变化:

  • wx:if → v-if(wx:elif → v-else-if,wx:else → v-else)
  • wx:for="{{list}}" → v-for="item in list"
  • wx:key → :key
  • bindtap → @tap,catchtap → @tap.stop
  • bindchange / bindinput / bindconfirm → @change / @input / @confirm
  • <block> → <template> 或直接用原生标签

3. Page({}) → Vue Options API

微信小程序的 Page({}) 和 Vue 组件之间有个核心差异:数据响应式。

// 微信小程序
Page({
  data: { count: 0 },
  methods: {
    increment() {
      this.setData({ count: this.data.count + 1 })
    }
  }
})
// Vue
export default {
  data() { return { count: 0 } },
  methods: {
    increment() {
      this.count++  // 直接赋值
    }
  }
}

关键区别:Vue 中不需要 this.setData(),直接修改属性即可触发视图更新。

4. App({}) → App.vue + main.js

微信小程序的 App({}) 需要拆分为 uni-app 的 App.vue 和 main.js:

// main.js
import App from './App'
import { createSSRApp } from 'vue'
export function createApp() {
  const app = createSSRApp(App)
  return { app }
}
<!-- App.vue -->
<script setup>
import { onLaunch, onShow } from '@dcloudio/uni-app'

onLaunch(() => {
  // 原来 App.onLaunch 中的逻辑
})
</script>

5. 条件编译处理平台差异

这是 uni-app 最大的优势。同样的组件,在不同平台表现不同:

<template>
  <!-- 微信小程序:使用 open-type 获取手机号 -->
  <!-- #ifdef MP-WEIXIN -->
  <button open-type="getPhoneNumber" @getphonenumber="getPhone">
    微信一键登录
  </button>
  <!-- #endif -->
  
  <!-- App 端:使用短信验证码 -->
  <!-- #ifdef APP-PLUS -->
  <button @tap="showSmsLogin">
    短信验证码登录
  </button>
  <!-- #endif -->
</template>

项目中需要条件编译的部分:

模块微信小程序App 端
登录uni.login() 获取 code短信验证码
支付微信支付 JSAPI微信/支付宝支付
订阅消息uni.requestSubscribeMessage移除
版本更新getUpdateManager移除
腾讯广告 SDK@dn-sdk/miniprogram移除

6. 微信 Behavior → Vue Composable

原项目使用了微信 Behavior 实现装修页面的逻辑复用(表单、倒计时、计算器等)。迁移到 uni-app 后用 Vue 3 的 composable 替代:

// composables/useDecorate.js
import { ref, reactive } from 'vue'

export function useDecorate() {
  const formValues = reactive({})
  const countdownShow = ref(false)
  
  function onFormInput(e) {
    const field = e.currentTarget.dataset.field
    formValues[field] = e.detail.value
  }
  
  // ... 更多逻辑
  
  return {
    formValues, countdownShow,
    onFormInput,
    // ...
  }
}

在页面中使用:

<script>
import { useDecorate } from '@/composables/useDecorate'

export default {
  setup() {
    const { formValues, onFormInput } = useDecorate()
    return { formValues, onFormInput }
  }
}
</script>

迁移数据统计

维度数据
页面数量36 个
迁移代码行数~15000 行 JS + ~8000 行 WXML + ~10000 行 WXSS
Vue 文件数量40 个(1 App + 3 组件 + 36 页面)
工具函数14 个文件
API 接口80+
迁移耗时~4 小时(借助 AI 批量转换 + 手动调优)

踩坑记录

1. getMenuButtonBoundingClientRect 兼容性

微信小程序的胶囊信息在 App 端不存在,自定义导航栏高度需要兜底:

// #ifdef MP-WEIXIN
const menuButton = uni.getMenuButtonBoundingClientRect()
const navHeight = menuButton.height + (menuButton.top - sysInfo.statusBarHeight) * 2 + sysInfo.statusBarHeight
// #endif
// #ifndef MP-WEIXIN
const navHeight = 88  // 固定值兜底
// #endif

2. <scroll-view> 的 enhanced 属性

微信小程序中 scroll-view 的 enhanced 属性在 App 端不支持,需要移除。

3. 模板中的 {{item}} 冲突

WXML 的 wx:for="{{list}}" 中默认 item 和 index 变量在 Vue 中同样适用,但 Vue 的 v-for 中 item 引用不需要双花括号包裹:

<!-- 错误:WXML 写法混入 Vue -->
<text>{{item.name}}</text>  
<!-- item.name 已自动求值,不需要外层 {{}} -->

实际上 Vue 中 {{ expr }} 是文本插值,WXML 中也是 {{ expr }},所以这部分语法是一致的。错误的写法是 wx:for="{{list}}" 这种在 Vue 中应为 v-for="item in list"。

4. wx.requestSubscribeMessage 仅在微信端可用

订阅消息是微信小程序特有的能力,App 端没有这个 API。用条件编译包裹整个 subscribe.js 工具模块。

总结

从原生微信小程序迁移到 uni-app,本质上是一个模板语法转换 + API 适配 + 架构重构的过程。

核心经验:

  1. 不要试图在原项目上改。原生小程序 Page({}) 和 Vue 组件的架构差异太大,混用只会更混乱。新建项目逐步迁移是最干净的方案。

  2. 逻辑层迁移成本远低于视图层。纯 JS 的工具函数和 API 封装只需要 wx→uni 替换就能跑,真正的成本在 36 个页面的 WXML → Vue template 转换。

  3. 条件编译是 uni-app 的灵魂。没有条件编译,跨端项目就会变成 if-else 地狱。用好 #ifdef / #ifndef 可以把平台差异控制在最小范围。

  4. 善用工具批量处理。对于大量重复的模板转换(36 个页面),纯手工改效率极低。结合 AI + 正则批量替换,可以大幅缩短迁移时间。

最终项目已完全跑通微信小程序和 App 端,H5 端也基本可用。代码已开源在 DCloud 插件市场,欢迎交流讨论。