拓冰建站拓冰建站
首页 / 资讯中心 / 正文

uniapp+Vue3工程化模板:Pinia状态管理与请求层封装实践

简介这是一套基于uniapp、uview-plus、Vue3、TypeScript与Pinia开发的快速开发模版源码定位为跨平台应用的基础工程适合同一套代码输出App、H5及各类小程序能明显节省项目初始化与多端适配的时间。资源包共29个文件以JavaScript、TypeScript、Vue、JSON、Scss及CSS等类型为主压缩包大小仅478KB工程内包含页面配置、状态管理、API请求封装、通用工具、自定义导航组件、全局样式与说明文档目录划分清晰方便按需裁剪。技术层面充分运用Vue3组合式API、TypeScript类型收窄、Pinia状态仓库以及uview-plus的丰富组件代码风格统一整体工程化程度较高。模板内置了可复用的页面框架、状态存储与网络层封装并配有清晰注释方便二次扩展。目前已有638人浏览学习适合具备一定Vue基础、希望直接基于规范模板开展业务迭代的中级前端开发者。1. 解压就能跑的 uniapp Vue3 模板工程结构先看这几处做 uniapp 项目最耗时间的往往不是业务页面而是看不见的工程层路由配置、状态管理、请求封装、导航栏适配每一处都决定后续迭代的体感。这套基于 uniapp uview-plus Vue3 TypeScript Pinia 的快速开发模板解压 upload.zip 就能看到一个完整可运行工程pagesMember 会员目录、common/http 请求层、stores/user 状态仓库、custom-navbar 自定义导航栏都已搭好。对于准备把 Vue2 迁到 Vue3、或第一次在 uniapp 里用 Pinia 的团队它的价值在于边界清楚二次开发不用全局搜索。源码只有 26 个文件正好研究跨端项目的标准切法。2. 从 pages.json 到 vite.config.ts模板的工程化底座2.1 pages.json 的跨端路由与导航配置pages.json 在 uniapp 里承担的不只是路由注册。导航栏标题、背景色、是否自定义导航以及 tabBar 的图标和选中态全部由这份 JSON 驱动。模板把主流程页面放在 src/pages 下会员相关页面单独放进 pagesMember 目录这种按业务域切分的方式在 multi-end 编译时很容易排查问题出在哪一端。{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 首页, enablePullDownRefresh: true, navigationStyle: custom } } ], globalStyle: { navigationBarTextStyle: black, navigationBarTitleText: 跨端模板, navigationBarBackgroundColor: #ffffff, backgroundColor: #f7f8fa } }navigationStyle 设为 custom 后页面顶部会留出状态栏高度的空白必须配合自定义导航栏组件使用模板里的 components/custom-navbar 就是干这件事的。enablePullDownRefresh 在 App 端和小程序端表现一致但 H5 端要自己在页面里实现 onPullDownRefresh 生命周期否则下拉回调不会触发。这里有个经常被忽略的细节pages.json 数组里第一个页面就是小程序端的首页顺序错了会导致编译产物启动时进错页面。2.2 manifest.json 里的多端身份配置manifest.json 是 uniapp 面向各平台的身份文件模块权限、App 图标、小程序 appid、H5 路由模式都集中在这里。很多人在真机调试时报错回头查都是 appid 没填或者填错环境。配置项位置影响范围appidmp-weixin 节点小程序真机预览和上传router.modeh5 节点H5 端 URL 是否带 #定位权限描述app-plus 节点iOS 上架审核必查分享开关mp-weixin 节点微信好友分享行为app-plus 节点里的权限描述要写到具体用途例如用于展示附近的商家位置直接写获取位置审核很容易被驳回。H5 端如果要嵌入微信公众号做定位前提是页面必须走 HTTPS且域名要加进公众号后台的 JS 接口安全域名白名单这两个条件缺一个授权弹窗都出不来这个问题通常要真机调试才暴露。2.3 vite.config.ts 别名、插件与构建参数Vue3 版本的 uniapp 使用 Vite 构建模板根目录的 vite.config.ts 核心职责是注册插件、配置别名和构建参数。import { defineConfig } from vite import uni from dcloudio/vite-plugin-uni import path from path export default defineConfig({ plugins: [uni()], resolve: { alias: { : path.resolve(__dirname, src), member: path.resolve(__dirname, src/pagesMember) } }, build: { sourcemap: process.env.NODE_ENV development, chunkSizeWarningLimit: 1500 } })uniapp 的 Vite 插件必须唯一注册如果自己额外引入 vitejs/plugin-vue 会导致编译冲突。alias 里的 指向 srcpagesMember 单独起一个 member 别名是为了在深层目录引用会员模块时少写相对路径。chunkSizeWarningLimit 调高只是避免 uview-plus 全量引入时告警刷屏不能依赖它后续应该在 main.ts 里改成组件按需引入否则小程序主包体积会很快逼近 2MB 上限。注意改完 vite.config.ts 里的 alias必须同步更新 tsconfig.json 的 paths否则编辑器的类型跳转和编译器的路径解析会不一致。3. Pinia 状态层与 common/http 请求层的拆解3.1 stores/user.js为什么状态仓库先写成 JS模板的 stores 目录下只有 user.js而且是 JavaScript 而不是 TypeScript。这不是偷懒而是 uniapp 生态里很多第三方 SDK 的类型声明并不完整强制用 TS 写会出现一堆 any 断言。常见做法是状态层用 JS 保持灵活页面调用处用 TypeScript 做类型收窄。user.js 里维护 token、userInfo、loginStatus 三个状态通过 Pinia 的 action 封装登录、登出和资料更新。// stores/user.js import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: uni.getStorageSync(token) || , userInfo: {}, loginStatus: false }), getters: { isLogin: (state) Boolean(state.token state.loginStatus) }, actions: { setToken(token) { this.token token uni.setStorageSync(token, token) this.loginStatus true }, logout() { this.token this.userInfo {} this.loginStatus false uni.removeStorageSync(token) } } })关键设计是 token 同时写入 Pinia 和 StoragePinia 负责内存中的响应式状态uni.setStorageSync 负责冷启动恢复App 进程被杀掉再打开用户不会掉登录。getters 里的 isLogin 用 token 加 loginStatus 双重判断避免只有 token 没有用户信息时误判为已登录。与 Vuex 相比Pinia 的 action 天然支持异步不需要额外包装按模块定义 store 时也不需要 namespaced 配置。对比项VuexPinia模块语法需要 namespaced定义即模块mutation必须同步已移除action 直接改状态TypeScript 推导需要手工声明原生友好HMR需要插件内置支持3.2 请求封装里的拦截、超时与 401 收敛所有请求都应该走统一出口而不是在页面里直接调 uni.request。模板的 common/http 目录承担这个职责config 文件导出 baseURL 和默认超时核心 index 文件做请求封装。// common/http/config.js export const baseURL https://api.example.com export const timeout 15000// common/http/index.js import { baseURL, timeout } from ./config import { useUserStore } from /stores/user export function request(options) { const userStore useUserStore() return new Promise((resolve, reject) { uni.request({ url: /^https?:\/\//.test(options.url) ? options.url : baseURL options.url, method: options.method || GET, data: options.data || {}, timeout, header: { Content-Type: application/json, Authorization: userStore.token ? Bearer ${userStore.token} : , ...options.header }, success: (res) { if (res.statusCode 401) { userStore.logout() uni.navigateTo({ url: /pagesMember/login/index }) reject(res) return } if (res.statusCode 200 res.statusCode 300) { resolve(res.data) } else { uni.showToast({ title: 请求失败${res.statusCode}, icon: none }) reject(res) } }, fail: (err) { uni.showToast({ title: 网络异常请检查连接, icon: none }) reject(err) } }) }) }URL 拼接用正则判断是否为绝对地址第三方接口也能走同一个封装不会误拼 baseURL。401 的处理收敛在请求层统一登出并跳转登录页避免每个页面重复写逻辑。timeout 默认 15 秒对普通 JSON 接口够用但图片上传、文件导出这类接口建议在调用时单独传 timeout 覆盖否则 iOS 端容易在弱网下提前断连。3.3 api 目录与页面调用的边界api 目录下的 index.js 把接口按模块导出页面只依赖 api 函数不感知 URL 和请求细节。// api/index.js import { request } from /common/http export const fetchUserInfo (userId) request({ url: /user/${userId}, method: GET }) export const updateProfile (data) request({ url: /user/profile, method: PUT, data })这样做的好处是接口路径调整时只改 api 目录一个文件。配合 JSDoc 注释JS 文件也能在编辑器里获得基本的参数提示后面如果要把 api 层重构成 TypeScript函数签名已经固定迁移成本很低。4. uview-plus 接入与 Scss 主题体系4.1 easycom 规则组件怎么做到免 importuview-plus 是 uview 在 Vue3 下的升级版本组件命名规律延续了 u- 前缀。模板在 pages.json 的 easycom 节点里配置了组件自动引入规则页面里直接用组件标签不需要手动 import 和注册。{ easycom: { autoscan: true, custom: { ^u--(.*): uview-plus/components/u-$1/u-$1.vue, ^up-(.*): uview-plus/components/u-$1/u-$1.vue, ^u-([^-].*): uview-plus/components/u-$1/u-$1.vue } } }easycom 的匹配顺序是声明顺序优先匹配u-- 前缀对应新版组件up- 前缀对应部分重命名组件u- 兜底兼容旧写法。项目里如果有大量 u- 开头的自定义业务组件要小心被错误匹配到 uview-plus 目录下常见规避方法是在 custom 规则末尾追加自定义组件的精确路径。easycom 只作用于模板中的组件标签不会影响 script 里的显式导入。4.2 uni.scss 变量覆盖与品牌主题替换uni.scss 里定义的变量会自动注入每个 Vue 组件的样式上下文不需要手动 import。uview-plus 的主题色变量也定义在这份文件里改一处全部生效。变量名默认值作用范围$u-primary#3c9cff主按钮、选中态、链接色$u-success#5ac725成功提示、完成状态$u-warning#f9ae3d警告、待处理状态$u-error#f56c6c错误、删除操作// uni.scss $u-primary: #3c9cff; $u-warning: #f9ae3d; $u-success: #5ac725; $u-error: #f56c6c; $custom-navbar-height: 44px; $custom-page-bg: #f7f8fa;品牌色替换只需要覆盖 $u-primary 这一项所有 u-button、u-tag、u-switch 的主题色会同步变化。但 uni.scss 里的变量只能作用于样式代码script 中读取不到要做暗黑模式或动态换肤需要在 Pinia 里维护一套 CSS 变量表通过行内 style 绑定到根节点。uview-plus 的版本升级偶尔会调整变量命名锁版本号再升级是省事做法。4.3 custom-navbar 与页面样式的组织方式模板同时提供 styles 目录和页面内 style 片段前者放全局重置和工具类后者只承载当前页面的私有样式这是跨端项目里比较稳妥的分层。// styles/index.scss page { background-color: $custom-page-bg; font-size: 28rpx; color: #303133; } .flex-center { display: flex; align-items: center; justify-content: center; } .text-ellipsis { overflow: hidden; white-space: nowrap; text-overflow: ellipsis; }页面里优先使用 rpx 单位不同屏幕宽度下自动换算。custom-navbar 组件在计算状态栏高度时使用 uni.getSystemInfoSync()返回的 statusBarHeight 在 Android 和 iOS 上差异明显iPhone 刘海屏约 44pxAndroid 常见 24px 到 32px。把这个值缓存到模块级变量里比在每个页面重复获取更可靠也能避免组件反复触发同步方法导致性能抖动。5. Vue2 迁 Vue3 的报错点与 TypeScript 边界收口5.1 main.ts 入口与 Pinia 注入顺序Vue2 的 uniapp 项目迁到这套模板最多见的报错集中在入口和全局 API 变化。Vue3 版本要求 main.ts 导出 createApp 函数由框架内部调度不是直接 app.mount。// main.ts import { createSSRApp } from vue import * as Pinia from pinia import App from ./App.vue export function createApp() { const app createSSRApp(App) app.use(Pinia.createPinia()) return { app, Pinia } }如果页面里报 getActivePinia was called with no active Pinia基本就是 Pinia 实例没有随返回值交给框架。Vue2 的 Vue.prototype.xxx 全局挂载方式在 Vue3 里要改成 app.config.globalProperties或者用 Composition API 的 provide/inject 传递。5.2 声明文件补齐与类型兜底模板里有 env.d.ts 和 shims-uni.d.ts 两份声明文件前者给 Vite 环境变量提供类型后者弥补 .vue 模块的 TS 缺失。模板中 shime-uni.d.ts 是历史遗留拼写新项目建议统一命名为 shims-uni.d.ts。// shims-uni.d.ts declare module *.vue { import type { DefineComponent } from vue const component: DefineComponent{}, {}, any export default component }如果项目要通过 TypeScript 严格模式检查建议安装 dcloudio/types 并配置到 tsconfig.json 的 types 节点这样 uni.getSystemInfo 等 API 才有完整参数提示。declare const uni: any 这类兜底声明只用于过渡长期保留会让严格检查形同虚设。5.3 上架与真机调试前的配置清单检查项位置注意事项小程序 appidmanifest.json mp-weixin影响真机预览和上传定位权限文案app-plus 节点iOS 审核要求说明用途H5 定位域名公众号后台必须 HTTPS 且加白名单Android 权限裁剪manifest.json app-plus无用权限尽量关闭自定义分享onShareAppMessage返回 title 和 path小程序端实现自定义分享好友必须在每个页面显式声明 onShareAppMessage 并返回 title 和 path只靠全局配置覆盖不到全部页面。依赖版本建议去掉 package.json 里的 ^ 前缀锁死精确版本升级 uview-plus 时先升 uniapp 编译器再升组件库中间靠 uni.scss 的变量覆盖层做兼容过渡这样能最大程度减少升级带来的样式漂移。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门