uni-shop-vue3/笔记.md
2025-05-15 16:48:32 +08:00

50 KiB
Raw Permalink Blame History

uni-app

基于 vue 语法的 全端开发框架

官网: https://uniapp.dcloud.io/

创建项目

https://uniapp.dcloud.net.cn/quickstart-cli.html

vue 3:

  1. 创建项目
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
  1. 运行项目 pnpm run dev:mp-weixin
  2. 使用微信小程序开发者工具来导入和预览项目 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 生命周期

页面组件优先使用小程序的生命周期钩子,也就是 onShow、onHide 这些,普通组件就用 Vue 生命周期钩子

页面生命周期

https://uniapp.dcloud.net.cn/tutorial/page.html#lifecycle

|700x1115

  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

触发返回

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 视图容器类似于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

  • 出现目的
  1. 很多的原生的小程序的api 不支持 promise wx.request
  2. 想要做一个跨平台 应用 那么 发送请求 该用哪一代码来发送 3. axios 针对 网页的!!! 4. wx.request 只针对 微信小程序 5. 自己去封装一个api 可以去兼容适配所有的客户端
if (微信小程序) wx.request
if (web)  axios
if (支付宝) ...
  • 如何使用
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

{
  // 推荐的扩展插件
  "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'

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
  })
})

商品详情页

字体可以上传到 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

登录模块

快捷登录

  • 登陆流程

    |700x710

  • 获取用户凭证 code

===有效期五分钟===。开发者需要在开发者服务器后台调用 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 模块 - 最小存货单位

类型定义

创建 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

订单详情页

自定义导航栏

滚动时切换 title

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,
    },
  )
})

支付

订单列表页

todo: 5 个不同类型的列表并发太慢

小程序打包

  1. 编译
pnpm build:mp-weixin
  1. 开发者工具: 点击上传
    1. 或者使用 ci: https://developers.weixin.qq.com/miniprogram/dev/devtools/ci.html
  2. 公众号平台提交审核 https://mp.weixin.qq.com

条件编译

是通过注释实现

https://uniapp.dcloud.net.cn/tutorial/platform.html#preprocessor

  • #ifdef#ifndef%PLATFORM% 开头,以 #endif 结尾。
    • #ifdefif defined 仅在某平台存在
    • #ifndefif 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

/* 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 系统状态栏高度 系统状态栏高度、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 只支持小程序, 可以使用停止维护的 api uni.chooseImage