50 KiB
uni-app
基于
vue
语法的全端开发框架
创建项目
https://uniapp.dcloud.net.cn/quickstart-cli.html
vue 3:
- 创建项目
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
- 运行项目
pnpm run dev:mp-weixin
- 使用微信小程序开发者工具来导入和预览项目
dist/dev/mp-weixin
==注意: 只能修改uni-app项目的文件, 不能修改编译后的小程序文件==
旧项目更新编译器主要依赖:
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列表
目录结构
├─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
{
// 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"
}
}
导航栏
"navigationStyle": "custom", // 隐藏顶部默认导航
https://ask.dcloud.net.cn/article/34921
easycom 组件自动导入
"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
"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
- 首先根据pages.json的配置,创建页面
- 根据页面template里的组件,创建静态dom
- 触发onLoad
- 比较适合的操作是:接受上页的参数,联网取数据,更新data
- 不能操作 dom
- 转场动画开始
- 页面onReady
- 转场动画结束
onShow和onHide
a页面刚进入时,会触发a页面的onShow
当a跳转到b页面时,a会触发onHide,而b会触发onShow
但当b被关闭时,b会触发onUnload,此时a再次显示出现,会再次触发onShow
不同tab页面互相切换时,会触发各自的onShow和onHide
onBackPress
触发返回
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 而是 onShowuni.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 | 视图容器,类似于HTML中的div |
scroll-view | 可滚动视图容器 |
swiper | 滑块视图容器,比如用于轮播banner |
match-media | 屏幕动态适配组件,比如窄屏上不显示某些内容 |
movable-area | 可拖动区域 |
movable-view | 可移动的视图容器,在页面中可以拖拽滑动或双指缩放。movable-view必须在movable-area组件中 |
cover-view | 可覆盖在原生组件的上的文本组件 |
cover-image | 可覆盖在原生组件的上的图片组件 |
基础内容
组件名 | 说明 |
---|---|
icon | 图标 |
text | 文字 |
rich-text | 富文本显示组件 |
progress | 进度条 |
表单组件
标签名 | 说明 |
---|---|
button | 按钮 |
checkbox | 多项选择器 |
editor | 富文本输入框 |
form | 表单 |
input | 输入框 |
label | 标签 |
picker | 弹出式列表选择器 |
picker-view | 窗体内嵌式列表选择器 |
radio | 单项选择器 |
slider | 滑动选择器 |
switch | 开关选择器 |
textarea | 多行文本输入框 |
路由与页面跳转
组件名 | 说明 |
---|---|
navigator | 页面链接。类似于HTML中的a标签 |
媒体组件
组件名 | 说明 |
---|---|
audio | 音频 |
camera | 相机 |
image | 图片 |
video | 视频 |
live-player | 直播播放 |
live-pusher | 实时音视频录制,也称直播推流 |
image
mode
:- aspectFit: 保持纵横比缩放图片,使图片的长边能完全显示出来
- aspectFill : 保持纵横比缩放图片,只保证图片的短边能完全显示出来
- widthFix: 宽度不变,高度自动变化,保持原图宽高比不变
地图
组件名 | 说明 |
---|---|
map | 地图 |
画布
组件名 | 说明 |
---|---|
canvas | 画布 |
webview
组件名 | 说明 |
---|---|
web-view | web浏览器组件 |
广告
组件名 | 说明 |
---|---|
ad | 广告组件 |
ad-draw | 沉浸视频流广告组件 |
页面属性配置
组件名 | 说明 |
---|---|
custom-tab-bar | 底部tabbar自定义组件 |
navigation-bar | 页面顶部导航 |
page-meta | 页面属性配置节点 |
uniCloud
组件名 | 说明 |
---|---|
unicloud-db组件 | uniCloud数据库访问和操作组件 |
uni-ui
扩展组件, 符合 easycom 标准, 会自动导入
# 安装
pnpm add @dcloudio/uni-ui
组件名 | 组件说明 |
---|---|
uni-badge | 数字角标 |
uni-calendar | 日历 |
uni-card | 卡片 |
uni-collapse | 折叠面板 |
uni-combox | 组合框 |
uni-countdown | 倒计时 |
uni-data-checkbox | 数据选择器 |
uni-data-picker | 数据驱动的picker选择器 |
uni-dateformat | 日期格式化 |
uni-datetime-picker | 日期选择器 |
uni-drawer | 抽屉 |
uni-easyinput | 增强输入框 |
uni-fab | 悬浮按钮 |
uni-fav | 收藏按钮 |
uni-file-picker | 文件选择上传 |
uni-forms | 表单 |
uni-goods-nav | 商品导航 |
uni-grid | 宫格 |
uni-group | 分组 |
uni-icons | 图标 |
uni-indexed-list | 索引列表 |
uni-link | 超链接 |
uni-list | 列表 |
uni-load-more | 加载更多 |
uni-nav-bar | 自定义导航栏 |
uni-notice-bar | 通告栏 |
uni-number-box | 数字输入框 |
uni-pagination | 分页器 |
uni-popup | 弹出层 |
uni-rate | 评分 |
uni-row | 布局-行 |
uni-search-bar | 搜索栏 |
uni-segmented-control | 分段器 |
uni-steps | 步骤条 |
uni-swipe-action | 滑动操作 |
uni-swiper-dot | 轮播图指示点 |
uni-table | 表格 |
uni-tag | 标签 |
uni-title | 章节标题 |
uni-transition | 过渡动画 |
uni-api
- 出现目的
- 很多的原生的小程序的api 不支持 promise
wx.request
- 想要做一个跨平台 应用 那么 发送请求 该用哪一代码来发送 3. axios 针对 网页的!!! 4. wx.request 只针对 微信小程序 5. 自己去封装一个api 可以去兼容适配所有的客户端
if (微信小程序) wx.request
if (web) axios
if (支付宝) ...
- 如何使用
uni.request
语法
- 标签优先使用 uni-app 的
- js语法和vue一样
- 特有的api:
uni.request
,uni.showToast
- 生命周期参考上面的
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
{
// 推荐的扩展插件
"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 提供
pnpm add @uni-helper/uni-app-types @uni-helper/uni-ui-types -D
微信小程序的类型提示
pnpm add miniprogram-api-typings -D
nodejs
pnpm add @types/node -D
在 tsconfig.json
的 types 中添加
{
//...
"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
{
// 在保存时格式化文件
"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
pnpm add 'prettier@^3' '@vue/eslint-config-prettier@^9' -D
.prettierrc.json
{
"singleQuote": true,
"semi": false,
"printWidth": 120,
"trailingComma": "all",
"endOfLine": "auto"
}
eslint
pnpm add 'eslint@^8' 'eslint-plugin-vue@^9' '@vue/eslint-config-typescript@^11' '@rushstack/eslint-patch@^1' -D
.eslintrc.cjs
/* 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
# 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
提交前格式校验
pnpm add husky lint-staged -D
.husky/pre-commit
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"
npm run lint-staged
package.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 持久化
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
import { createPinia } from 'pinia'
import persist from 'pinia-plugin-persistedstate'
// 创建 pinia 实例
const pinia = createPinia()
// 使用持久化存储插件
pinia.use(persist)
// 默认导出,给 main.ts 使用
export default pinia
main.ts
// 导入 pinia 实例
import pinia from './stores/'
// ...
// 在 return 前添加
// 使用 pinia
app.use(pinia)
stores/modules/member.ts
- 小程序没有 localStorage
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
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
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
样式适配: 安全区域 (例如刘海屏)
// 获取屏幕边界到安全区域距离
const { safeAreaInsets } = uni.getSystemInfoSync()
用来设置 paddingTop
<view class="navbar" :style="{ paddingTop: safeAreaInsets!.top + 10 + 'px' }">
</view>
轮播图通用组件
配置 easycom 自动导入
page.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
const handleChange: UniHelper.SwiperOnChange = (e) => {
activeIndex.value = e.detail!.current
}
类型提示
src/types/components.d.ts
import JbcSwiper from '@/components/JbcSwiper.vue'
declare module 'vue' {
export interface GlobalComponents {
JbcSwiper: typeof JbcSwiper
}
}
// 组件实例类型 (typeof xxx组件 获取到的是构造函数类型)
export type JbcSwiperInstance = InstanceType<typeof JbcSwiper>
- 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 获取路由参数
// uni-app获取路由参数
const query = defineProps<{
type: string
}>()
console.log(query)
- uni-app 动态设置标题
// 动态设置标题
uni.setNavigationBarTitle({ title: '标题名' })
- 判断当前是不是开发环境, 方便设置模拟数据
{
page: import.meta.env.DEV ? 33 : 1,
pageSize: 10,
}
商品分类页
- 小程序开发者工具直接显示分类页, 免去点击跳转:
点击'普通编译 -> 添加编译模式 -> 选择要启动页面为要编译的页面'
- 切换分类时, 滚动条不恢复到顶部:
// 切换分类时,滚动到顶部
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
@font-face {
font-family: 'iconfont'; /* Project id */
src: url('iconfont.ttf?t=1745547896290') format('truetype');
}
- 操作栏有按钮, 需要设置安全区域 (可能有小白条) 防止误操作
<view class="toolbar" :style="{ paddingBottom: safeAreaInsets?.bottom + 'px' }">
</view>
- 弹出层组件
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()
报错, 需要使用交叉类型
const popup = ref<
UniHelper.UniPopupInstance & {
open: (type?: UniHelper.UniPopupType) => void
close: () => void
}
>()
NonNullable<type>
: 去除类型里的 null 和 undefined
登录模块
快捷登录
===有效期五分钟===。开发者需要在开发者服务器后台调用 code2Session,使用 code 换取 openid、unionid、session_key 等信息
不能在 getphonenumber
里调用, 在 onload
里
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 文件夹下
import type { JbcGuessInstance } from '@/types/components'
import { ref } from 'vue'
export const useGuessList = () => {
const guessRef = ref<JbcGuessInstance>()
const onScrollToLower = () => {
guessRef.value?.getMore()
}
return { guessRef, onScrollToLower }
}
调用:
import { useGuessList } from '@/composables'
const { guessRef, onScrollToLower } = useGuessList()
- 设置页分包
把不常用的页面拆成另外的包, 当某些步骤可能需要打开时, 提前预加载
存放目录: 和 pages
同级, 例如 pagesMember/settings/settings.vue
在 pages.json
中添加 subPackages
, 和 pages
同级
// 分包
"subPackages": [
{
// 子包的根目录
"root": "pagesMember",
// pages参数和主包一样
"pages": [
{
"path": "settings/settings",
"style": {
"navigationBarTitleText": "设置"
}
}
]
}
]
- 设置预下载
page.json
key 是页面路径
https://uniapp.dcloud.net.cn/collocation/pages.html#preloadrule
// 预下载
"preloadRule": {
"pages/my/my": {
"network": "all",
"packages": ["pagesMember"]
}
}
设置页面
- 显示模态框:
uni.showModal()
- 路由后退:
uni.navigateBack()
修改用户信息
Pick<Type, Keys>
: 从类型中选取指定属性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
数据校验
- 表单校验: 使用
<uni-forms>
组件- 表单绑定
ref modelValue rules
- 表单项设置
label name
- 原生 checkbox 或三方组件不支持 v-model 等,
onFieldChange
- 提交前再调用 formRef 的
.validate()
, 返回的是 Promise
- 表单绑定
https://uniapp.dcloud.net.cn/component/uniui/uni-forms.html
// 表单验证规则
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
目录下
import { Component } from '@uni-helper/uni-app-types'
/** SKU 弹出层 */
export type SkuPopup = Component<SkuPopupProps>
/** SKU 弹出层实例 */
export type SkuPopupInstance = InstanceType<SkuPopup>
/** 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
import { Component } from '@uni-helper/uni-app-types'
/** 步进器 */
export type InputNumberBox = Component<InputNumberBoxProps>
/** 步进器实例 */
export type InputNumberBoxInstance = InstanceType<InputNumberBox>
/** 步进器属性 */
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
生命周期执行
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 - 去支付
- 生产环境:
- 需要通过认证的应用对应的 appid https://uniapp.dcloud.net.cn/tutorial/app-payment-weixin.html#%E5%BC%80%E9%80%9A
- 提交资料申请开通获得商户号
PartnerID
和密码, 置APIv2密钥 - 服务器接入 https://pay.weixin.qq.com/doc/v3/merchant/4012791911
- 流程:
- 开发环境:
- 模拟支付
- 生产环境:
- 微信支付参数的返回类型:
WechatMiniprogram.RequestPaymentOption
, 给wx.requestPayment
调用 - 复制剪切板:
uni.setClipboardData
订单列表页
todo: 5 个不同类型的列表并发太慢
小程序打包
- 编译
pnpm build:mp-weixin
- 开发者工具: 点击上传
- 公众号平台提交审核 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
注意:
- 样式的条件编译,无论是 css 还是 sass/scss/less/stylus 等预编译语言中,必须使用
/*注释*/
的写法。 - 通过 import 导入的 scss 中的条件编译似乎不生效
h5 预览
- 相对路径
manifest.json
/* H5特有相关 */
"h5": {
"router": {
"base": "./"
}
},
安卓打包
调试:
- 需要
HBuilderX
- 安装运行插件和基座
- 调试手机开启开发者工具
云打包:
- 注册 DCloud 账号
- 获取 DCloud AppID: 在 HBuilderX 中打开
manifest.json
- 设置应用名称图标
- 发行-云打包-使用云端证书
ios 打包
调试:
- 需要mac
- 安装 xcode
- 运行到模拟器
打包:
- 需要苹果开发者账号和年费
样式兼容
- 小程序不支持 * 选择器
- 页面视口差异
- 小程序: 分tabBar 页和普通页
- 在 tabBar 页中, 视口不包括顶部和底部
- 在普通页中, 视口不包括顶部
- h 5: 视口包括浏览器整个区域
- 小程序: 分tabBar 页和普通页
- 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 | 系统状态栏高度 | 系统状态栏高度、nvue 注意见下 | 25px | 0 |
--window-top | 内容区域距离顶部的距离 | 0 | 0 | NavigationBar 的高度 |
--window-bottom | 内容区域距离底部的距离 | 0 | 0 | TabBar 的高度 |
示例:
/* 吸底工具栏 */
.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
只支持小程序, 可以使用停止维护的 apiuni.chooseImage