From 29f7cffe0e90d9ed7259e73e3aa2307e1867f271 Mon Sep 17 00:00:00 2001 From: jqtmviyu Date: Fri, 16 May 2025 10:11:06 +0800 Subject: [PATCH] docs: :memo: update learning notes --- docs/learning/5-vue/1-vue3.md | 1412 ++++++++++++----------- docs/learning/6-小程序/uni-app.md | 1726 +++++++++++++++++++++++++++-- 2 files changed, 2409 insertions(+), 729 deletions(-) diff --git a/docs/learning/5-vue/1-vue3.md b/docs/learning/5-vue/1-vue3.md index d906eef..7329002 100755 --- a/docs/learning/5-vue/1-vue3.md +++ b/docs/learning/5-vue/1-vue3.md @@ -3,7 +3,6 @@ ## 优点 * 打包体积更小 -* 渲染速度更快 * 内存占用减少 * **更好的支持TS** * **使用Proxy代替defineProperty实现数据响应式** @@ -46,16 +45,7 @@ $ npm install $ npm run dev ``` -或者 yarn: - -```bash -$ yarn create vite --template vue -$ cd -$ yarn -$ yarn dev -``` - -或者 pnpm: +pnpm: ```bash $ pnpm create vite -- --template vue @@ -70,12 +60,13 @@ $ pnpm dev [官方文档](https://composition-api.vuejs.org/zh/api.html) -### 生命周期 +### 生命周期和钩子函数 -创建 挂载 更新 销毁, 每个阶段都有两个钩子,一前一后 +创建->挂载->更新->销毁, 每个阶段都有两个**钩子函数** -* `beforeCreate` -> 使用 `setup()` -* `created`创建实例前 -> 使用 `setup()` +* `setup()`-> ` - - - - ``` +```ts +// Vue 3 SFC 一般会自动从组件的文件名推断出组件的 name。在大多数情况下,不需要明确的 name 声明。 +// 唯一需要的情况是当你需要 包含或排除或直接检查组件的选项时,你需要这个名字。 + + + +``` 2. ts兼容 - ```ts - // 通过解构的方式去导入类型,setup sugar 会进行自动导出, 此时ts会报错 - //解决方法: - import type { test } from "./test" - ``` +```ts +// 通过解构的方式去导入类型,setup sugar 会进行自动导出, 此时ts会报错 +//解决方法: +import type { test } from "./test" +``` + +### ==vue 2 和 vue 3 的响应式区别== + +#### vue 2 + +* 核心: + * 对象: 通过 `Object.defineProperty()` 对象的已有属性值的读取和修改进行劫持 (监视/拦截) + * 数组: 通过 `重写数组` 更新数组一系列更新元素的 `方法` 来实现元素修改的劫持 + +```js +Object.defineProperty(data, 'count', { + get () {}, + set () {} +}) +``` + +* 问题 + * 数组直接通过下标替换元素或更新 length, 界面不会自动更新 + * `push() pop() shift() unshift() splice() sort() reverse()` 这类会改变原数据的方法, 都进行了包裹 + * `this.$set(this.array, index, newValue)` + * 对象直接新添加的属性或删除已有属性, 界面不会自动更新 + * `this.$set(this.obj, key , newValue)` + * `this.$delete(this.obj, key)` + * `this.obj=Object.assign({}, this.obj, {a:1, b:2})` + +#### vue 3 + +* 核心: + * ref:创建的是一个包含 `.value` 属性的对象。对这个 `.value` 属性的访问和修改会触发 `get` 和 `set` 拦截,从而实现响应式(数据劫持)。 + * reactive:通过 `Proxy` 来实现响应式(数据劫持),并通过 Reflect 操作原对象内部的数据 +* 问题 + * 数组用下标更新元素或者更新 length, 同样会丢失响应式 + * `push() pop() shift() unshift() splice() sort() reverse()` + * 对象使用 `Proxy` 实现响应式, 可以直接添加属性和删除属性 + * 对象提取的 `基本类型` 没有响应式 + * 用 computed 或者 toRef + * 对象 `解构` 和 `展开运算符` 会丢失响应式 + * `toRefs` + * `reactive` 创建新的响应式对象 + * `Object.assign(someReactive, newObj)` + * 对象 `整个替换` 会丢失响应式 +* 文档: + * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy + * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect + +```html + + + + + + Proxy 与 Reflect + + + + + +``` ### ref 函数 @@ -246,39 +353,33 @@ const attrs = useAttrs() * 在 script 中修改值,获取值的时候,需要`.value` * 在模板 template 中使用ref声明的响应式数据,不需要.value * 其实也可以定义复杂数据类型的响应式数据, 如果用ref对象/数组, 内部会自动将对象/数组转换为reactive的代理对象 -* 基本数据类型:响应式是靠`Object.defineProperty()`的`get`与`set`进行设置和取值操作 +* 基本数据类型:创建的是一个包含 `.value` 属性的对象。对这个 `.value ` 属性的访问和修改会触发 ` get ` 和 ` set ` 拦截,从而实现响应式追踪和更新。 ```vue - ``` -* **当你明确知道需要的是一个响应式数据 *对象* 那么就使用 reactive 即可** -* **其他情况使用ref** +* **减少心智负担, 优先 ref** ### toRef 函数 @@ -293,41 +394,39 @@ export default { ```vue - + ``` ### toRefs 函数 -> 转换**响应式对象**中所有属性为单独响应式数据,对象成为普通对象,并且**值是关联的** +> 转换一个响应式对象, 返回一个普通对象, 普通对象的每个属性都是响应式的 * 语法:`const person = toRefs(person)` @@ -336,41 +435,42 @@ export default { ```vue - - + + ``` ### isRef @@ -379,7 +479,7 @@ export default { ### unref -如果参数是一个 [`ref`](https://v3.cn.vuejs.org/api/refs-api.html#ref),则返回内部值,否则返回参数本身。这是 `val = isRef(val) ? val.value : val` 的语法糖函数 +如果参数是一个 `ref`,则返回内部值,否则返回参数本身。这是 `val = isRef(val) ? val.value : val` 的语法糖函数 ### shallowRef函数 @@ -460,21 +560,21 @@ const keyword = useDebouncedRef('', 500) > 一般用来定义复杂数据类型成为响应数据 -* reactive定义的响应式是『深层次的』,对象中的所有对象都是响应式的 +* reactive() 会对对象进行深层响应式转换,但是这种深层转换是**惰性的:只有当访问嵌套属性(触发 getter)时,Vue 才会递归将该属性也转为响应式。 * 注意: 用解构取出的单独属性, 不再是响应式的(用toRef) * 给reactive赋值 {} , 不再是响应式(用ref). 举例: 从接口获取数据, 直接把data赋值给reactive, 会失去相应式. 改为用ref定义, 给ref.value赋值 * 内部基于es6的proxy实现,通过代理对象内部数据进行操作 ```vue - + ``` * 用proxy实现数据相应式后, 就不再需要v2的this.$set了(proxy响应是深层次的) - ```vue - - - - ``` +```vue + + + +``` * 当将 `ref`分配给 `reactive` property 时,ref 将被自动解包。 - ```ts - const count = ref(1) - const obj = reactive({}) - - obj.count = count - - console.log(obj.count) // 1 - console.log(obj.count === count.value) // true - ``` +```ts +const count = ref(1) +const obj = reactive({}) + +obj.count = count + +console.log(obj.count) // 1 +console.log(obj.count === count.value) // true +``` ### shallowReactive proxy代理有性能问题, https://www.zhihu.com/question/460330154, https://www.cnblogs.com/zmj97/p/10954968.html > 创建一个响应式代理,它跟踪其自身 property 的响应性,但不执行嵌套对象的深层响应式转换 (暴露原始值) -> > 简单理解: 只有第一层是响应式的 -> > 应用: **有一个对象数据, 结构比较深, 但变化时只是外层属性变化** ```ts @@ -697,90 +787,6 @@ const bar = reactive({ console.log(foo.nested === bar.nested) // false ``` -### ==vue2和vue3的响应式区别== - -#### vue2 - -* 核心: - * 对象: 通过`Object.defineProperty()`对象的已有属性值的读取和修改进行劫持(监视/拦截) - * 数组: 通过`重写数组`更新数组一系列更新元素的`方法`来实现元素修改的劫持 - -```js -Object.defineProperty(data, 'count', { - get () {}, - set () {} -}) -``` - -* 问题 - * 对象直接新添加的属性或删除已有属性, 界面不会自动更新 - * 直接通过下标替换元素或更新length, 界面不会自动更新 arr[1] = {} - -#### vue3 - -核心: - -* ref:通过`Object.defineProperty()`的set和get属性实现响应式(数据劫持) -* reactive:通过`Proxy`来实现响应式(数据劫持),并通过Reflect操作原对象内部的数据 -* 文档: - * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Proxy - * https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Reflect - -```html - - - - - - Proxy 与 Reflect - - - - - -``` - ### computed 函数 计算属性 computed函数: @@ -793,72 +799,55 @@ Object.defineProperty(data, 'count', { ```vue - + ``` ### watch 函数 -监听 watch函数: +监听 watch 函数: * 与watch配置功能一致 * 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调 -* 默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次 +* 默认初始时不执行回调, 但可以通过配置 immediate 为 true, 来指定初始时立即执行第一次 * 通过配置deep为true, 来指定深度监视 -* 监听reactive对象中的属性, 必须通过函数返回该属性来指定 +* ===监听reactive对象中的属性, 必须通过函数返回该属性来指定=== * 回调函数可以接收到两个参数, newValue和oldValue ```vue @@ -867,67 +856,96 @@ export default { fistName:
lastName:
fullName1:
- - + +``` + +#### 监听 prop + +> 第一个参数需要通过箭头函数返回 props 的值 +> props 理解成 reactive 对象 + +```ts + ``` @@ -938,17 +956,15 @@ export default { * 默认初始时就会执行第一次, 从而可以收集需要监视的数据 * 监视数据发生变化时回调 -```js +```vue + + + +``` + +| 特性 | watchEffect | watch | +| ---------- | --------------- | ------------------------ | +| **依赖收集方式** | 自动收集所有回调中的响应式依赖 | 显式指定要监听的源 | +| **立即执行** | 立即执行回调 | 默认不立即执行(可配置 `immediate`) | +| **返回值处理** | 无法直接访问变化前后的值 | 可以获取新旧值 | +| **调试信息** | 调试信息较少 | 提供更丰富的调试信息 | +| **性能开销** | 略高(自动依赖收集) | 略低(明确依赖关系) | + +#### 动态依赖 + +> 每次运行时都会重新收集依赖 +> watch 是静态依赖 + +* 条件依赖 + +```ts +watchEffect(() => { + if (condition.value) { + console.log(activeData.value) // 条件为真时才依赖 activeData + } else { + console.log(backupData.value) // 条件为假时才依赖 backupData + } +}) + +/** +当 condition 变化时,会重新执行并收集依赖 +每次执行可能建立不同的依赖关系 +*/ +``` + +* 动态依赖 + +```ts +const activeTab = ref('users') +const users = ref([]) +const products = ref([]) +watchEffect(async () => { + if (activeTab.value === 'users') { + users.value = await fetchUsers() + } else { + products.value = await fetchProducts() + } +}) + +/** +当 activeTab 从 'users' 变为 'products' 时: +移除对 users 的依赖 +新增对 products 的依赖 +*/ ``` ### ref标签属性 > 绑定DOM或者组件 > -> * 单个元素:先申明ref响应式数据,返回给模版使用,通过ref绑定数据 +> 单个元素:先申明ref响应式数据,返回给模版使用,通过ref绑定数据 注意: 写法和vue2不同, 把一个使用`ref`声明的响应式数据绑定到标签的ref属性上, 使用时要用`.value` @@ -997,25 +1068,19 @@ export default { - ``` @@ -1034,45 +1099,38 @@ https://v3.cn.vuejs.org/guide/migration/array-refs.html#frontmatter-title
{{ i }}
- + ``` ### 自定义hook函数 > 自定义hook的作用类似于vue2中的mixin技术 -> > 使用Vue3的组合API封装的可复用的**功能函数** -> > 文件命名时最好在前面加上`use` -> > 在setup中能使用的函数,在hook函数中也都可以使用 和mixin的区别 @@ -1141,19 +1199,16 @@ const {x, y} = useMousePosition() 变化的总体概述: -* 非兼容 - - :用于自定义组件时,`v-model` prop 和事件默认名称已更改: - +* 非兼容:用于自定义组件时,`v-model` prop 和事件默认名称已更改: * prop:`value` -> `modelValue`; * 事件:`input` -> `update:modelValue`; * **非兼容**:`v-bind` 的 `.sync` 修饰符和组件的 `model` 选项已移除,可在 `v-model` 上加一个参数代替; * **新增**:现在可以在同一个组件上使用多个 `v-model` 绑定; * **新增**:现在可以自定义 `v-model` 修饰符。 -ps: vue2.2引入`model`组件选项, 但只允许在组件上使用一个`v-model` +ps: vue2.2 引入 `model` 组件选项, 但只允许在组件上使用一个 `v-model` -### 3.x新语法 +### prop 和事件名发生变化 父组件 @@ -1171,24 +1226,26 @@ ps: vue2.2引入`model`组件选项, 但只允许在组件上使用一个`v-mode 子组件 ```vue - + ``` ### 自定义model名(可以用来替代原来的`.sync`语法糖) @@ -1235,98 +1292,104 @@ ps: 内置的有`.trim`, `number`, `.lazy` 父组件 - ```vue - - ``` +```vue + +``` 子组件 - ```vue - - ``` +```vue + +``` * 例子: 自定义model名 + 自定义修饰符号 父组件 - ```vue - - ``` +```vue + +``` 子组件 - ```vue - - - - - - ``` +```vue + + + + + +``` ## 依赖注入 > 无论组件层次结构有多深,父组件都可以作为其所有子组件的依赖提供者。 -> > 这个特性有两个部分:父组件有一个 `provide` 选项来提供数据,子组件有一个 `inject` 选项来开始使用这些数据。 -> -> ==只能在setup生命周期里使用== +> ==在 ` - ``` +```vue + + + +``` ## 新组件 @@ -1429,6 +1492,8 @@ const data = inject('data') ``` +vue 2 必须添加一个根标签把这两个 h2 包住 + ### Teleport 传送门 > 一个组件模板的一部分在逻辑上从属于该组件,但从整个应用视图的角度来看,它在 DOM 中应该被渲染在整个 Vue 应用外部的其他地方。 @@ -1472,6 +1537,118 @@ const data = inject('data') * v-if优先v-for解析(和vue2不同) +## 常见错误 + +1. 引入组件时, `.vue`后缀不能省略 +2. `@` 路径不生效, 在`tsconfig.json`里添加配置 + +```json +"compilerOptions": { + //... + "paths": { + "@/*": [ + "./src/*" + ], + }, + //... +} +``` + + 在`vite.config.ts` + +```ts +export default defineConfig({ + base: "./", + resolve: { + alias: [ + { + find: "@", + replacement: resolve(__dirname, "./src") // __dirname报错, 则需要安装 `@types/node` npm包 + } + ] + }, + // ... +}) +``` + +3. less支持 + +```ts +// 1. 先安装less 和 less-loader +// 在vite.config.ts +export default defineConfig({ + css: { + preprocessorOptions: { + less: { + javascriptEnabled: true + } + } + }, + // ... +}) +``` + +## h 函数直接生成虚拟dom + +待补充... + +## render函数渲染 + +待补充... + +## 动态加载组件 + + `import()` 函数和 `defineAsyncComponent` + +## vue2 vue3 是怎么自动更新页面的 + +数据劫持/数据代理 + 依赖收集 + 派发更新 + +vue 2: + +* 数据劫持: 通过 `Object.definedProperry()` 为数据对象添加 getter 和 setter 方法, 当读取属性的时候, 会触发 getter 方法, 当修改属性值时, 会触发 setter 方法 +* 依赖收集: 在 `getter` 方法中会进行依赖收集. 当 Vue 渲染时, 会访问数据对象的属性, getter 方法会将当前的渲染函数和计算属性添加到一个 `依赖列表` 中 +* 派发更新: 当数据发生变化时, `setter` 方法会通知所有依赖该属性的地方进行更新. Vue 通过 `Wathcher 观察者模式` 实现 + +vue 3: + +* 数据代理: 通过 `Proxy` 代理整个对象, 无须递归劫持 +* 依赖收集: 也是在 `getter` 访问属性时收集依赖, 但是用 `WeakMap` (key 是对象, 可以被自动回收) + `Map` 存储依赖关系 +* 派发更新: `setter` 方法会调用 `trigger`, 遍历 `effect`(类似 Vue 2 的 `Watcher`), 通过调度器(`Scheduler`)执行 + +| **对比项** | **Vue 2** (`Object.defineProperty`) | **Vue 3** (`Proxy + WeakMap`) | +| ---------- | ----------------------------------- | -------------------------------------------- | +| **劫持方式** | 递归使用 `Object.defineProperty` 劫持每个属性 | 使用 `Proxy` 一次性劫持整个对象 | +| **动态属性监听** | ❌ 新增属性无法响应(需 `Vue.set`) | ✅ 动态添加/删除属性也能被监听 | +| **依赖收集** | `getter` 中手动调用 `Dep` 收集依赖 | 利用 `WeakMap + Map` 存储依赖关系,结构更清晰 | +| **依赖存储结构** | 每个属性有自己的 `Dep`,记录对应 `Watcher` | 中心化的 `targetMap: WeakMap -> Map -> Set` 存储结构 | +| **派发更新** | `setter` 中触发对应 `Watcher.update()` | 使用 `trigger(target, key)` 精准触发 `effect` 函数 | +| **数组监听** | 重写数组方法(如 `push`、`pop`)手动触发更新 | Proxy 能直接监听数组索引和长度变化 | +| **性能优化** | 初始化时递归全对象,成本高 | **按需懒劫持**,只在访问时创建响应式,性能更高 | +| **调度机制** | 借助 `nextTick()` 实现异步更新 | 提供内建的 `scheduler` 参数,可自定义任务调度 | +| **内存管理** | 手动管理引用关系 | `WeakMap` 自动管理依赖,**对象无引用时自动回收** | + +## vue 2 和 vue 3 diff 算法的区别 + +* vue 2 基于标签和 key 识别节点, 需要 `递归遍历` 比较所有节点 +* vue 3 增加了 `静态标记`, 静态节点会被跳过, 动态节点会标记哪里会变 +* vue 2 使用 `双端比较算法`, 需要同时比较新旧虚拟节点的头部和尾部 +* vue 3 先排除首尾不变的部分, 对于变化部分, 使用基于 `最长递增子序列算法` . 举例: + +``` +A-B-C-D 变成 A-C-B-D +1. 会先去掉首尾不变的部分, 即 B-C-D 变成 C-B-D +2. 基于最新的节点, 生成序列 [0, 1, 2], 对应旧节点, 那么序列为 [1, 0, 2] +3. 最长递增子序列是 [0, 2], 只需要调整 1 的位置就行 +``` + +| **优化点** | **Vue 2** | **Vue 3** | +| ------------------ | ----------------------- | ------------------------- | +| **静态节点优化** | 无优化,全部重新渲染 | 编译时标记静态节点,跳过不变的部分 | +| **VNode Patch** | 递归比对所有子节点 | `patchFlag` 标记动态部分,跳过静态内容 | +| **列表 Diff(v-for)** | O(n) 复杂度遍历整个 `children` | 最长递增子序列(LIS),优化 DOM 操作 | +| **Fragment 片段** | 需要额外的 `div` 作为根节点 | 允许多个根节点,减少无意义 Diff | + ## 手写api ### shallowReactive 与 reactive @@ -1527,7 +1704,6 @@ function reactive (target) { return target } - /* 测试自定义shallowReactive */ const proxy = shallowReactive({ a: { @@ -1538,7 +1714,6 @@ const proxy = shallowReactive({ proxy.a = {b: 4} // 劫持到了 proxy.a.b = 5 // 没有劫持到 - /* 测试自定义reactive */ const obj = { a: 'abc', @@ -1604,7 +1779,6 @@ const ref3 = shallowRef({ ref3.value = 'xxx' ref3.value.a = 'yyy' - /* 测试自定义ref */ const ref1 = ref(0) const ref2 = ref({ @@ -1717,7 +1891,6 @@ function isProxy (obj) { return isReactive(obj) || isReadonly(obj) } - /* 测试判断函数 */ console.log(isReactive(reactive({}))) console.log(isRef(ref({}))) @@ -1725,66 +1898,3 @@ console.log(isReadonly(readonly({}))) console.log(isProxy(reactive({}))) console.log(isProxy(readonly({}))) ``` - -## 常见错误 - -1. 引入组件时, `.vue`后缀不能省略 -2. `@` 路径不生效, 在`tsconfig.json`里添加配置 - - ```json - "compilerOptions": { - //... - "paths": { - "@/*": [ - "./src/*" - ], - }, - //... - } - ``` - - 在`vite.config.ts` - - ```ts - export default defineConfig({ - base: "./", - resolve: { - alias: [ - { - find: "@", - replacement: resolve(__dirname, "./src") // __dirname报错, 则需要安装 `@types/node` npm包 - } - ] - }, - // ... - }) - ``` - -3. less支持 - - ```ts - // 1. 先安装less 和 less-loader - // 在vite.config.ts - export default defineConfig({ - css: { - preprocessorOptions: { - less: { - javascriptEnabled: true - } - } - }, - // ... - }) - ``` - -## h 函数直接生成虚拟dom - -待补充... - -## render函数渲染 - -待补充... - -## 动态加载组件 - - `import()` 函数和 `defineAsyncComponent` \ No newline at end of file diff --git a/docs/learning/6-小程序/uni-app.md b/docs/learning/6-小程序/uni-app.md index 732b389..70a4d05 100755 --- a/docs/learning/6-小程序/uni-app.md +++ b/docs/learning/6-小程序/uni-app.md @@ -2,96 +2,396 @@ > 基于 `vue` 语法的 `全端开发框架` -https://uniapp.dcloud.io/ +官网: https://uniapp.dcloud.io/ -使用: - -1. 全局安装vue的脚手架 -2. 创建项目 - - ```bash - vue create -p dcloudio/uni-preset-vue my-project - ``` - -3. 运行项目 -4. 使用微信小程序开发者工具来导入和预览项目 - -==注意: 只能修改uni-app项目的文件, 不能修改编译后的小程序文件== - -## 使用cli +## 创建项目 https://uniapp.dcloud.net.cn/quickstart-cli.html -## uni-app 配置文件 +vue 3: -> 对应 小程序 app.json 全局配置 和 页面配置 index.json +1. 创建项目 -``` -src\pages.json +```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 ``` -![](https://img.081024.xyz/20210220155544.png) +2. 运行项目 `pnpm run dev:mp-weixin` +3. 使用微信小程序开发者工具来导入和预览项目 `dist/dev/mp-weixin` -## uni-app 项目配置 +==注意: 只能修改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 ``` -修改了项目的配置文件 可能需要重启 +修改了项目的配置文件, 需要重启 -![image-20210220093139256](http://192.168.68.69:9876/medias/image-20210220093139256.png) - -## uni-app 中 使用 sass - -> 内置好了 sass 配置 没有帮你安装好依赖 - -自己去安装 sass的依赖 - -``` -npm i sass sass-loader -``` - -![image-20210220160219961](https://img.081024.xyz/20210220170114.png) - -可能会出现版本过高的情况 - -卸载 - -``` -npm uninstall sass-loader -``` - -重新-安装对应的版本 - -``` -npm i sass-loader@10 -``` - -代码中使用 `sass` - -```vue - -``` +最外面的appid, 是需要用到 Dcloud 云服务 (例如打包成 ios) 自动分配的, `mp-weixin => appid` 这里的才是微信小程序的 ## uni-app 生命周期 -1. 应用 `App.vue` 小程序 `app.js` 生命周期类似 -2. 页面 `index.vue` 小程序 页面 `index` 生命周期类似 -3. 组件 的生命周期 类似 vue的生命周期 +* 应用生命周期:与 **小程序** 应用的生命周期一致(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 -使用uniapp开发项目的时候 +**页面组件优先使用小程序的生命周期钩子**,也就是 onShow、onHide 这些,普通组件就用 Vue 生命周期钩子 -1. 当前编译的文件是谁 - 1. App.vue 使用的生命周期 就是 小程序的应用的生命周期 - 2. 页面.vue 使用的生命周期 就是 小程序 页面的生命周期 - 3. 组件的.vue 使用的生命周期 就是 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 @@ -99,26 +399,1296 @@ npm i sass-loader@10 1. 很多的原生的小程序的api 不支持 promise `wx.request` 2. 想要做一个跨平台 应用 那么 发送请求 该用哪一代码来发送 - 1. axios 针对 网页的!!! - 2. wx.request 只针对 微信小程序 - 3. 自己去封装一个api 可以去兼容适配所有的客户端 + 3. axios 针对 网页的!!! + 4. wx.request 只针对 微信小程序 + 5. 自己去封装一个api 可以去兼容适配所有的客户端 - ``` - if (微信小程序) wx.request - if (web) axios - if (支付宝) ... - ``` +``` +if (微信小程序) wx.request +if (web) axios +if (支付宝) ... +``` * 如何使用 -``` +```js uni.request ``` ## 语法 -1. 标签优先使用小程序的或者uView +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` \ No newline at end of file