# uni-app > 基于 `vue` 语法的 `全端开发框架` 官网: https://uniapp.dcloud.io/ ## 创建项目 https://uniapp.dcloud.net.cn/quickstart-cli.html vue 3: 1. 创建项目 ```sh npx degit 'dcloudio/uni-preset-vue#vite-ts' uni-shop # zsh中#不用引号包起来会报错 # 直接下载模板的话在 https://github.com/dcloudio/uni-preset-vue/tree/vite-ts pnpn i pnpm add -D sass ``` 2. 运行项目 `pnpm run dev:mp-weixin` 3. 使用微信小程序开发者工具来导入和预览项目 `dist/dev/mp-weixin` ==注意: 只能修改uni-app项目的文件, 不能修改编译后的小程序文件== 旧项目更新编译器主要依赖: ```sh npx @dcloudio/uvm@latest ``` ## 小程序的注意项 - 标签: div 和 ul 和 li 等改为 `view`、span 改为 `text`、a 改为 `navigator`、img 改为 `image` - 不能使用浏览器对象: 比如document、xmlhttp、cookie、window、location、navigator、localstorage、websql、indexdb、webgl等对象 - style 不需要设置 soped, 多页面样式互相隔离 - css 不能使用 \* 选择器, body的元素选择器请改为page - [微信小程序当前bug列表](https://developers.weixin.qq.com/community/develop/issueList?type=%E4%BF%AE%E5%A4%8D%E4%B8%AD&block=bug) ## 目录结构 ``` ├─pages 业务页面文件存放的目录 │ └─index │ └─index.vue index页面 ├─static 存放应用引用的本地静态资源的目录(注意:静态资源只能存放于此) ├─unpackage 非工程代码,一般存放运行或发行的编译结果 ├─index.html H5端页面 ├─main.js Vue初始化入口文件 ├─App.vue 配置App全局样式、监听应用生命周期 ├─pages.json 配置页面路由、导航栏、tabBar等页面类信息 ├─manifest.json 配置appid、应用名称、logo、版本等打包信息 └─uni.scss uni-app内置的常用样式变量 ``` ## pages.json 页面路由 > 对 uni-app 进行全局配置,决定页面文件的路径、窗口样式、原生的导航栏、底部的原生tabbar 等 https://uniapp.dcloud.net.cn/collocation/pages.html `src\pages.json` ```json { // https://uniapp.dcloud.io/collocation/pages // 页面路由 // pages数组中第一项表示应用启动页 "pages": [ { "path": "pages/index/index", // 页面样式 "style": { "navigationStyle": "custom", // 隐藏顶部默认导航 "navigationBarTitleText": "首页" } }, { "path": "pages/my/index", "style": { "navigationStyle": "custom", "navigationBarTitleText": "我的" } } ], // 全局样式 "globalStyle": { "navigationBarTextStyle": "black", "navigationBarTitleText": "uni-app", "navigationBarBackgroundColor": "#F8F8F8", "backgroundColor": "#F8F8F8" } } ``` ### 导航栏 ```json "navigationStyle": "custom", // 隐藏顶部默认导航 ``` https://ask.dcloud.net.cn/article/34921 ### easycom 组件自动导入 ```json "easycom": { // 是否开启自动扫描 "autoscan": true, // 以正则方式自定义组件匹配规则 "custom": { // uni-ui 规则如下配置 "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue", // 以 Jbc 开头的组件,在 components 文件夹中查找引入(需要重启服务器) "^Jbc(.*)": "@/components/Jbc$1.vue" } }, ``` ### 底部标签栏 https://uniapp.dcloud.net.cn/collocation/pages.html#tabbar ```json "tabBar": { "color": "#333", "selectedColor": "#18c7ff", "backgroundColor": "#fff", "borderStyle": "white", "spacing": "5px", "list": [ { "text": "首页", "pagePath": "pages/index/index", "iconPath": "static/tabs/home_default.png", "selectedIconPath": "static/tabs/home_selected.png" }, { "text": "我的", "pagePath": "pages/my/index", "iconPath": "static/tabs/user_default.png", "selectedIconPath": "static/tabs/user_selected.png" } ] }, ``` ## manifest.json 应用配置 > 例如修改 appid https://uniapp.dcloud.net.cn/collocation/manifest.html ``` src\manifest.json ``` 修改了项目的配置文件, 需要重启 最外面的appid, 是需要用到 Dcloud 云服务 (例如打包成 ios) 自动分配的, `mp-weixin => appid` 这里的才是微信小程序的 ## uni-app 生命周期 - 应用生命周期:与 **小程序** 应用的生命周期一致(onLaunch、onShow、onHide 等) https://uniapp.dcloud.net.cn/collocation/App.html#applifecycle - 页面生命周期:与 **小程序** 页面的生命周期一致(onLoad、onUnload、onShow 等) https://uniapp.dcloud.net.cn/tutorial/page.html#lifecycle - 组件生命周期:与 **Vue.js** 组件的生命周期一致(created、mounted等) https://uniapp.dcloud.net.cn/tutorial/page.html#componentlifecycle **页面组件优先使用小程序的生命周期钩子**,也就是 onShow、onHide 这些,普通组件就用 Vue 生命周期钩子 ### 页面生命周期 https://uniapp.dcloud.net.cn/tutorial/page.html#lifecycle ![|700x1115](https://img.081024.xyz/image-1744773925981.jpeg) 1. 首先根据pages.json的配置,创建页面 2. 根据页面template里的组件,创建静态dom 3. 触发onLoad 1. 比较适合的操作是:接受上页的参数,联网取数据,更新data 2. 不能操作 dom 4. 转场动画开始 5. 页面onReady 6. 转场动画结束 #### onShow和onHide a页面刚进入时,会触发a页面的onShow 当a跳转到b页面时,a会触发onHide,而b会触发onShow 但当b被关闭时,b会触发onUnload,此时a再次显示出现,会再次触发onShow 不同tab页面互相切换时,会触发各自的onShow和onHide #### onBackPress > 触发返回 ```js onBackPress(options) { console.log('from:' + options.from) } // from: // 'backbutton'——左上角导航栏按钮及安卓返回键; // 'navigateBack'——uni.navigateBack() 方法 ``` ### 组件生命周期 > 和 vue 一样 ## 页面和路由 https://uniapp.dcloud.net.cn/api/router.html#navigateto - `uni.navigateTo`: ===保留当前页面, 跳转到新页面===, 旧页面并没有被销毁, 而是放入页面栈 - `uni.navigateBack` 后退时不会触发 onLoad 而是 onShow - `uni.redirectTo` 销毁页面跳转到新页面 ## 页面通讯 `uni.$emit(eventName,OBJECT)` `uni.$on(eventName,callback)` `uni.$once(eventName,callback)` `uni.$off(eventName, callback)` ## ui 库 uview 不支持 vue 3, 使用 uni-app 自带组件 https://uniapp.dcloud.net.cn/component/ ### 内置组件 #### 视图容器 | 组件名 | 说明 | | ------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------- | | [view](https://uniapp.dcloud.net.cn/component/view.html) | 视图容器,类似于HTML中的div | | [scroll-view](https://uniapp.dcloud.net.cn/component/scroll-view.html) | 可滚动视图容器 | | [swiper](https://uniapp.dcloud.net.cn/component/swiper.html) | 滑块视图容器,比如用于轮播banner | | [match-media](https://uniapp.dcloud.net.cn/component/match-media.html) | 屏幕动态适配组件,比如窄屏上不显示某些内容 | | [movable-area](https://uniapp.dcloud.net.cn/component/movable-view.html#movable-area) | 可拖动区域 | | [movable-view](https://uniapp.dcloud.net.cn/component/movable-view.html#movable-view) | 可移动的视图容器,在页面中可以拖拽滑动或双指缩放。movable-view必须在movable-area组件中 | | [cover-view](https://uniapp.dcloud.net.cn/component/cover-view#cover-view) | 可覆盖在原生组件的上的文本组件 | | [cover-image](https://uniapp.dcloud.net.cn/component/cover-view#cover-image) | 可覆盖在原生组件的上的图片组件 | #### 基础内容 | 组件名 | 说明 | | ------------------------------------------------------------------ | -------------- | | [icon](https://uniapp.dcloud.net.cn/component/icon.html) | 图标 | | [text](https://uniapp.dcloud.net.cn/component/text.html) | 文字 | | [rich-text](https://uniapp.dcloud.net.cn/component/rich-text.html) | 富文本显示组件 | | [progress](https://uniapp.dcloud.net.cn/component/progress.html) | 进度条 | #### 表单组件 | 标签名 | 说明 | | ---------------------------------------------------------------------- | -------------------- | | [button](https://uniapp.dcloud.net.cn/component/button.html) | 按钮 | | [checkbox](https://uniapp.dcloud.net.cn/component/checkbox.html) | 多项选择器 | | [editor](https://uniapp.dcloud.net.cn/component/editor.html) | 富文本输入框 | | [form](https://uniapp.dcloud.net.cn/component/form.html) | 表单 | | [input](https://uniapp.dcloud.net.cn/component/input.html) | 输入框 | | [label](https://uniapp.dcloud.net.cn/component/label.html) | 标签 | | [picker](https://uniapp.dcloud.net.cn/component/picker.html) | 弹出式列表选择器 | | [picker-view](https://uniapp.dcloud.net.cn/component/picker-view.html) | 窗体内嵌式列表选择器 | | [radio](https://uniapp.dcloud.net.cn/component/radio.html) | 单项选择器 | | [slider](https://uniapp.dcloud.net.cn/component/slider.html) | 滑动选择器 | | [switch](https://uniapp.dcloud.net.cn/component/switch.html) | 开关选择器 | | [textarea](https://uniapp.dcloud.net.cn/component/textarea.html) | 多行文本输入框 | #### 路由与页面跳转 | 组件名 | 说明 | | ------------------------------------------------------------------ | ----------------------------- | | [navigator](https://uniapp.dcloud.net.cn/component/navigator.html) | 页面链接。类似于HTML中的a标签 | #### 媒体组件 | 组件名 | 说明 | | ---------------------------------------------------------------------- | ---------------------------- | | [audio](https://uniapp.dcloud.net.cn/component/audio.html) | 音频 | | [camera](https://uniapp.dcloud.net.cn/component/camera.html) | 相机 | | [image](https://uniapp.dcloud.net.cn/component/image.html) | 图片 | | [video](https://uniapp.dcloud.net.cn/component/video.html) | 视频 | | [live-player](https://uniapp.dcloud.net.cn/component/live-player.html) | 直播播放 | | [live-pusher](https://uniapp.dcloud.net.cn/component/live-pusher.html) | 实时音视频录制,也称直播推流 | ##### image - `mode`: - aspectFit: 保持纵横比缩放图片,使图片的长边能完全显示出来 - aspectFill : 保持纵横比缩放图片,只保证图片的短边能完全显示出来 - widthFix: 宽度不变,高度自动变化,保持原图宽高比不变 #### 地图 | 组件名 | 说明 | | ------------------------------------------------------ | ---- | | [map](https://uniapp.dcloud.net.cn/component/map.html) | 地图 | #### 画布 | 组件名 | 说明 | | ------------------------------------------------------------ | ---- | | [canvas](https://uniapp.dcloud.net.cn/component/canvas.html) | 画布 | #### webview | 组件名 | 说明 | | ---------------------------------------------------------------- | ------------- | | [web-view](https://uniapp.dcloud.net.cn/component/web-view.html) | web浏览器组件 | #### 广告 | 组件名 | 说明 | | -------------------------------------------------------------- | ------------------ | | [ad](https://uniapp.dcloud.net.cn/component/ad.html) | 广告组件 | | [ad-draw](https://uniapp.dcloud.net.cn/component/ad-draw.html) | 沉浸视频流广告组件 | #### 页面属性配置 | 组件名 | 说明 | | ---------------------------------------------------------------------------- | -------------------- | | [custom-tab-bar](https://uniapp.dcloud.net.cn/component/custom-tab-bar.html) | 底部tabbar自定义组件 | | [navigation-bar](https://uniapp.dcloud.net.cn/component/navigation-bar.html) | 页面顶部导航 | | [page-meta](https://uniapp.dcloud.net.cn/component/page-meta.html) | 页面属性配置节点 | #### uniCloud | 组件名 | 说明 | | ----------------------------------------------------------------- | ---------------------------- | | [unicloud-db组件](https://doc.dcloud.net.cn/uniCloud/unicloud-db) | uniCloud数据库访问和操作组件 | ### uni-ui > 扩展组件, 符合 easycom 标准, 会自动导入 ```sh # 安装 pnpm add @dcloudio/uni-ui ``` | 组件名 | 组件说明 | | --------------------- | ------------------------------------------------------------------------------- | | uni-badge | [数字角标](https://ext.dcloud.net.cn/plugin?name=uni-badge) | | uni-calendar | [日历](https://ext.dcloud.net.cn/plugin?name=uni-calendar) | | uni-card | [卡片](https://ext.dcloud.net.cn/plugin?name=uni-card) | | uni-collapse | [折叠面板](https://ext.dcloud.net.cn/plugin?name=uni-collapse) | | uni-combox | [组合框](https://ext.dcloud.net.cn/plugin?name=uni-combox) | | uni-countdown | [倒计时](https://ext.dcloud.net.cn/plugin?name=uni-countdown) | | uni-data-checkbox | [数据选择器](https://ext.dcloud.net.cn/plugin?name=uni-data-checkbox) | | uni-data-picker | [数据驱动的picker选择器](https://ext.dcloud.net.cn/plugin?name=uni-data-picker) | | uni-dateformat | [日期格式化](https://ext.dcloud.net.cn/plugin?name=uni-dateformat) | | uni-datetime-picker | [日期选择器](https://ext.dcloud.net.cn/plugin?name=uni-datetime-picker) | | uni-drawer | [抽屉](https://ext.dcloud.net.cn/plugin?name=uni-drawer) | | uni-easyinput | [增强输入框](https://ext.dcloud.net.cn/plugin?name=uni-easyinput) | | uni-fab | [悬浮按钮](https://ext.dcloud.net.cn/plugin?name=uni-fab) | | uni-fav | [收藏按钮](https://ext.dcloud.net.cn/plugin?name=uni-fav) | | uni-file-picker | [文件选择上传](https://ext.dcloud.net.cn/plugin?name=uni-file-picker) | | uni-forms | [表单](https://ext.dcloud.net.cn/plugin?name=uni-forms) | | uni-goods-nav | [商品导航](https://ext.dcloud.net.cn/plugin?name=uni-goods-nav) | | uni-grid | [宫格](https://ext.dcloud.net.cn/plugin?name=uni-grid) | | uni-group | [分组](https://ext.dcloud.net.cn/plugin?name=uni-group) | | uni-icons | [图标](https://ext.dcloud.net.cn/plugin?name=uni-icons) | | uni-indexed-list | [索引列表](https://ext.dcloud.net.cn/plugin?name=uni-indexed-list) | | uni-link | [超链接](https://ext.dcloud.net.cn/plugin?name=uni-link) | | uni-list | [列表](https://ext.dcloud.net.cn/plugin?name=uni-list) | | uni-load-more | [加载更多](https://ext.dcloud.net.cn/plugin?name=uni-load-more) | | uni-nav-bar | [自定义导航栏](https://ext.dcloud.net.cn/plugin?name=uni-nav-bar) | | uni-notice-bar | [通告栏](https://ext.dcloud.net.cn/plugin?name=uni-notice-bar) | | uni-number-box | [数字输入框](https://ext.dcloud.net.cn/plugin?name=uni-number-box) | | uni-pagination | [分页器](https://ext.dcloud.net.cn/plugin?name=uni-pagination) | | uni-popup | [弹出层](https://ext.dcloud.net.cn/plugin?name=uni-popup) | | uni-rate | [评分](https://ext.dcloud.net.cn/plugin?name=uni-rate) | | uni-row | [布局-行](https://ext.dcloud.net.cn/plugin?name=uni-row) | | uni-search-bar | [搜索栏](https://ext.dcloud.net.cn/plugin?name=uni-search-bar) | | uni-segmented-control | [分段器](https://ext.dcloud.net.cn/plugin?name=uni-segmented-control) | | uni-steps | [步骤条](https://ext.dcloud.net.cn/plugin?name=uni-steps) | | uni-swipe-action | [滑动操作](https://ext.dcloud.net.cn/plugin?name=uni-swipe-action) | | uni-swiper-dot | [轮播图指示点](https://ext.dcloud.net.cn/plugin?name=uni-swiper-dot) | | uni-table | [表格](https://ext.dcloud.net.cn/plugin?name=uni-table) | | uni-tag | [标签](https://ext.dcloud.net.cn/plugin?name=uni-tag) | | uni-title | [章节标题](https://ext.dcloud.net.cn/plugin?name=uni-title) | | uni-transition | [过渡动画](https://ext.dcloud.net.cn/plugin?name=uni-transition) | ## uni-api - 出现目的 1. 很多的原生的小程序的api 不支持 promise `wx.request` 2. 想要做一个跨平台 应用 那么 发送请求 该用哪一代码来发送 3. axios 针对 网页的!!! 4. wx.request 只针对 微信小程序 5. 自己去封装一个api 可以去兼容适配所有的客户端 ``` if (微信小程序) wx.request if (web) axios if (支付宝) ... ``` - 如何使用 ```js uni.request ``` ## 语法 1. 标签优先使用 uni-app 的 2. js语法和vue一样 3. 特有的api: `uni.request`, `uni.showToast` 4. 生命周期参考上面的 ### css 单位 rpx - 以 750 px 宽的屏幕为基准,750rpx 恰好为屏幕宽度 - 设计师可以用 iPhone6 作为视觉稿的标准, 对应 750rpx ## 分包优化 https://uniapp.dcloud.net.cn/collocation/manifest.html#关于分包优化的说明 ## 项目实战 视频: https://www.bilibili.com/video/BV1Bp4y1379L 笔记: https://gitee.com/Megasu/uni-app-shop-note 高星框架: https://github.com/codercup/unibest ### 框架搭建 #### vscode 插件 `.vscode/extensions.json` ```json { // 推荐的扩展插件 "recommendations": [ "mrmaoddxxaa.create-uniapp-view", // 创建 uni-app 页面 "uni-helper.uni-helper-vscode", // uni-app 代码提示 "evils.uniapp-vscode", // uni-app 文档 "vue.volar", // vue3 语法支持 "editorconfig.editorconfig", // editorconfig "dbaeumer.vscode-eslint", // eslint "esbenp.prettier-vscode" // prettier ] } ``` #### create-uniapp-view 设置 设置: 创建同名文件夹、不使用 scoped、预编译器 scss、vue 3 #### 类型提示 > uni-app 类型, 由第三方 uni-helper 提供 ```sh pnpm add @uni-helper/uni-app-types @uni-helper/uni-ui-types -D ``` > 微信小程序的类型提示 ```sh pnpm add miniprogram-api-typings -D ``` > nodejs ```sh pnpm add @types/node -D ``` 在 `tsconfig.json` 的 types 中添加 ```json { //... "compilerOptions": { //... "types": [ "@dcloudio/types", "miniprogram-api-typings", "@uni-helper/uni-app-types", "@uni-helper/uni-ui-types" ] }, //... "vueCompilerOptions": { "plugins": ["@uni-helper/uni-app-types/volar-plugin"] } } ``` - TypeScript 默认设置 typeRoots 包含了 `node_modules/@types`, 以 `@types` 开头的包不需要手动添加 #### vscode 设置 `.vscode/settings.json` ```json { // 在保存时格式化文件 "editor.formatOnSave": true, // 文件格式化配置 "[json]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, // 控制资源管理器是否应以紧凑形式呈现文件夹。单个子文件夹将被压缩在组合的树元素中。 "explorer.compactFolders": false, // 配置 vue 文件的默认格式化工具为 prettier "[vue]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, // 配置 html 文件的默认格式化工具为 prettier "[html]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, // 配置 ts 文件的默认格式化工具为 prettier "[typescript]": { "editor.defaultFormatter": "esbenp.prettier-vscode" }, // 配置语言的文件关联 "files.associations": { "pages.json": "jsonc", // pages.json 可以写注释 "manifest.json": "jsonc" // manifest.json 可以写注释 } } ``` #### prettierrc ```sh pnpm add 'prettier@^3' '@vue/eslint-config-prettier@^9' -D ``` `.prettierrc.json` ```json { "singleQuote": true, "semi": false, "printWidth": 120, "trailingComma": "all", "endOfLine": "auto" } ``` #### eslint ```sh pnpm add 'eslint@^8' 'eslint-plugin-vue@^9' '@vue/eslint-config-typescript@^11' '@rushstack/eslint-patch@^1' -D ``` `.eslintrc.cjs` ```json /* eslint-env node */ require('@rushstack/eslint-patch/modern-module-resolution') module.exports = { root: true, extends: [ 'plugin:vue/vue3-essential', 'eslint:recommended', '@vue/eslint-config-typescript', '@vue/eslint-config-prettier', ], // 小程序全局变量 globals: { uni: true, wx: true, WechatMiniprogram: true, getCurrentPages: true, UniApp: true, UniHelper: true, Page: true, AnyObject: true, }, parserOptions: { ecmaVersion: 'latest', }, rules: { 'prettier/prettier': [ 'warn', { singleQuote: true, semi: false, printWidth: 100, trailingComma: 'all', endOfLine: 'auto', }, ], 'vue/multi-word-component-names': ['off'], 'vue/no-setup-props-destructure': ['off'], 'vue/no-deprecated-html-element-is': ['off'], '@typescript-eslint/no-unused-vars': ['off'], }, } ``` #### editorconfig `.editorconfig` ```ini # editorconfig.org root = true [*] indent_style = space indent_size = 2 end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.md] trim_trailing_whitespace = false ``` #### husky > 提交前格式校验 ```sh pnpm add husky lint-staged -D ``` `.husky/pre-commit` ```sh #!/usr/bin/env sh . "$(dirname -- "$0")/_/husky.sh" npm run lint-staged ``` `package.json` ```json { // ... "scripts": { // ... "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", "prepare": "husky install", "lint-staged": "lint-staged" }, "lint-staged": { "*.{js,ts,vue}": ["eslint --fix"] } //... } ``` ### 修改appid 微信公众号平台: https://mp.weixin.qq.com/ 注册 --> 管理 --> 开发管理 --> AppID(小程序ID) 可以修改 `src/manifest.json` 中的 `mp-weixin => appid`, 或者直接在模拟器的详情中修改 ### pinia 持久化 ```sh pnpm add 'pinia@^2' 'pinia-plugin-persistedstate@^3' ``` - `pinia-plugin-persistedstate` 默认使用 `localStorage`, 需要改为 uni app 的 `uni.setStorageSync()` 和 `uni.getStorageSync()` - https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/config.html#storage - pinia 3 暂时不兼容: https://github.com/dcloudio/uni-app/issues/5326 `src/stores/index.ts` ```ts import { createPinia } from 'pinia' import persist from 'pinia-plugin-persistedstate' // 创建 pinia 实例 const pinia = createPinia() // 使用持久化存储插件 pinia.use(persist) // 默认导出,给 main.ts 使用 export default pinia ``` `main.ts` ```ts // 导入 pinia 实例 import pinia from './stores/' // ... // 在 return 前添加 // 使用 pinia app.use(pinia) ``` `stores/modules/member.ts` - 小程序没有 localStorage ```ts export const useMemberStore = defineStore( 'member', () => { //…省略 }, { // 配置持久化 persist: { // 调整为兼容多端的API storage: { setItem(key, value) { uni.setStorageSync(key, value) // [!code warning] }, getItem(key) { return uni.getStorageSync(key) // [!code warning] }, }, }, }, ) ``` ### 拦截器 > 应用场景: 给请求 `request` 添加基础地址、超时时间、请求头标识、token > 文件上传 `uploadFile` 同样添加 https://uniapp.dcloud.net.cn/api/interceptor.html#addinterceptor - 添加拦截器: `uni.addInterceptor` ```js uni.request({ url: 'request/login', //仅为示例,并非真实接口地址。 success: (res) => { console.log(res.data) // 打印: {code:1,...} }, }) uni.addInterceptor('request', { invoke(args) { // 拦截前触发 args.url = 'https://www.example.com/' + args.url }, success(args) { // 成功回调拦截 args.data.code = 1 }, fail(err) { // 失败回调拦截 console.log('interceptor-fail', err) }, complete(res) { // 完成回调拦截 console.log('interceptor-complete', res) }, }) uni.addInterceptor({ returnValue(args) { // 方法调用后触发,处理返回值 return args.data }, }) ``` - 删除拦截器 `uni.removeInterceptor` ```js uni.removeInterceptor('request') ``` ### 响应封装 - 成功: - 提取 res. data - 添加类型支持 - 失败 - 网络错误: 提示换网络 - 401: 清理用户信息, 跳转登录页 - 其他错误: 轻提示 注意项: - uni-app 的 request 只有网络错误才会走 fail, 权限不足等后端有响应的, 都是 success. 需要通过 `res.statusCode` 状态码进一步控制 ### 自定义导航栏 https://ask.dcloud.net.cn/article/34921 `src/pages/index/components/CustomNavbar.vue` 样式适配: 安全区域 (例如刘海屏) ```ts // 获取屏幕边界到安全区域距离 const { safeAreaInsets } = uni.getSystemInfoSync() ``` 用来设置 paddingTop ```vue ``` ### 轮播图通用组件 #### 配置 easycom 自动导入 `page.json` ```json "easycom": { // 是否开启自动扫描 "autoscan": true, // 以正则方式自定义组件匹配规则 "custom": { // ... // 以 Jbc 开头的组件,在 components 文件夹中查找引入(需要重启服务器) "^Jbc(.*)": "@/components/Jbc$1.vue" } }, ``` #### 组件 `src/components/JbcSwiper.vue` 使用: `swiper` 滑动视图容器 https://uniapp.dcloud.net.cn/component/swiper.html 在 `onload` 钩子中调用 api - `swiper` 绑定 `change` 事件的类型: `UniHelper.SwiperOnChange` ```ts const handleChange: UniHelper.SwiperOnChange = (e) => { activeIndex.value = e.detail!.current } ``` #### 类型提示 `src/types/components.d.ts` ```ts import JbcSwiper from '@/components/JbcSwiper.vue' declare module 'vue' { export interface GlobalComponents { JbcSwiper: typeof JbcSwiper } } // 组件实例类型 (typeof xxx组件 获取到的是构造函数类型) export type JbcSwiperInstance = InstanceType ``` - declare module 会与现有模块类型合并,而不是覆盖 #### api 封装 `src/services/home.ts` - `uni.request()` : 当请求类型为 get 时, 把 data 里的键值对转化为 query 参数 ### 首页分类组件 略 ### 热门推荐 略 ### 猜你喜欢通用模块 参考: `JbcSwiper.vue` 首页添加滚动组件: - `scroll-view` 组件: - @scrolltolower: 滚动到底触发 父组件调用子组件的方法: - 子组件通过 `defineExpose({methodName})` 暴露方法 - 父组件给组件绑定 `ref="xxx"`, 通过 `xxx.value.methodName()` - 子组件的类型为自定义组件实例类型 `JbcGuessInstance` 滚动到底时调用猜你喜欢模块获取下页数据插入数组 ### 首页下拉刷新 - `scroll-view` 组件: - refresher-enabled: 开启自定义下拉刷新 - @refresherrefresh: 自定义下拉刷新被触发 - refresher-triggered: 设置当前下拉刷新状态 - enable-back-to-top: iOS点击顶部状态栏、安卓双击标题栏时,滚动条返回顶部,只支持竖向 刷新时, 多个 api 用 `Promise.allSettled()` bug 修复: 下拉刷新后轮播图指示器错乱 原因: - 每次刷新, 获取的轮播图数据都会随机打乱, 用 id 作为 key 导致 dom 更新异常改为 `:key="item.id + index"` - `watch` 监听 `props.list` 的变化, 复位 `activeIndex.value=0` ### 首页骨架屏 - 在微信开发者工具模拟器右下角三个小点的菜单里, 有生成骨架屏选项 - 复制 `index.skeleton.wxml` 到 template, 复制 `index.skeleton.wxss` 到 style - 自定义导航栏不需要加载数据, 只保留滚动容器里面的部分 ### 热门推荐详情页 - uni-app 获取路由参数 ```ts // uni-app获取路由参数 const query = defineProps<{ type: string }>() console.log(query) ``` - uni-app 动态设置标题 ```ts // 动态设置标题 uni.setNavigationBarTitle({ title: '标题名' }) ``` - 判断当前是不是开发环境, 方便设置模拟数据 ```ts { page: import.meta.env.DEV ? 33 : 1, pageSize: 10, } ``` ### 商品分类页 - 小程序开发者工具直接显示分类页, 免去点击跳转: 点击'普通编译 -> 添加编译模式 -> 选择要启动页面为要编译的页面' - 切换分类时, 滚动条不恢复到顶部: ```ts // 切换分类时,滚动到顶部 const scrollTop = ref(0) // scrollTop绑定到 scroll-view 的 scroll-top 属性上 // 记录滚动位置, 保存到另一个变量 // 切换分页的时候,先还原scrollTop的旧值,然后在视图渲染后,再设置scrollTop等于0触发还原 const scroolTopOld = ref(0) type ScrollEvent = { detail: { scrollTop: number scrollLeft: number scrollHeight: number scrollWidth: number deltaX: number deltaY: number } [key: string]: any } const handlerScrool = (e: ScrollEvent) => { scroolTopOld.value = e.detail.scrollTop } watch(activeIndex, () => { scrollTop.value = scroolTopOld.value nextTick(() => { scrollTop.value = 0 }) }) ``` ### 商品详情页 - 图标: 使用阿里巴巴字体图标 https://www.iconfont.cn/ 字体可以上传到 cdn, `iconfont.css` 需要修改 `font-face` ```css @font-face { font-family: 'iconfont'; /* Project id */ src: url('iconfont.ttf?t=1745547896290') format('truetype'); } ``` - 操作栏有按钮, 需要设置安全区域 (可能有小白条) 防止误操作 ```vue ``` - 弹出层组件 `uni-popup` https://uniapp.dcloud.net.cn/component/uniui/uni-popup.html 属性: - type: 弹出方式, top center bottom left right message dialog share - background-color: 主窗口背景色 - 先用 ref 绑定 `uni-popup`, 再调用 ref 的 open 和 close 方法控制是否显示 类型定义: `UniHelper.UniPopupInstance` 类型里的 `open()` 和 `close()` 是可选的, 会导致 `popup.value?.open()` 报错, 需要使用交叉类型 ```ts const popup = ref< UniHelper.UniPopupInstance & { open: (type?: UniHelper.UniPopupType) => void close: () => void } >() ``` - `NonNullable`: 去除类型里的 null 和 undefined ### 登录模块 #### 快捷登录 - 登陆流程 ![|700x710](https://img.081024.xyz/image-1745582023800.jpeg) - 获取用户凭证 `code` ===有效期五分钟===。开发者需要在开发者服务器后台调用 code2Session,使用 code 换取 openid、unionid、session_key 等信息 不能在 `getphonenumber` 里调用, 在 `onload` 里 ```ts wx.login(Object object) ``` https://developers.weixin.qq.com/miniprogram/dev/api/open-api/login/wx.login.html - 获取手机号 (小程序有隐私保护, 需要用户同意, 且个人开发者没法调用) `button` 需要设置 `open-type=getPhoneNumber`, 然后在 `@getphonenumber` 回调中拿到手机号 https://uniapp.dcloud.net.cn/component/button.html#button https://developers.weixin.qq.com/miniprogram/dev/framework/open-ability/getPhoneNumber.html ### 个人中心 - 封装组合式函数 `src/composables/index.ts` > 函数命名以 use 开头, 放 composables 文件夹下 ```ts import type { JbcGuessInstance } from '@/types/components' import { ref } from 'vue' export const useGuessList = () => { const guessRef = ref() const onScrollToLower = () => { guessRef.value?.getMore() } return { guessRef, onScrollToLower } } ``` 调用: ```ts import { useGuessList } from '@/composables' const { guessRef, onScrollToLower } = useGuessList() ``` - 设置页分包 > 把不常用的页面拆成另外的包, 当某些步骤可能需要打开时, 提前预加载 存放目录: 和 `pages` 同级, 例如 `pagesMember/settings/settings.vue` 在 `pages.json` 中添加 `subPackages`, 和 `pages` 同级 ```json // 分包 "subPackages": [ { // 子包的根目录 "root": "pagesMember", // pages参数和主包一样 "pages": [ { "path": "settings/settings", "style": { "navigationBarTitleText": "设置" } } ] } ] ``` - 设置预下载 `page.json` > key 是页面路径 https://uniapp.dcloud.net.cn/collocation/pages.html#preloadrule ```json // 预下载 "preloadRule": { "pages/my/my": { "network": "all", "packages": ["pagesMember"] } } ``` ### 设置页面 - 显示模态框: `uni.showModal()` - 路由后退: `uni.navigateBack()` ### 修改用户信息 - `Pick` : 从类型中选取指定属性 - `picker` 选择器 - 时间选择器: `mode = time` - 省市区选择器: `mode = region` https://uniapp.dcloud.net.cn/component/picker.html - `radio` 选择器 https://uniapp.dcloud.net.cn/component/radio.html - 修改头像: 调用拍照 api `uni.chooseMedia` - 上传头像: `uni.uploadFile` ### 收货地址 #### 地址表单 - 切换开关 `switch` 组件 https://uniapp.dcloud.net.cn/component/switch.html - `uni.navigateBack`, 触发的是 onShow 事件, 要把请求列表放到 onShow 而不是 onLoad #### 数据校验 - 表单校验: 使用 `` 组件 - 表单绑定 `ref modelValue rules` - 表单项设置 `label name` - 原生 checkbox 或三方组件不支持 v-model 等, `onFieldChange` - 提交前再调用 formRef 的 `.validate()`, 返回的是 Promise https://uniapp.dcloud.net.cn/component/uniui/uni-forms.html ```ts // 表单验证规则 const rules: UniFormsRules = { receiver: { rules: [ { required: true, errorMessage: '请输入收货人姓名' }, { minLength: 2, maxLength: 5, errorMessage: '姓名长度在 {minLength} 到 {maxLength} 个字符', }, ], }, contact: { rules: [ { required: true, errorMessage: '请输入联系电话' }, { pattern: /^1[3-9]\d{9}$/, errorMessage: '手机号码为11位数字', }, ], }, fullLocation: { rules: [{ required: true, errorMessage: '请选择省市区' }], }, address: { rules: [ { required: true, errorMessage: '请输入详细地址' }, { minLength: 2, maxLength: 20, errorMessage: '详细地址长度在 {minLength} 到 {maxLength} 个字符', }, ], }, } ``` #### 删除功能 - 删除地址: 侧划组件 `uni-swipe-action` https://uniapp.dcloud.net.cn/component/uniui/uni-swipe-action.html ### SKU 模块 - 最小存货单位 - SKU 算法: https://juejin.cn/post/7002746459456176158 - 插件市场: https://ext.dcloud.net.cn/ - 插件下载: https://gitee.com/vk-uni/vk-u-goods-sku-popup #### 类型定义 创建 `vk-data-goods-sku-popup.d.ts`, 放在 `vk-data-goods-sku-popup` 目录下 ```ts import { Component } from '@uni-helper/uni-app-types' /** SKU 弹出层 */ export type SkuPopup = Component /** SKU 弹出层实例 */ export type SkuPopupInstance = InstanceType /** SKU 弹出层属性 */ export type SkuPopupProps = { /** 双向绑定,true 为打开组件,false 为关闭组件 */ modelValue: boolean /** 商品信息本地数据源 */ localdata: SkuPopupLocaldata /** 按钮模式 1:都显示 2:只显示购物车 3:只显示立即购买 */ mode?: 1 | 2 | 3 /** 该商品已抢完时的按钮文字 */ noStockText?: string /** 库存文字 */ stockText?: string /** 点击遮罩是否关闭组件 */ maskCloseAble?: boolean /** 顶部圆角值 */ borderRadius?: string | number /** 最小购买数量 */ minBuyNum?: number /** 最大购买数量 */ maxBuyNum?: number /** 每次点击后的数量 */ stepBuyNum?: number /** 是否只能输入 step 的倍数 */ stepStrictly?: boolean /** 是否隐藏库存的显示 */ hideStock?: false /** 主题风格 */ theme?: 'default' | 'red-black' | 'black-white' | 'coffee' | 'green' /** 默认金额会除以100(即100=1元),若设置为0,则不会除以100(即1=1元) */ amountType?: 1 | 0 /** 自定义获取商品信息的函数(已知支付宝不支持,支付宝请改用localdata属性) */ customAction?: () => void /** 是否显示右上角关闭按钮 */ showClose?: boolean /** 关闭按钮的图片地址 */ closeImage?: string /** 价格的字体颜色 */ priceColor?: string /** 立即购买 - 按钮的文字 */ buyNowText?: string /** 立即购买 - 按钮的字体颜色 */ buyNowColor?: string /** 立即购买 - 按钮的背景颜色 */ buyNowBackgroundColor?: string /** 加入购物车 - 按钮的文字 */ addCartText?: string /** 加入购物车 - 按钮的字体颜色 */ addCartColor?: string /** 加入购物车 - 按钮的背景颜色 */ addCartBackgroundColor?: string /** 商品缩略图背景颜色 */ goodsThumbBackgroundColor?: string /** 样式 - 不可点击时,按钮的样式 */ disableStyle?: object /** 样式 - 按钮点击时的样式 */ activedStyle?: object /** 样式 - 按钮常态的样式 */ btnStyle?: object /** 字段名 - 商品表id的字段名 */ goodsIdName?: string /** 字段名 - sku表id的字段名 */ skuIdName?: string /** 字段名 - 商品对应的sku列表的字段名 */ skuListName?: string /** 字段名 - 商品规格名称的字段名 */ specListName?: string /** 字段名 - sku库存的字段名 */ stockName?: string /** 字段名 - sku组合路径的字段名 */ skuArrName?: string /** 字段名 - 商品缩略图字段名(未选择sku时) */ goodsThumbName?: string /** 被选中的值 */ selectArr?: string[] /** 打开弹出层 */ onOpen: () => void /** 关闭弹出层 */ onClose: () => void /** 点击加入购物车时(需选择完SKU才会触发)*/ onAddCart: (event: SkuPopupEvent) => void /** 点击立即购买时(需选择完SKU才会触发)*/ onBuyNow: (event: SkuPopupEvent) => void } /** 商品信息本地数据源 */ export type SkuPopupLocaldata = { /** 商品 ID */ _id: string /** 商品名称 */ name: string /** 商品图片 */ goods_thumb: string /** 商品规格列表 */ spec_list: SkuPopupSpecItem[] /** 商品SKU列表 */ sku_list: SkuPopupSkuItem[] } /** 商品规格名称的集合 */ export type SkuPopupSpecItem = { /** 规格名称 */ name: string /** 规格集合 */ list: { name: string }[] } /** 商品SKU列表 */ export type SkuPopupSkuItem = { /** SKU ID */ _id: string /** 商品 ID */ goods_id: string /** 商品名称 */ goods_name: string /** 商品图片 */ image: string /** SKU 价格 * 100, 注意:需要乘以 100 */ price: number /** SKU 规格组成, 注意:需要与 spec_list 数组顺序对应 */ sku_name_arr: string[] /** SKU 库存 */ stock: number } /** 当前选择的sku数据 */ export type SkuPopupEvent = SkuPopupSkuItem & { /** 商品购买数量 */ buy_num: number } /** 全局组件类型声明 */ declare module 'vue' { export interface GlobalComponents { 'vk-data-goods-sku-popup': SkuPopup } } ``` ### 购物车 - 步进器组件 `vk-data-input-number-box.vue` - 步进器组件类型定义: `vk-data-input-number-box.d.ts` ```ts import { Component } from '@uni-helper/uni-app-types' /** 步进器 */ export type InputNumberBox = Component /** 步进器实例 */ export type InputNumberBoxInstance = InstanceType /** 步进器属性 */ export type InputNumberBoxProps = { /** 输入框初始值(默认1) */ modelValue: number /** 用户可输入的最小值(默认0) */ min: number /** 用户可输入的最大值(默认99999) */ max: number /** 步长,每次加或减的值(默认1) */ step: number /** 是否禁用操作,包括输入框,加减按钮 */ disabled: boolean /** 输入框宽度,单位rpx(默认80) */ inputWidth: string | number /** 输入框和按钮的高度,单位rpx(默认50) */ inputHeight: string | number /** 输入框和按钮的背景颜色(默认#F2F3F5) */ bgColor: string /** 步进器标识符 */ index: string /** 输入框内容发生变化时触发 */ change: (event: InputNumberBoxEvent) => void /** 输入框失去焦点时触发 */ blur: (event: InputNumberBoxEvent) => void /** 点击增加按钮时触发 */ plus: (event: InputNumberBoxEvent) => void /** 点击减少按钮时触发 */ minus: (event: InputNumberBoxEvent) => void } /** 步进器事件对象 */ export type InputNumberBoxEvent = { /** 输入框当前值 */ value: number /** 步进器标识符 */ index: string } /** 全局组件类型声明 */ declare module 'vue' { export interface GlobalComponents { 'vk-data-input-number-box': InputNumberBox } } ``` - 购物车有两个, 一个页底部导航栏的, 一个是商品页面按钮的 ### 订单页 - 送货时间: `picker` 自定义选择列表 - 商品页跳转快速下单: 小程序开发者工具左下角可以看到页面参数 - 跳转订单详情页使用 `uni.redirectTo` ### 订单详情页 #### 自定义导航栏 - 自定义导航栏设置左上角按钮 - 获取当前页面栈 : https://developers.weixin.qq.com/miniprogram/dev/reference/api/getCurrentPages.html - `getCurrentPages()` 最后一个就是当前页面 #### 滚动时切换 title - 向下滚动时切换 title - 滚动驱动的动画: https://developers.weixin.qq.com/miniprogram/dev/framework/view/animation.html#滚动驱动的动画 - 获取当前页面的实例 - 在页面渲染完毕 `onReady` 生命周期执行 ```ts onReady(() => { // 动画效果: 导航栏背景色 pageInstance.animate( '.navbar', [ { backgroundColor: 'transparent', }, { backgroundColor: '#f8f8f8', }, ], 1000, { scrollSource: '#scroller', timeRange: 1000, startScrollOffset: 0, endScrollOffset: 50, }, ) // 动画效果: 导航栏标题 pageInstance.animate( '.navbar .title', [ { color: 'transparent', }, { color: '#000', }, ], 1000, { scrollSource: '#scroller', timeRange: 1000, startScrollOffset: 0, endScrollOffset: 50, }, ) // 动画效果: 返回按钮 pageInstance.animate( '.navbar .back', [ { color: '#fff', }, { color: '#000', }, ], 1000, { scrollSource: '#scroller', timeRange: 1000, startScrollOffset: 0, endScrollOffset: 50, }, ) }) ``` #### 支付 - 支付倒计时: `uni-countdown` https://uniapp.dcloud.net.cn/component/uniui/uni-countdown.html - 去支付 - 生产环境: 1. 需要通过认证的应用对应的 appid https://uniapp.dcloud.net.cn/tutorial/app-payment-weixin.html#%E5%BC%80%E9%80%9A 2. 提交资料申请开通获得商户号 `PartnerID` 和密码, 置APIv2密钥 3. 服务器接入 https://pay.weixin.qq.com/doc/v3/merchant/4012791911 4. 流程: ![|700x537](https://img.081024.xyz/image-1746845896307.jpeg) - 开发环境: - 模拟支付 - 微信支付参数的返回类型: `WechatMiniprogram.RequestPaymentOption`, 给 `wx.requestPayment` 调用 - 复制剪切板: `uni.setClipboardData` ### 订单列表页 todo: 5 个不同类型的列表并发太慢 ### 小程序打包 1. 编译 ```sh pnpm build:mp-weixin ``` 2. 开发者工具: 点击上传 1. 或者使用 ci: https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html 3. 公众号平台提交审核 https://mp.weixin.qq.com #### 条件编译 > 是通过注释实现 https://uniapp.dcloud.net.cn/tutorial/platform.html#preprocessor - 以 `#ifdef` 或 `#ifndef` 加 `%PLATFORM%` 开头,以 `#endif` 结尾。 - `#ifdef`:if defined 仅在某平台存在 - `#ifndef`:if not defined 除了某平台均存在 - `%PLATFORM%`:平台名称 | 条件编译写法 | 说明 | | ------------------------------------------------------------ | --------------------------------------------------------------------------------- | | `#ifdef APP-PLUS`
需条件编译的代码
`#endif ` | 仅出现在 App 平台下的代码 | | `#ifndef H5`
需条件编译的代码
`#endif` | 除了 H5 平台,其它平台均存在的代码(注意if后面有个n) | | ` #ifdef H5 丨丨 MP-WEIXIN`
需条件编译的代码
`#endif` | 在 H5 平台或微信小程序平台存在的代码(这里只有 \|\|,不可能出现&&,因为没有交集) | - 支持的文件: - .vue/.nvue/.uvue - .js/.uts - .css - pages.json - 各预编译语言文件,如:.scss、.less、.stylus、.ts、.pug **注意:**  1. 样式的条件编译,无论是 css 还是 sass/scss/less/stylus 等预编译语言中,必须使用 `/*注释*/` 的写法。 2. 通过 import 导入的 scss 中的条件编译似乎不生效 #### h5 预览 - 相对路径 `manifest.json` ```json /* H5特有相关 */ "h5": { "router": { "base": "./" } }, ``` #### 安卓打包 调试: - 需要 `HBuilderX` - 安装运行插件和基座 - 调试手机开启开发者工具 云打包: - 注册 DCloud 账号 - 获取 DCloud AppID: 在 HBuilderX 中打开 `manifest.json` - 设置应用名称图标 - 发行-云打包-使用云端证书 #### ios 打包 调试: - 需要mac - 安装 xcode - 运行到模拟器 打包: - 需要苹果开发者账号和年费 #### 样式兼容 - 小程序不支持 \* 选择器 - 页面视口差异 - 小程序: 分tabBar 页和普通页 - 在 tabBar 页中, 视口不包括顶部和底部 - 在普通页中, 视口不包括顶部 - h 5: 视口包括浏览器整个区域 - H 5 端默认开启 scoped - uni-app 跨端注意 - uni-app css 支持 - uni-app 条件编译 实际遇到的问题: - h5 中的顶栏和底栏都是 div 模拟的, 这样会有遮住的问题 uni-app 内置的 css 变量: https://uniapp.dcloud.net.cn/tutorial/syntax-css.html#css-%E5%8F%98%E9%87%8F | CSS 变量 | 描述 | App | 小程序 | H5 | | ------------------- | ---------------------- | -------------------------------------------------------------------------------------------------------------------- | ------ | -------------------- | | --status-bar-height | 系统状态栏高度 | [系统状态栏高度](http://www.html5plus.org/doc/zh_cn/navigator.html#plus.navigator.getStatusbarHeight)、nvue 注意见下 | 25px | 0 | | --window-top | 内容区域距离顶部的距离 | 0 | 0 | NavigationBar 的高度 | | --window-bottom | 内容区域距离底部的距离 | 0 | 0 | TabBar 的高度 | 示例: ```scss /* 吸底工具栏 */ .toolbar { /* ... */ bottom: calc(var(--window-bottom)); /* ... */ } ``` - 骨架屏样式错位 原因: h5 样式隔离 具体点: 骨架屏是由小程序开发工具生成的, 自动生成的样式和 h5 样式隔离后的 class 对应不上 解决方法: 把骨架屏里对应的子组件样式单独拆分, 然后在骨架屏中条件编译导入, 搜索 `is=` 对应的组件, 就是需要处理的 - App flex 布局不生效: App 没有 page 元素, 只有 `#app` #### 组件兼容 小程序特有的: - `open-type=`, 例外: `navigate` `switchTab` `redirect` - `wx.` - `.animate` 动画效果 - `uni-navigator`: 在 h5 中实现, 实际在外层嵌套了一个 a 标签, 可能导致原来的样式失效 - 解决方法: 添加属性 `:render-link="false"` #### API 兼容 - 选择头像的 `uni.chooseMedia` 只支持小程序, 可以使用停止维护的 api `uni.chooseImage`