Vue3+uni-app小程序开发实战:从技能大赛到工程化落地 📅 发布时间:2026/9/8 18:44:13 👁 浏览次数: 简介基于Vue框架开发的2024江西省职业院校技能大赛小程序源码聚焦赛事在线参与与高效管理场景适合参赛选手、指导老师及前端学习者用于备赛练习、课程设计与项目参考尤其适合已有一定前端基础、希望将Vue生态应用到实际竞赛项目的读者。资源压缩包共132个文件、约1.51MB其中包括101个PNG图片、9个Vue组件、5个JSON配置、4个GIF动图以及JavaScript、WebP等类型图片与动图支撑界面展示Vue组件体现模块化拆分JSON文件承担页面路由与全局配置整体结构清晰便于按模块检索。目前已有127人学习浏览。除完整可运行的小程序外源码还展现了uni-app跨平台适配、Sass预处理器应用、异步操作封装等实践思路配套LICENSE与说明文档便于合法使用与二次开发可作为高职软件技术、移动应用开发等专业综合实训的参考案例是连接赛题要求与工程实现的完整素材。 2024年江西省职业院校技能大赛的小程序开发设计赛项我用Vue框架完整跑通了整个项目从赛题解读到源码交付中间踩了不少坑也积累了一些值得分享的经验。这篇文章就把整个参赛过程的技术方案、选型逻辑、实操细节和踩坑记录做一个完整梳理希望对准备参加同类比赛或者正在做小程序项目的朋友有帮助。1. 赛题拆解读懂“小程序开发设计”背后的真正考点职业院校技能大赛的赛题名称叫“小程序开发设计”听起来就是做一个微信小程序这么简单但实际拆开来看里面包含的技能点远比想象中多。我的理解是这个赛题考核的绝对不是“会不会写小程序”而是“有没有完整的前端工程化能力”。从近年省赛、国赛的命题趋势来看考核点集中在这样几个维度界面还原能力给定设计稿要求高精度还原页面布局和交互细节框架应用能力是否掌握流行前端框架Vue/React并能在小程序环境中灵活运用业务逻辑实现包括登录鉴权、数据请求、状态管理、本地缓存等通用业务场景组件化思维是否能把页面拆成可复用的组件代码结构是否清晰工程化规范目录组织、代码规范、注释质量、打包配置等我选择的方案是用Vue框架配合uni-app来开发微信小程序。为什么要这么选核心原因有两点第一Vue框架的语法模型和开发体验对前端开发者来说非常友好模板语法、响应式数据绑定、计算属性这些特性比原生小程序语法写起来效率高很多第二uni-app本身编译到微信小程序平台非常成熟一套代码可以后续扩展到App、H5等其他平台在比赛时间有限的情况下能省去大量重复开发的时间。比赛时间通常只有4到6个小时这个时间窗口决定了你不可能从零开始写所有东西所以“基于源码”这个表述就特别关键。赛前准备好一套结构清晰、扩展性良好的基础源码相当于给自己上了双保险。2. 技术选型Vue3组合式API uni-app Pinia的组合逻辑技术选型不是越多越好而是要贴合比赛场景的实际需求。我在选型时做了几轮对比最终敲定的方案是uni-appVue3版本 Vite构建 Pinia状态管理 SCSS样式预处理。2.1 为什么选uni-app而不是原生小程序开发原生微信小程序开发用的是WXML、WXSS和JS语法自成一套体系。如果你已经熟悉Vue的响应式思维切回原生小程序会有明显的“倒退感”——数据更新需要手动setData、组件通信需要处理各种边界情况、样式写起来也没有SCSS嵌套那么舒服。uni-app的核心价值在于它让开发者用Vue的语法写小程序框架帮你完成编译转换。模板指令、计算属性、侦听器、组件生命周期这些Vue特性可以完整体验同时又直接输出微信小程序的产物。比赛场景下代码的书写速度直接决定了功能完成度这一点uni-app有绝对优势。而且从长期价值来看学会uni-app等于同时解锁了小程序、H5、App三条技能线这对以后求职或者接私活都是加分项。如果我只熟悉原生小程序未来的技术边界就被锁死在微信生态里了。2.2 Vue3组合式API比选项式API好在哪这个赛项允许自由选择Vue版本我毫不犹豫选了Vue3的组合式API。原因很实际组合式API把“同一业务逻辑的代码”聚合在一起而不是像选项式API那样强制分散在data、methods、computed、watch里。举个例子一个登录功能涉及的响应式数据、处理函数、计算属性、侦听器在组合式API里可以全部写在一个setup函数块内// 组合式API登录逻辑集中管理 import { ref, computed } from vue import { loginApi } from /api/user export function useLogin() { const username ref() const password ref() const loading ref(false) const isFormValid computed(() username.value.length 4 password.value.length 6 ) async function handleLogin() { if (!isFormValid.value) return loading.value true try { const res await loginApi({ username: username.value, password: password.value }) uni.setStorageSync(token, res.data.token) uni.switchTab({ url: /pages/index/index }) } finally { loading.value false } } return { username, password, loading, isFormValid, handleLogin } }比赛时大脑处于高度紧张状态组合式API让我在切换功能模块时不需要在文件的各个区块之间来回跳转写起来非常顺。这种“高内聚”的代码组织方式也方便复查和排错——一个功能的所有相关代码就在一个块里。2.3 Pinia替代Vuex作为状态管理方案如果项目里存在跨页面共享的数据比如用户登录信息、购物车数据、全局配置就一定要引入状态管理。Vue生态里主流的方案是Vuex和Pinia我选Pinia的原因很简单API更简洁没有mutations的概念直接改state就行而且对TypeScript的支持也更好。// stores/user.js import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: , userInfo: null }), getters: { isLoggedIn: (state) !!state.token }, actions: { setToken(token) { this.token token uni.setStorageSync(token, token) }, logout() { this.token this.userInfo null uni.removeStorageSync(token) } } })在uniapp项目里使用Pinia需要注意一个小细节main.js中要使用createPinia()创建实例同时在App.vue的onLaunch生命周期中注入而不是像纯Vue项目那样直接在createApp时use(pinia)。这个坑如果不提前踩过比赛中容易卡住。// main.js import { createSSRApp } from vue import * as Pinia from pinia import App from ./App.vue export function createApp() { const app createSSRApp(App) const pinia Pinia.createPinia() app.use(pinia) return { app, Pinia } }我知道有些人会问比赛时间这么短有必要用状态管理吗我的回答是如果赛题范围包含多页面需要共享登录态或配置信息用Pinia比用全局变量靠谱得多。全局变量的数据刷新页面就丢了而Pinia配合持久化插件可以做到刷新后自动恢复这个能力在比赛评分时是实实在在的加分项。3. 项目架构设计赛前就搭好的工程骨架比赛不是让你临场发挥创造力而是把准备好的能力在有限时间内最大化输出。所以赛前必须把项目的基础架构完全搭好这样比赛时只需要往框架里填业务功能。3.1 目录结构规划的细节我设计的目录结构是这样的├── src │ ├── api # 接口请求模块 │ │ ├── modules # 按业务域拆分的接口 │ │ └── request.js # 请求封装拦截器、错误处理 │ ├── components # 公共组件 │ │ ├── custom-nav-bar # 自定义导航栏 │ │ ├── empty-state # 空状态占位 │ │ └── load-more # 加载更多 │ ├── pages # 页面目录 │ │ ├── index # 首页 │ │ ├── category # 分类页 │ │ ├── cart # 购物车 │ │ └── user # 个人中心 │ ├── stores # Pinia状态模块 │ ├── styles # 全局样式 │ │ ├── variables.scss # SCSS变量 │ │ ├── mixins.scss # 混合宏 │ │ └── common.scss # 公共样式 │ ├── utils # 工具函数 │ │ ├── format.js # 格式化 │ │ ├── validate.js # 校验 │ │ └── auth.js # 鉴权相关 │ ├── static # 静态资源 │ ├── App.vue │ └── main.js ├── manifest.json # uni-app应用配置 ├── pages.json # 页面路由配置 └── uni.scss这个目录结构不是我随便拍拍脑袋定的而是基于“模块边界清晰、公共逻辑下沉”的原则。api目录和stores目录分离保证了数据请求层和状态管理层互不干扰组件目录按通用性组织页面里重复出现的UI区块全部抽成组件。组卷时如果赛题有新增页面照着这个结构往里加就行不需要动已有模块。3.2 请求封装的统一处理逻辑小程序开发中请求层是所有业务的底座封装得好不好直接影响开发效率。我封装的核心思路是“统一配置、统一拦截、统一错误处理”// src/api/request.js const BASE_URL https://api.example.com export function request(options) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: uni.getStorageSync(token) || }, success: (res) { // 业务状态码统一处理 if (res.data.code 0) { resolve(res.data.data) } else if (res.data.code 401) { // token过期跳转登录 uni.navigateTo({ url: /pages/login/login }) reject(res.data) } else { uni.showToast({ title: res.data.message || 请求失败, icon: none }) reject(res.data) } }, fail: (err) { uni.showToast({ title: 网络异常请稍后重试, icon: none }) reject(err) } }) }) }这里特别说一下为什么我要在请求层统一处理401状态。小程序项目里token过期是一个高频场景如果在每个页面都写一遍跳转登录的逻辑代码会非常冗余。在请求拦截器里统一处理业务页面只需要关心数据返回或者错误提示逻辑会清爽很多。这个设计在比赛答辩时也是可以拿出来讲的一个亮点——说明你有“横切关注点”的架构意识。3.3 components.json 动态配置与条件编译uni-app的页面配置、组件配置都可以在pages.json和manifest.json中统一管理。比赛时如果遇到需要动态配置页面标题的场景可以用uni.setNavigationBarTitle来实现。但这里有一个坑如果你使用了自定义导航栏组件原生导航栏的标题设置就不生效了需要在自定义组件里自己处理标题的数据绑定。// 自定义导航栏组件中动态设置标题 const props defineProps({ title: { type: String, default: } }) // 在页面中使用 custom-nav-bar :titlepageTitle /另外还有一个小技巧利用条件编译来处理不同平台的差异。比如微信小程序中可以使用wx.login获取code但H5端没有这个API条件编译能让你在代码里无缝处理这种区别// #ifdef MP-WEIXIN uni.login({ provider: weixin, success: (loginRes) { // 处理微信登录 } }) // #endif // #ifdef H5 // H5平台的登录逻辑 // #endif这类平台差异化处理是比赛评分中容易被注意到的地方能体现你实际开发经验而不是只会按文档写一种平台的代码。4. 核心功能落地从页面还原到业务逻辑闭环比赛评分的主要来源就是功能实现度和界面还原度。这两个维度做的质量直接决定奖项级别。接下来拆解几个我在实际比赛中实现的核心功能点。4.1 首页信息流与下拉刷新加载更多首页通常是一个信息流列表比赛常见的做法是直接调接口渲染。但单纯渲染列表远远不够需要处理加载状态、空状态、下拉刷新和触底加载这四件事全部做完一个体验完整的列表才算合格。我是这么处理的首次加载显示loading骨架屏数据返回后隐藏下拉刷新通过enablePullDownRefresh开启配合onPullDownRefresh生命周期重置页码并重新请求第一页触底加载通过onReachBottom触发下一页请求并把新数据concat到已有列表数据为空组件empty-state展示占位图和引导文字// 列表页核心逻辑组合式API封装 export function useList(fetcher) { const list ref([]) const page ref(1) const pageSize 10 const loading ref(false) const finished ref(false) async function loadMore() { if (loading.value || finished.value) return loading.value true try { const res await fetcher(page.value, pageSize) list.value.push(...res.list) if (list.value.length res.total) { finished.value true } else { page.value } } finally { loading.value false } } async function refresh() { page.value 1 finished.value false list.value [] await loadMore() } return { list, loading, finished, loadMore, refresh } }这个useList函数把列表逻辑做成了通用组合式函数后面任何需要列表的页面订单列表、收藏列表、通知列表都能直接复用这是组合式API最大的魅力所在。比赛代码复查时这种复用设计也是加分项。4.2 商品详情页的SKU联动选择商品详情页是电商类赛题的必考项SKU选择的交互细节很多稍不注意就会做砸。我对SKU的实现思路是每个规格属性维护一个选中状态已选属性组合后判断剩余库存无库存的规格置灰不可选。function isOptionDisabled(specIndex, optionValue) { // 临时替换当前规格的选择状态 const tempSelected [...selectedSpecs.value] tempSelected[specIndex] optionValue // 根据临时组合查找库存 const sku skuList.value.find(item { return item.specs.every((v, i) v tempSelected[i]) }) return !sku || sku.stock 0 }这里有一个容易踩的坑库存判断时如果只考虑“已经选完所有规格”的情况用户在只选了一部分规格时无法判断某个选项是否可点。正确的做法是在只选了部分规格的情况下只要存在“包含当前选项的SKU组合有库存”该选项就应该可点。这个判断逻辑在比赛时很容易想漏建议提前写好自己的通用SKU组件比赛时直接引入使用。4.3 登录鉴权与用户状态持久化登录是几乎所有小程序的入口功能。比赛里的登录实现通常有两种一种是微信授权登录调uni.login获取code再换token另一种是账号密码登录。多数赛题会给到模拟的账号系统所以账号密码登录会更常见。不管哪种方式登录后的状态管理直接用前面建的Pinia store来处理就行。关键是要处理好几个边界场景登录态过期请求返回401时清理本地token并跳转登录页登录后回跳登录成功后要回到用户原本想访问的页面而不是死板地跳首页登录拦截需要在路由层统一判断哪些页面需要登录才能访问// 路由拦截在App.vue的onLaunch或页面级onShow中判断 export function checkLogin() { const userStore useUserStore() if (!userStore.isLoggedIn) { uni.navigateTo({ url: /pages/login/login?redirect${encodeURIComponent(currentPage)} }) return false } return true }我一般会在需要登录的页面onShow里调用checkLogin通过redirect参数记录来源页面登录成功后使用uni.redirectTo跳转回去。这个细节很多参赛者会忽略导致用户体验断层——登录完回到了首页用户还得重新找入口评分时会被扣掉交互分。4.4 微信支付流程的实现与避坑2024年的比赛里有一个高频考点是微信支付对接尤其是v3版本的接口。这个模块如果赛题里有那它的复杂度会排在所有功能点前列。微信支付v3和v2的核心差异在签名方式和加密算法上。v2是MD5/HMAC-SHA256签名v3是RSA非对称签名RSA加密敏感信息。在小程序端完整流程是前端调用uni.login获取code后端用code换openid后端调用微信支付统一下单接口v3版还要求商户证书和平台证书后端返回支付参数时间戳、随机串、签名等前端用uni.requestPayment拉起支付这里最大的坑是小程序端的支付必须由后端签名前端不要也不能自己拼支付参数。签名所需的商户私钥绝不能放在前端代码里这既是安全要求也是微信的硬性规定。如果比赛环境里的支付只是模拟的那就用mock数据模拟整个支付流程把交互闭环跑通重点是让界面上的“待支付”变为“已支付”这个状态流转做得顺畅自然。5. 经典踩坑记录编译报错与运行时异常的完整排查链路比赛时最怕的其实是那些看似莫名其妙的问题——明明代码写对了却报错页面白屏却不给任何提示。这类问题非常消耗时间我把自己遇到过的几类问题整理成完整的排查链路希望能帮你少走弯路。5.1 组件引入后不渲染easycom规则和路径大小写现象页面引入了自定义组件控制台无报错但组件就是不渲染在页面上。排查链路第一步检查组件路径对不对。pages.json中usingComponents的路径是相对src的拼错层级就会静默失败第二步检查组件名大小写。微信小程序的自定义组件要求首字母大写my-button会被当成内置组件第三步检查是否开了easycom。uni-app的easycom规则可以自动扫描components目录下的组件前提是目录命名符合规范比如components/custom-nav-bar/custom-nav-bar.vue路径和文件名必须完全一致最终的解决方案是规范化组件命名并利用easycom的自动导入机制避免在页面里手动注册组件时反复踩路径问题。5.2 样式错乱rpx在某些机型上出现1px间隙现象使用rpx单位布局的页面在某些安卓机型上出现元素之间的细缝看起来像没对齐。原因rpx是微信小程序的响应式像素单位设计稿宽度750rpx对应屏幕宽度不同机型的换算值存在四舍五入的精度损失导致部分场景出现1px的间隙。解决方案如果是相邻元素的边框或背景错位给父容器加overflow: hidden如果是对半分列布局尽量用flexgap或计算宽度避免直接用5个margin-right去对齐如果追求像素级还原可以考虑在关键UI区域使用px单位配合rpx混用。这个问题的复现概率不算高但一旦出现非常难排查因为它在开发者工具里通常显示正常。比赛时尽量少用百分比和复杂嵌套布局简单直接的flex方案最稳妥。5.3 页面白屏但无报错生命周期里的异步阻塞现象点击某个页面后白屏控制台无报错过一会儿才出内容或者干脆一直白屏。排查链路第一步检查页面onLoad里是否同步阻塞了。如果onLoad中调用了同步耗时操作比如大数据量的循环页面会阻塞渲染第二步检查是否有死循环。computed依赖了会修改自身的值或者watch中修改了监听源都会导致渲染异常第三步检查请求接口是否异常挂起。如果请求没有设置超时时间且后端接口无响应页面数据永远为空我的解决习惯是所有接口请求都设置超时时间页面初始数据给默认值渲染层用v-if判断数据是否存在避免空数据导致渲染失败。这类白屏问题本质上是“数据流没走通”从数据层面排查比从视图层面排查更高效。5.4 自定义导航栏的兼容性问题小程序原生导航栏的样式限制很多比赛时如果赛题要求“沉浸式”或“自定义”导航栏就需要自己写组件。但这个组件有很多细节状态栏高度不同机型状态栏高度不同可以用uni.getSystemInfoSync().statusBarHeight动态获取胶囊按钮位置右上角的胶囊按钮是微信渲染的无法隐藏自定义导航栏时必须避开它滚动渐变页面滚动时导航栏背景色的渐变效果需要监听onPageScroll并动态更新样式如果赛题没有明确要求自定义导航栏建议优先用原生导航栏可以节省大量开发时间。自定义导航栏的样式细节非常吃时间在比赛这种场景下性价比偏低。5.5 视频播放功能m3u8格式的兼容处理技能大赛的小程序赛题里视频播放是一个常驻考点。很多题目会给到m3u8格式的视频流地址需要在小程序里播放。这里有两个关键信息要明确微信小程序的video组件原生支持m3u8格式播放不需要额外引入播放器库如果使用custom内核播放部分Android机型对m3u8的支持不稳定建议使用wechat内核模式或者直接使用原生组件能力video :srcvideoUrl controls autoplay object-fitcontain enable-progress-gesture /video曾经有选手在比赛时把m3u8地址放到h5的video标签里发现不能播放其实是平台的video组件和HTML的video标签实现机制不同。在小程序端直接用video组件是最稳妥的不要绕道去做转换。6. 打包、提交与答辩源码交付阶段的细节决定了最终评分代码写完了不代表比赛结束。源码的整理、打包和提交答辩占了很大比重很多选手代码写得不错却在交付环节丢了分非常可惜。6.1 源码目录清理与注释规范比赛提交的源码会被评委查看代码是否规范直接影响印象分。我提交前的固定动作是删除node_modules、unpackage等构建产物目录清理无用的console.log保留关键逻辑注释在每个工具函数和组件顶部加上功能说明注释在README里写清楚项目启动步骤、技术栈和模块功能列表/** * 格式化日期时间 * param {Date|string|number} date 日期对象/字符串/时间戳 * param {string} pattern 格式化模式默认YYYY-MM-DD HH:mm:ss * returns {string} 格式化后的日期字符串 */ export function formatDateTime(date, pattern YYYY-MM-DD HH:mm:ss) { const d new Date(date) const map { YYYY: d.getFullYear(), MM: String(d.getMonth() 1).padStart(2, 0), DD: String(d.getDate()).padStart(2, 0), HH: String(d.getHours()).padStart(2, 0), mm: String(d.getMinutes()).padStart(2, 0), ss: String(d.getSeconds()).padStart(2, 0) } return pattern.replace(/YYYY|MM|DD|HH|mm|ss/g, (key) map[key]) }别小看注释这件事。评委一天要看几十份代码结构清晰、注释到位的源码能让评委在短时间内理解你的设计意图。这比你多做一个花哨功能更能在评分上受益。6.2 打包上传与真机预览检查开发工具里编译通过不代表真机没问题。提交前一定要在微信开发者工具里做完整的真机预览确认检查页面在不同机型下的适配情况检查网络请求在真机环境下的连通性检查自定义组件的渲染是否正常检查各按钮的点击区域是否有误触有一个基础但必须做的操作在开发者工具里执行“清缓存 → 重新编译”。很多项目在HMR热更新模式下一切正常但冷启动后问题全冒出来——典型的情况是全局变量没有重新初始化、某些静态资源引用路径在打包后失效。这个操作能在提交前帮助你提前发现问题。6.3 答辩环节的技术亮点陈述如果比赛包含答辩环节提前准备好技术亮点陈述非常关键。我的答辩思路是“一核两翼”——以“工程化”为核心以“组件复用”和“状态管理”为两翼。具体来说我会准备这样几个可讲的点请求层的统一封装如何提升了团队协作效率和数据安全性组合式API如何解决了复杂业务逻辑的代码组织问题状态管理如何避免了多页面数据不一致的问题条件编译如何让你的代码同时适配小程序和H5平台答辩时用词要克制不要吹嘘你用了多高级的技术而是讲清楚“为什么这样设计、解决了什么问题”。评委更看重思维能力而不是技术名词堆砌。7. 比赛复盘与进阶建议源码之外的个人能力沉淀比赛结束后我花了一些时间对整份源码做了二次重构把比赛中临时写的“一次性代码”改造成更通用的组件和工具函数。这个复盘过程的价值可能比比赛本身还大。从这次比赛中我总结出的三条核心经验是第一赛前准备比赛中发挥更重要。比赛本质上是把准备好的代码库和开发习惯在限定时间内迁移到赛题场景中。赛前把请求封装、状态管理、公共组件、样式变量、工具函数全部准备到位比赛中就只需要专注业务逻辑的拼装。那些比赛时还在从零搭项目的选手天然就输在了起跑线上。第二代码规范直接决定项目底线。比赛中后期连续开发几十个文件没有统一规范的代码寸步难行。变量命名、目录结构、组件划分这些看似琐碎的规范会在你排查问题时省下大量时间。第三对框架底层能力的理解深度决定了你能走多远。会写Vue模板和真正理解Vue响应式原理是两码事。比赛结束后我把Vue3的响应式源码和uni-app的编译原理都过了一遍这让我在后续做复杂项目时能快速判断问题源头而不是对着报错信息干瞪眼。对于准备参加下一届职业院校技能大赛的朋友我的建议是先别急着刷题把Vue框架的底层机制、uni-app的工程化配置、微信小程序的平台特性三块基础打牢。然后准备一套自己的“万能脚手架”源码反复迭代优化。等到比赛真正来临时你做的就不是“从零开发一个项目”而是“用积累的组件和能力高效拼装一个解决方案”——这个思维差异是拿奖与否的分水岭。最后再分享一个小技巧比赛现场的网络环境通常不太稳定所以你的源码里所有静态资源尽量本地化接口请求设计容错机制数据展示有兜底逻辑。这样即使突然断网你的页面也不会白屏交互还能继续跑通。这个细节在关键时刻能救你一命。本文还有配套的精品资源点击获取