笔记内容主要参考uni-app官方文档,仔细阅读uni-app官方文档 介绍教程等部分能获得更多内容。

uni-app面试题

条件编译

条件编译指的是:开发时,可以为不同平台编写不同的代码,分别用#ifdef#endif,但在编译后,只保留对应平台的代码,其他平台的代码被忽略,从在实现多端兼容的同时减少打包后的代码体积

页面跳转和层级

在uni-app中,页面跳转就那五个api:

uni.navigateTo:不关闭当前页面跳转到非tabbar页面

uni.redirectTo:关闭当前页面跳转到非tabbar页面

uni.reLaunch:关闭所有页面跳转到任意页面

uni.switchTab:关闭所有非tabbar页面,跳转到tabbar页面

uni.navigateBack:关闭当前页面,回退到历史记录栈中的一个页面

层级 = 页面在栈中的深度,比如

  • 首页:层级 0
  • 商品页:层级 1
  • 详情页:层级 2
  • 评论页:层级 3

navigateBack({ delta: 2 }) 表示回退 2 层,即回到“商品页”

uni.navigateBack通常配合getCurrentPages()使用

1
2
3
4
5
6
7
8
const arr = getCurrentPages()
if (arr.length > 1) {
uni.navigateBack()
} else {
uni.switchTab({
url: '/pages/home/home'
})
}

用uni-app开发和使用vue开发web有什么区别

标签不同

功能uni-app 标签Vue Web 标签说明
块级容器<view><div>viewdiv 的跨端替代
行内文本<text><span>text 支持长按选中,是唯一可选中文本的标签
图片<image><img>image 支持懒加载、缩放模式等小程序特性
按钮<button><button><div>两者都有 button,但属性不同
列表<scroll-view><list>(nvue)<div> + CSS 滚动scroll-view 支持滚动事件、下拉刷新
输入框<input> / <textarea><input> / <textarea>属性和事件有差异
导航栏<navigator><router-link><a>navigator 控制页面跳转
条件渲染<block v-if><template v-if>block 不渲染为节点,仅逻辑分组
视频<video><video>属性和事件不同,uni-app 更简化

因此模板中的标签和样式中的选择器都要修改

请求的方式不同

在基于vue的web开发中,通过axios发送请求,在uni-app中,有自己的请求api,比如uni.request

package.json文件

在hbuilder上,uni-app项目没有package.json也可以运行,因为uni-app项目使用的是自己的本地依赖

组件库

内部有许多组件库,不能使用 Element Plus这样的组件库,因为Element Plus 是为 Vue 3 + Web(浏览器) 设计的组件库,而 uni-app 是一个多端框架,存在根本性不兼容。

pinia

在uni-app中使用vue的状态管理库比如pinia无需手动下载,直接使用即可。

vue-router

不推荐使用vue-router来进行路由跳转,而是使用自带的路由跳转api,路由传参几乎只能借助查询参数,接收路由参数也不能使用useRoute,而是在onLoad钩子中接收。

路由中的hash值无法使用

dom操作

dom操作只能在浏览器上生效,所以uni-app项目不能书写任何dom操作

事件

鼠标事件和键盘事件不能使用(除非只打包为web端)

其他

  • IntersectionObserver只能在支持浏览器的环境中使用
  • 在web开发中和uni-app跨端开发中,storage api不同,web开发中的storage api 只能在浏览器上生效。
  • 不能使用布局属性比如clientWidth
  • window和document对象也不能使用
  • 阿里图标在app上不生效,因为App 端(iOS/Android)默认禁止加载外部网络资源(安全策略);小程序端不支持动态加载外部 CSS。解决方式是将阿里图标下载到本地然后再引入 (在App.vue中引入iconfont.css)

将uni-app项目编译到微信小程序,有什么注意事项

  • uni-app 中不能使用 * 选择器。只能通过元素选择器来替代
  • 微信小程序不支持a标签和i标签选择器,i标签会被编译成块级元素,如果i标签写在text标签中,则无法看见字体图标
  • 小程序的tabbar不支持midButton
  • 在微信小程序的普通组件中,只能通过getCurrentPages来获取当前页面的查询参数,不能使用onLoad
  • 在微信小程序中组件标签不区分大小写,Video会被识别为video
  • 微信小程序不支持ul和li标签,更不支持这2个标签的选择器
  • 在小程序中checkbox-group中不能嵌套组件,即使组件中包含checkbox
  • 字体图标:uni-app 在编译时,会对 static 目录下的某些资源进行处理,小于 8KB 的 .ttf, .woff 等字体文件自动转为 base64 内联,这个机制是为了减少 HTTP 请求(H5 端优化),最主要的是,小程序不支持在 css 中使用本地文件,包括本地的背景图和字体文件,需以 base64 方式方可使用。
  • onPageScroll这个钩子在微信小程序中的非页面组件,无法生效

App打包需要注意些什么

  • 静态资源文件名不能包含中文

  • 打包前可以自定义应用图标和开屏图片,不能是webp格式的

  • 云打包简单快捷,只需排队。本地打包虽然速度快,但是只能生成打包所需资源,最终还要使用Android Studio 进行最终的打包,需要原生开发知识,较为复杂。

  • 如果app中包含video组件,打包前需要勾选VideoPlayer模块

  • 在App端可以给video组件添加vslide-gesture-in-fullscreen="true"来开启亮度和音量调节的手势。

  • 在app端中,由于视频文件上传不能分片,所以请求体的体积有时候会很大,所以需要修改nginx的配置,以支持更大的请求体:

    1
    2
    3
    4
    5
    6
    7
    server {
    listen 80;
    server_name www.sanye.space;

    # ✅ 允许最大 500MB 的请求体
    client_max_body_size 500M;
    }
  • 在app端中,当上传文件的时候,发现上传的速率很慢,后来才发现因为上传使用的是4G流量,使用WIFI上传就没问题了,因为:移动网络的“上传”能力天生就比“下载”弱很多

    网络类型典型下载速度典型上传速度上传/下载比
    📶 Wi-Fi(家用宽带)100~1000 Mbps20~100 Mbps较均衡
    📱 4G LTE20~100 Mbps5~15 Mbps上传只有下载的 1/5~1/10
    📱 5G100~1000 Mbps10~50 Mbps上传仍远低于下载
  • 自定义开屏动画有图片跳动问题,目前这个问题官方还未解决,可以观察到B站软件自己也是有这个问题的,但是是用2张不同的图片来解决突兀的跳动效果的。

  • 在uni-app中某个元素被长按,通常会被选中且有激活样式,比如navigator标签,此时我们就可以通过添加hover-class="none"属性来解决这个问题

  • 编译到app端,输入框不会被移动键盘压住,而是会被挤压上去

  • 在非H5端,无法通过直接修改scrollTop属性来将滚动条滚动到底部,而是可以通过使用scroll-view组件并动态修改scrollTop属性来实现:

    1
    <scroll-view class="content" scroll-y :scroll-top="scrollTop">
    1
    2
    3
    nextTick(() => {
    scrollTop.value = 99999
    })
  • 在非H5端无法通过keydown事件来实现按下enter键发送消息或者评论,在uni-app中可以使用 @confirm 事件,用于监听“软键盘上的确认/回车键”。@confirm 在 App、H5、小程序全平台都支持!

用uni-app做图片下载和图片上传的时候,如何做到多端兼容

图片下载

H5端

使用a标签加download属性,如果是跨域资源,此时需要后端设置 Content-Disposition: attachment

app端

先调用uni.downloadFile下载文件,在成功的回调success中拿到文件临时路径res.tempFilePath,如果下载的是图片,再调用uni.saveImageToPhotosAlbum方法,将临时文件下载到图库中。如果调用的是saveFile方法,即便文件下载成功也不知道去哪儿找。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 封装下载和保存逻辑
function downloadAndSave() {
uni.downloadFile({
url: userStore.userinfo.avatar_url,
success(res) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePath,
success() {
uni.showToast({
title: '已保存到相册',
icon: 'success'
})
}
})
},
fail() {
uni.showToast({
title: '网络错误',
icon: 'error'
})
}
})
}

小程序端

下载图片时,最好先调用 uni.getSetting方法,检查授权情况,判断用户是否授权小程序访问图库,如果已授权,则直接下载并保存图片;如果未授权,则调用uni.authorize申请用户授权,授权成功后下载并保存图片,如果用户拒绝授权,提示用户"需要您授权保存图片到相册",如果用户同意授权,则调用uni.openSetting() 打开小程序设置页。

要注意的是 uni.getSettinguni.authorizeuni.openSetting() 这三个方法都只能在小程序端使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
const onSaveAvatar = () => {
// #ifndef H5
uni.getSetting({
success(res) {
if (res.authSetting['scope.writePhotosAlbum']) {
// 已授权,直接下载并保存
downloadAndSave()
} else {
// 未授权,申请权限
uni.authorize({
scope: 'scope.writePhotosAlbum',
success() {
downloadAndSave()
},
fail() {
// 用户拒绝授权
uni.showModal({
title: '提示',
content: '需要您授权保存图片到相册',
showCancel: true,
confirmText: '去设置',
success(modalRes) {
if (modalRes.confirm) {
uni.openSetting() // 打开小程序设置页
}
}
})
}
})
}
}
})
// #endif
}

图片上传

图片上传前,需要先选择图片,通过调用uni.chooseImage 实现,这个方法在各端都支持。调用uni.chooseImage,在成功的回调函数中就能拿到选择的图片的临时路径,这个临时路径可以用来展示。

在uni-app中怎么做文件分片和分片上传

H5端

确定分片的起始字节编号,调用File对象的slice方法对文件进行分片即可。分片上传代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
uploadTask = uni.uploadFile({
url: 'https://www.sanye.space/api/uploadChunk',
formData,
file: chunk,
name: 'chunk', //文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
header: {
Authorization: `Bearer ${userStore.token}`
},
success: successCb
});
const successCb = (response) => {
//这个箭头函数内部的this指向file对象
this.index++; //当前分片上传完毕后,分片号++
//如果整个文件还未上传完毕,则继续上传分片,当end == this.size的时候,恰好上传完毕
//当前分片的右区间小于文件的大小,说明还有分片需要上传
if (end < this.size) {
this.start = end; //更新左区间
this.uploadChunk(); //递归调用
} else {
this.status = "completed"; //如果上传完毕,则文件对象的修改状态
this.video_url = JSON.parse(response.data).path; //记录后端返回的视频文件路径
this.progress = 100
}
}

APP端

在uni-app中,App端不支持文件的分片,可以将整个文件视为一个分片,上传代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
uploadTask = uni.uploadFile({
url: 'https://www.sanye.space/api/uploadChunk',
formData: {
index: 0,
total: 1,
name: this.name
},
filePath: this.tempFilePath, //临时文件路径
name: 'chunk', //文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
header: {
Authorization: `Bearer ${userStore.token}`
},
success: (response) => {
this.status = "completed"; //如果上传完毕,则文件对象的修改状态
this.video_url = JSON.parse(response.data).path; //记录后端返回的视频文件路径
this.progress = 100
}
});

小程序端

调用 uni.getFileSystemManager得到fs对象,然后调用fs对象的readFile按范围读取文件,进行文件分片,最终得到的也是二进制文件,但是想要上传,必须确定每个分片的临时路径,所以还需要结合writeFile将文件分片写进内存得到临时文件路径,后续用户分片上传。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
const fs = uni.getFileSystemManager()
const getChunkNative = (file, start, length, chunkIndex) => {
try {
const tempChunkPath = `${wx.env.USER_DATA_PATH || uni.env.USER_DATA_PATH}/chunk_${chunkIndex}`
return new Promise((resolve, reject) => {
fs.readFile({
filePath: file.tempFilePath,
encoding: 'binary',
position: start,
length: start + length > file.size ? file.size - start : length,
success: (res) => {
fs.writeFile({
filePath: tempChunkPath,
data: res.data,
encoding: 'binary',
success: () => {
resolve(tempChunkPath) //实际上返回的是临时路径
}
})
},
fail: (err) => {
console.log(err)
reject()
}
})
})
} catch (err) {
console.error(err)
}
}
1
2
3
4
5
6
7
8
9
10
uploadTask = uni.uploadFile({
url: 'https://www.sanye.space/api/uploadChunk',
formData,
filePath: chunkPath,
name: 'chunk', //文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
header: {
Authorization: `Bearer ${userStore.token}`
},
success: successCb
});

在uni-app中如何做图片裁剪

在uni-app中进行图片裁剪有多端通用的写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// 获取图片裁剪框在图片中的位置和大小
const { left, top, width, height } = getCropInfo();
const zoom = naturalWidth / containerWidth
const ctx = uni.createCanvasContext('myCanvas', instance.value)
// 其中props.img_url是图片临时路径
// width, height 不乘以zoom防止裁剪后的图片尺寸过大(宽高等于像素数)
// 进行绘制,将裁剪框内的全部像素,绘制到canvas中裁剪框大小的区域
ctx.drawImage(props.img_url, left * zoom, top * zoom, width * zoom, height * zoom, 0, 0, width, height);
//canvas中绘制的像素导出为图片,得到图片的临时路径,如果不是H5端,还需将其转化成base64格式
const res = await new Promise((resolve, reject) => {
ctx.draw(false, () => {
uni.canvasToTempFilePath({
canvasId: 'myCanvas',
x: 0,
y: 0,
width: width,
height: height,
success: async function(res) {
// console.log('裁剪后的图片路径:', res.tempFilePath);
// #ifdef H5
resolve(res.tempFilePath)
// #endif
// #ifndef H5
// 如果需要base64格式,可以在App端通过额外步骤转换
const base64 = await convertTempFilePathToBase64(res.tempFilePath)
resolve(base64)
// #endif
}
}, instance.value);
});
})
return res;

其他

微信开发者工具预览功能

如果预览微信小程序的时候遇到了网络错误的问题,需要选择开发调试,点击开启调试

什么是uni-app

是一个使用 Vue.js 开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程快应用等多个平台。

uni-app的由来:uni-app官网

HBuilderX

HBuilderX是通用的前端开发工具,但为uni-app做了特别强化,和uni-app属于同一家公司的产品。

基本配置

  • 插件下载:工具 -> 插件下载
  • 快捷方式定制:工具 -> 预定快捷方案
  • tab补全代码:工具->设置->语言服务配置
  • 保存代码格式化:工具->设置->编辑器配置
  • 合并代码时显示最后一行代码:工具->设置->编辑器配置

创建一个uni-app项目

在点击工具栏里的文件 -> 新建 -> 项目

选择uni-app类型,输入工程名,选择模板,点击创建,即可成功创建。

uni-app自带的模板有默认的空项目模板、Hello uni-app 官方组件和API示例,还有一个重要模板是 uni ui项目模板,日常开发推荐使用该模板,已内置大量常用组件。

也可以使用vue-cli命令行开发,不过个人还是喜欢使用HBuilder。

运行uni-app

点击左上角的运行选项,即可选择项目的运行方式

如果要运行到手机,需要连接usb,在设置中开启USB调试。如果要运行到手机模拟器,则需要先下载,比如逍遥,雷电模拟器,然后打开这些应用,才能被hbuilder检测到

将项目运行到微信开发者工具:

  • AppID:在manifest->微信小程序配置中填写自己的微信小程序的 AppID,这个是需要我们去申请的

  • 安装路径:在设置中配置“微信开发者工具”的安装路径,只有配置了微信开发者工具路径,hx才能打开微信开发者工具

  • 开启服务端口:在微信开发者工具中,通过 设置 -> 安全设置面板,开启微信开发者工具的服务端口

发布uni-app

打包为原生App

在打包之前也需要在manifest.json 的可视化界面做相关配置。

  • 在基础配置面板中,获取uni-app 应用标识,并填写应用名称
  • 切换到 App 图标配置面板,点击浏览按钮,选择合适的图片之后,再点击自动生成所有图标并替换

在HBuilderX工具栏,点击发行,选择原生app-云端打包(在线打包),云端打包支持安心打包,保护用户隐私,不会上传代码和证书,通过差量包制作方式实现安心打包,缺点是可能需要排队。

云打包完成后,在内置控制台点击链接下载 apk 的安装包,并安装到 Android 手机中查看打包的效果。

虽然安心打包已经满足需求,但如仍然希望自己使用 xcode 或 Android studio 进行离线打包,则在 HBuilderX 发行菜单里找到本地打包菜单,生成离线打包资源,然后参考离线打包文档操作:https://nativesupport.dcloud.net.cn/AppDocs/README

App打包时,注意如果涉及三方sdk,需进行申请并在manifest.json里配置,否则相关功能无法使用。

iOS App打包需要向Apple申请证书

发布为Web网站

manifest.json 的可视化界面,进行如下配置

如果使用history路由,就需要后端服务器做对应的配置

当我们令运行时的基础路径为/api/,打包后index.html引入文件的路径就会变成如下:

1
2
 <script type="module" crossorigin src="/api/assets/index-yJj5sN9l.js"></script>
<link rel="stylesheet" crossorigin href="/api/assets/index-Dl1z_jWj.css">

在HBuilderX工具栏,点击发行,选择网站-H5,点击会弹出这个窗口:

无需在意,直接点击发行,即可生成 H5 的相关资源文件,保存于unpackage/dist/build/web 目录。

至于部署打包后的网页,其实和部署博客一样,可以借助github-pages+vercel,或者使用dcloud免费的网页托管服务。

发布为微信小程序

  1. 申请微信小程序AppID,参考:微信教程

  2. 在HBuilderX中顶部菜单依次点击 “发行” => “小程序-微信”,输入小程序名称和appid点击发行即可

如果手动发行(未勾选自动上传微信平台),则点击发行按钮后,会在项目的目录 unpackage/dist/build/mp-weixin 生成微信小程序项目代码。打开微信小程序开发者工具,导入生成的微信小程序项目,测试项目代码运行正常后,点击上传按钮,之后按照 提交审核=> 发布小程序标准流程,逐步操作即可,详细查看:微信官方教程

如果在发行界面勾选了自动上传微信平台,则无需再打开微信工具手动操作,将直接上传到微信服务器提交审核。

项目结构

下面只展示基础的项目结构,更详细的结构可以参考官方文档。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
│─components            符合vue组件规范的uni-app组件目录
│ └─comp-a.vue 可复用的a组件
├─pages 业务页面文件存放的目录
│ ├─index
│ │ └─index.vue index页面
│ └─list
│ └─list.vue list页面
├─static 存放应用引用的本地静态资源(如图片、视频等)的目录,注意:静态资源都应存放于此目录
├─uni_modules 存放uni_module 详见
├─unpackage 非工程代码,一般存放运行或发行的编译结果
├─main.js Vue初始化入口文件
├─App.vue 应用配置,用来配置App全局样式以及监听 应用生命周期
├─pages.json 配置页面路由、导航条、选项卡等页面类信息
├─manifest.json 配置应用名称、appid、logo、版本等打包信息
└─uni.scss 内置的常用样式变量

微信小程序中的一个页面就是一个文件夹,然后里面有四个文件,分别对应html,css,js,json文件,而在uni-app中,一个vue文件就包含了前三者,然后页面json文件被page.json中的pages.style属性给替代了。

page.json

项目页面的配置文件 ,类似微信小程序中的app.json,这种命名方式貌似着重强调了pages属性。

主要组成:

globalStyles全局定义页面的样式,会被pages选项中页面单独定义的样式覆盖

1
2
3
4
5
6
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#fff",
"backgroundColor": "#F8F8F8"
// "navigationStyle": "custom"
},

可以看出,page.json文件中的globalStyles属性替换了微信小程序中的app.json文件中的window属性

pages:注册页面的地方;值是一个数组,数组元素是一个一个的页面对象,第一个页面对象就是首屏,在pages中注册的页面(路由组件)都会被打包进主包

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path": "pages/home/home",
"style": {
"navigationBarTitleText": "推荐",
"navigationStyle": "custom", //自定义导航栏(隐藏原生)
"enablePullDownRefresh": true
}
},
{
"path": "pages/classify/classify",
"style": {
"navigationBarTitleText": "分类",
"navigationStyle": "custom", //自定义导航栏(隐藏原生)
"enablePullDownRefresh": true
}
}
]
}

页面对象属性:

  • path:页面组件路径
  • style:定义页面的一些样式,比如配置,"navigationStyle": "custom";配置后这个页面的导航栏直接消失了

tabbar:值是一个对象,用来配置标签栏。

要注意的是,H5 端有时会缓存旧的 pages.json 配置,导致配置了tabBar但是不显示的问题,此时就需要重新编译,重启项目。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
{
"tabBar": {
"selectedColor": "#28b389",
"list": [{
"pagePath": "pages/home/home",
"iconPath": "static/home.png",
"selectedIconPath": "static/home-h.png",
"text": "推荐"
},
{
"pagePath": "pages/classify/classify",
"iconPath": "static/classify.png",
"selectedIconPath": "static/classify-h.png",
"text": "分类"
},
{
"pagePath": "pages/user/user",
"iconPath": "static/user.png",
"selectedIconPath": "static/user-h.png",
"text": "我的"
}
]
}
}

subPackages:用来注册分包,属性的值是一个数组,数组的值是一个一个分包对象,有几个分包对象最终就有几个分包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"subPackages": [
{
"root": "subpkg",
"name":'pkgA',//分包别名
//当前分包下所有页面的相对路径
"pages": [
"detail/detail",
"goods_list/goods_list",
"search/search"
]
}
]
}

root: "subpkg",表示这个分包的根目录,是项目下的 subpkg 文件夹,所有该分包的文件都放在 subpkg/ 目录下,而pages中的页面路径相对的都是root

分包中的页面不需要在pages.jsonpages属性中注册,pages属性中注册的页面都会被打包进主包中。

更多内容参考:pages.json 页面路由 | uni-app官网

static

uni-app编译器根据pages.json扫描需要编译的页面,并根据页面引入的js、css合并打包文件。

对于本地的图片、字体、视频、文件等资源,如果可以直接识别,那么也会把这些资源文件打包进去,但如果这些资源以变量的方式引用, 比如:<image :src="url"></image>,甚至可能有更复杂的函数计算,此时编译器无法分析。

那么有了static目录,编译器就会把这个目录整体复制到最终编译包内,只要运行时确实能获取到这个图片,就可以显示。

当然这也带来一个注意事项,如果static里有一些没有使用的废文件,也会被打包到编译包里,造成体积变大。

static 目录下的文件(vue组件、js、css 等)只有被引用时,才会被打包编译。

简单的来说就是,static目录下的文件无论是否被引用,都会被打包进最终文件;而非static目录下的文件,只有被引用了,才会被打包进最终文件。

uni.scss

uni.scss文件的用途是为了方便整体控制应用的风格。比如按钮颜色、边框风格,uni.scss文件里预置了一批scss变量

1
2
3
4
5
6
7
....
/* 行为相关颜色 */
$uni-color-primary: #007aff;
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
.....

uni.scss是一个特殊文件,在代码中无需 import 这个文件,即可在scss代码中使用这里的样式变量。uni-app的编译器在webpack配置中特殊处理了这个uni.scss,使得每个scss文件都被注入这个uni.scss,达到全局可用的效果。

简单来说,这就是一个只包含scss变量的文件,会被自动注入到所有scss文件中。

注意:如要使用这些常用变量,需要在 HBuilderX 里面安装 scss 插件;使用时需要在 style 节点上加上 lang="scss"

pages.json不支持scss,原生导航栏和tabbar的样式修改只能使用js api。

1
2
3
4
5
{
"navigationBarTitleText": "首页",
"navigationBarTextStyle": "white",
"navigationBarBackgroundColor": "$primary-color" // ❌ 不支持 SCSS 变量!
}

uni-app 提供了 JS API 在运行时修改导航栏和 tabbar。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 修改导航栏标题
uni.setNavigationBarTitle({
title: '新标题'
});

// 修改导航栏颜色
uni.setNavigationBarColor({
frontColor: '#ffffff', // 标题颜色(仅支持 #ffffff 或 #000000)
backgroundColor: '#FF0000', // 背景颜色
animation: {
duration: 300,
timingFunc: 'easeIn'
}
});

//修改 tabbar 某项
uni.setTabBarItem({
index: 0,
text: '新标签',
iconPath: '/static/new-icon.png',
selectedIconPath: '/static/new-icon-active.png'
});

main.js

是 uni-app 的入口文件,类似微信小程序中的app.js文件,命名为main.js更符合vue的风格。

main.js主要作用是:

  • 初始化vue实例
  • 定义全局组件
  • 使用需要的插件如 i18n、vuex。

说白了不就是和vue的入口文件作用一样吗,只不过在语法上略有区别。

1
2
3
4
5
6
7
8
9
10
11
12
13
//vue2
import Vue from 'vue'
import App from './App'
import PageHead from './components/page-head.vue' //全局引用 page-head 组件

Vue.config.productionTip = false
Vue.component('page-head', PageHead) //全局注册 page-head 组件,每个页面将可以直接使用该组件
App.mpType = 'app'

const app = new Vue({
...App
})
app.$mount() //挂载 Vue 实例

App.vue

所有页面都是在App.vue下进行切换的,是应用入口文件。但App.vue本身不是页面,这里不能编写视图元素,也就是

没有<template>。而在标准的 Vue.js 项目中,App.vue 通常作为应用的根组件,并且包含 <template><script><style> 标签。

而在 uni-app 中,App.vue 主要扮演的是一个全局配置文件(沦落至此)的角色,而不是具体的页面组件。在uni-app中的作用包括:监听应用生命周期、配置全局样式。我们可能有疑问,全局样式配置不是在uni.scss文件中进行的吗?其实不是哈,我们之前也介绍过,这个文件内部只是一些scss变量,很遗憾,这个文件虽然也在项目根目录,但并不像微信小程序中的app.wxss文件一样,用来书写全局样式。

应用生命周期仅可在App.vue中监听,在页面监听无效。至此,uni-app中的生命周期就包括了:组件的生命周期,页面的生命周期和应用的生命周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
}
}
</script>

<style lang="scss">
/*每个页面公共css */
@import "@/common/styles/common.scss";
@import "@/uni.scss";
</style>

至于应用的生命周期有哪些,下面只列举了常见的部分。

函数名说明
onLaunchuni-app 初始化完成时触发(全局只触发一次),参数为应用启动参数,同 uni.getLaunchOptionsSync 的返回值
onShowuni-app 启动,或从后台进入前台显示,参数为应用启动参数
onHideuni-app 从前台进入后台
onErroruni-app 报错时触发
onUnhandledRejection对未处理的 Promise 拒绝事件监听函数(2.8.1+ app-uvue 暂不支持)
onPageNotFound页面不存在监听函数
onLastPageBackPress最后一个页面按下Android back键,常用于自定义退出
onExit监听应用退出

页面

uni-app项目中,一个页面就是一个 vue 文件。

在 uni-app js 引擎版中,后缀名是.vue文件或.nvue文件。 这些页面均全平台支持,差异在于当 uni-app 发行到App平台时,.vue文件会使用webview进行渲染,.nvue会使用原生进行渲染,详见:nvue原生渲染

一个页面可以同时存在vue和nvue,在pages.json的路由注册中不包含页面文件名后缀,同一个页面可以对应2个文件名。重名时优先级如下:

  • 在非app平台,先使用vue,忽略nvue
  • 在app平台,使用nvue,忽略vue

weex

Weex 是由阿里巴巴集团开发的一个跨平台UI 开发框架,它允许开发者使用 Web 技术(如 HTML、CSS 和 JavaScript)来编写一次代码,并将其部署到多个平台,包括 iOS、Android 和 Web。

Weex 生成的是原生组件而不是基于WebView的渲染,因此它提供了接近原生应用的性能表现。它也通过将 js逻辑原生渲染引擎分离,实现了高效的更新机制。Weex 的 js引擎是在基础 js引擎(如 JavaScriptCore 或 V8)的基础上进行了扩展,以支持特定的 API 和功能,但这些 API 并没有涵盖所有可能的设备功能或第三方服务集成需求。

为了解决这些问题,一些团队选择扩展 Weex 的功能集,或者转向其他提供更广泛 API 支持的框架,如 React NativeFlutter。此外,DCloud 的nVue框架通过增强对原生 API 的支持,解决了 Weex 在这方面的一些局限性,使前端工程师能够更加独立地开发完整应用程序。

nvue

nVue(Native Vue)是由 DCloud 公司开发的一个用于构建原生移动应用的框架。它允许开发者使用 Vue.js 语法来编写代码,但最终生成的是真正的原生 UI 组件,而不是 WebView 中的网页。

nvue如何实现原生渲染?

uni-app 的App 端内置了一个基于 weex 改进的原生渲染引擎,提供了原生渲染能力。

一个 App 中可以同时使用两种页面,比如首页使用 nvue,二级页使用 vue 页面,hello uni-app 示例就是如此。

应用场景

虽然 nvue 也可以多端编译,输出 H5 和小程序,但 nvue 的 css 写法受限所以如果你不开发 App,那么不需要使用 nvue。

如果你熟悉 weex 或 react native 开发,那么开发app时, nvue 是你的更优选择,能切实提升你的开发效率,降低成本。

如果你是 web 前端,不熟悉原生排版,那么建议你仍然以使用 vue 页面为主,在 App 端某些 vue 页面表现不佳的场景下使用 nvue 作为强化补充。这些场景在官方文档中有介绍。

新建页面

uni-app中的页面,默认保存在工程根目录下的pages目录下。

每次新建页面,均需在pages.json中配置pages列表;未在pages.json -> pages 中注册的页面,uni-app会在编译阶段进行忽略。

通过HBuilderX开发 uni-app 项目时,在 uni-app 项目上右键新建页面,HBuilderX会自动pages.json中完成页面注册,非常更方便。同时,HBuilderX 还内置了常用的页面模板(如图文列表、商品列表等,区别于之前提到的项目模板),选择这些模板,可以大幅提升你的开发效率。

新建页面时,可以选择是否创建同名目录。创建目录的意义在于:

  • 如果你的页面较复杂,需要拆分多个附属的js、css、组件等文件,则使用目录归纳比较合适。

  • 如果只有一个页面文件,大可不必多放一层目录。

删除页面

删除页面时,需做两件工作:

  • 删除.vue文件、.nvue.uvue文件
  • 删除pages.json -> pages列表项中的配置 (如使用HBuilderX删除页面,会在状态栏提醒删除pages.json对应内容,点击后会打开pages.json并定位到相关配置项)

页面改名

操作和删除页面同理,依次修改文件和 pages.json

页面结构

页面文件结构就是vue文件呗,学过vue的都比较熟悉了。

页面生命周期

无论组件还是页面,都是vue文件,但区别在于,注册在pages.json -> pages中的组件才能算作页面。**uni-app 页面,由于既是组件又页面,所以除支持 Vue 组件生命周期外,还支持页面生命周期函数。**

当以组合式 API 使用时,在 Vue2 和 Vue3 中存在一定区别。如果使用的是vue3语法,这些uni-app页面独有的生命周期函数,使用前也要按需导入。

函数名说明
onLoad监听页面加载,该钩子被调用时,响应式数据、计算属性、方法、侦听器、props、slots 已设置完成,其参数为上个页面传递的数据,参数类型为 Object(用于页面传参),参考示例
onShow监听页面显示,页面每次出现在屏幕上都触发,包括从下级页面点返回露出当前页面
onReady监听页面初次渲染完成,此时组件已挂载完成,DOM 树($el)已可用,注意如果渲染速度快,会在页面进入动画完成前触发
onHide监听页面隐藏
onUnload监听页面卸载
onResize监听窗口尺寸变化
onPullDownRefresh监听用户下拉动作,一般用于下拉刷新
onReachBottom页面滚动到底部的事件(不是scroll-view滚到底),常用于下拉下一页数据。
onShareAppMessage用户点击右上角分享
onPageScroll监听页面滚动,参数为Object
onShareTimeline监听用户点击右上角转发到朋友圈
onAddToFavorites监听用户点击右上角收藏

页面生命周期,说白了就是页面会自动监听某些事件,并执行对应的回调函数。

官方文档还给出了生命周期流程图,下面只给出使用vue2开发的页面声明周期流程图,与vue3对应的流程图区别只在于部分生命周期函数的名称不同(beforeDestory->beforeUnmount,Destoryed->Unmounted)

uni-app组成和跨端原理

组成

uni-app代码编写,基本语言包括js、vue、css。以及ts、scss等css预编译器。在app端,还支持原生渲染的nvue,以及可以编译为kotlin和swift的uts

跨端原理

uni-app分为编译器和运行时(runtime)。uni-app能实现一套代码、多端运行,是通过这2部分配合完成的。

编译器将开发者的代码进行编译,编译的输出物(每个平台支持的特有代码)由各个终端的runtime进行解析,每个平台(Web、Android App、iOS App、各家小程序)都有各自的runtime

uni-app的编译器基于webpack或者vite,能把基于uni-app规范编写的代码,编译,打包成能在特定平台上运行的代码,并这个过程中集成相应的runtime;编译打包后的代码文件,还会被组织成在结构上符合特定平台规范的项目

编译器

编译器运行在电脑开发环境。一般是内置在HBuilderX工具中,也可以使用独立的cli版。

使用HBuilderX可视化界面创建的项目,编译器在HBuilderX的安装目录下的plugin目录,随着HBuilderX的升级会自动升级编译器。

开发者按uni-app规范编写代码,由编译器将开发者的代码编译生成特定平台支持的代码

  • 在web平台,将.vue文件编译为js,css代码。与普通的vue cli项目类似
  • 在微信小程序平台,编译器将.vue文件拆分生成wxml、wxss、js等代码。
  • 在app平台,将.vue文件编译为js代码

编译器分vue2版和vue3版

  • vue2版:基于webpack实现
  • vue3版:基于Vite实现,性能更快

这些工具本身都是基于Node.js平台开发的,因此在你的本地开发环境中必须安装 Node.js 来运行这些构建脚本。

编译器支持条件编译。在同一份代码中,根据目标平台的不同,只编译并保留对应平台的代码,其他平台的代码被忽略,减小打包体积

这解决了多端兼容问题,避免写大量 if-else 判断

1
2
3
// #ifdef  App
console.log("这段代码只有在App平台才会被编译进去。非App平台编译后没有这段代码")
// #endif

运行时环境

uni-app在每个平台都准备了相应的runtime,会在编译的过程中集成到项目中。

  • uni-app给小程序端提供的runtime,主要是一个小程序版的vue runtime,页面路由、组件、api等方面基本都是转义。
  • uni-app给web端提供的runtime,相比普通的vue项目,多了一套ui库、页面路由框架、和uni对象(即常见API封装)
  • uni-app给App端提供的runtime更复杂,可以先简单理解为DCloud也有一套小程序引擎,打包app时将开发者的代码和DCloud的小程序打包成了apk或ipa。

uni-app提供的runtime包括3部分:基础框架、组件、API

基础框架:

  • 在web和小程序上,不需要uni-app提供js引擎排版引擎。因为浏览器有浏览器内核,可以执行渲染和运行js代码的工作;各个小程序平台也有自己的webviewjs引擎;但安卓上,需要uni-app提供谷歌v8引擎,webview则使用系统自带的;ios上也不需要uni-app提供js引擎和webview,都使用ios系统自带的。
  • App的渲染引擎:同时提供了2套渲染引擎.vue页面文件由webview渲染,原理与小程序相同;.nvue页面文件由weex原生渲染。

组件

  • 在小程序端,uni-app基础组件会直接转义为小程序自己的内置组件。提供给小程序的runtime中,基础组件不占体积
  • 在web和android、iOS端,这几十个组件都在uni-app的runtime中,会占用一定体积,相当于内置了一套ui库。

api

  • uni-app runtime内置了大量常见的、跨端的 API,比如联网(uni.request)、读取存储(uni.getStorage)
  • 使用uni-app的标准API,可以跨端使用。但对于不跨端的部分(就是某个平台的特色api),仍可以调用该端的专有API。由于常见的API都已经被封装内置,所以日常开发时,开发者只需关注uni标准API,当需要调用特色端能力时在条件编译里编写特色API调用代码。
  • 小程序平台:uni对象会转为小程序的自有对象,比如在微信小程序平台,编写uni.request等同于wx.request。
  • web平台:window、dom等浏览器专用API仍可以使用

逻辑层和渲染层分离

在web平台,逻辑层(js)和渲染层(html、css),都运行在统一的webview(浏览器内核)里,也就是说web平台的逻辑层和渲染层并没有分离。 但在小程序和app端,逻辑层和渲染层被分离了。逻辑层独立成了单独的js引擎(jscore或者v8引擎),负责执行业务逻辑;渲染层仍然是webview,负责页面渲染。简单来说,就是由单线程转换成了双线程。要注意的是这里的app端,指的是混合开发的app,即混合了前端开发技术和app原生开发技术,而不是原生app;使用uni-app开发的app都是混合app,原生app是不涉及前端技术:比如js的。

分离的核心原因是性能。过去很多开发者吐槽基于webview的app性能不佳,很大原因是js运算和界面渲染抢资源导致的卡顿,这一也说明,webview其实是能执行js代码的,只不过为了提高性能,现在只被用来渲染。

逻辑层和渲染层分离的利与弊

逻辑层和渲染层分离,好处是 js 运算不阻塞渲染,缺点是,逻辑层和渲染层属于2个不同的线程,不同线程之间的通信是有一定延时的。

逻辑层详解

逻辑层是运行在一个独立的js引擎里的,它不依赖于本机的 webview,所以一方面它没有浏览器兼容问题,可以在 Android4.4 上跑 es6 代码;另一方面,它无法运行 windowdocumentnavigatorlocalstorage 等浏览器专用的 js API。

比如,jscore就是一个标准 js 引擎,可以正常运行标准 js(ECMAscript) ,比如 if、for、各种字符串、日期处理等。

  • 所谓浏览器 js 引擎,就是 jscorev8 的基础上新增了一批浏览器专用 API,比如 dom;
  • node.js 引擎,则是 v8 基础上补充一些电脑专用 API,比如本地 io;
  • 小程序端的 js 引擎,其实是在 jscore 上补充了一批手机端常用的 JS API,比如扫码;
  • 安卓端的js引擎,则是在谷歌v8引擎的基础上添加了一些手机端常用的 JS API;ios端的js引擎则是基于iOS操作系统提供的jscore

视图层详解

视图层交由webview渲染,所以什么是webview?webview是移动端原生应用中的嵌入式浏览器控件,用于在原生应用中加载网页内容,实现混合开发。

  • 在web平台并没有webview的概念,在浏览器中,渲染(浏览器引擎的布局与绘制)和js代码的执行(逻辑)共享同一线程。
  • 而小程序平台有自己的webview组件,本质也类似浏览器内核。
  • 在 iOS 上,只能使用 iOS 提供的 Webview(默认是 WKWebview)。它有一定的兼容问题,iOS 版本不同,它的表现有细微差异。
  • 安卓App 端默认使用了 Android system webview,这个安卓系统 webview 跟随手机不同而有差异。当然 App 端也支持使用腾讯 X5 引擎,此时可以在 Android 端统一视图层。

js语法

浏览器js与标准js

uni-app的js API由标准ECMAScript的js API 和 uni扩展API 这两部分组成。

标准ECMAScript的js仅是最基础的js。浏览器基于它扩展了window、document、navigator等对象。小程序也基于标准js扩展了各种wx.xx、my.xx、swan.xx的API。node也扩展了fs等模块。所以说浏览器js不等于标准js。

ES6 支持

uni-app 在支持绝大部分 ES6 API 的同时,也支持了 ES7 的 await/async,具体支持情况参考官方文档。

在App端JS脚本运行在独立的JS引擎(jscore)中,vue页面使用系统webview渲染,nvue页面使用系统原生View渲染。

Android平台

  • JS脚本运行在独立Google V8引擎中,因此支持的语法与Android系统版本无关
  • vue页面渲染在系统Webview(android system webview)中,受Android系统版本影响,当然也可以使用x5等三方webview来拉齐实现。
  • nvue页面使用系统原生View渲染

iOS平台

  • JS脚本运行在iOS操作系统提供的JsCore 引擎,因此支持的语法与iOS系统有关
  • vue页面渲染在系统WKWebview中,受iOS系统版本影响
  • nvue页面使用系统原生View渲染

css语法

处理器支持

uni-app 支持less、sass、scss、stylus等预处理器,但是需要安装相应的插件。

尺寸单位

uni-app 支持的通用 css 单位包括 px、rpx。

  • px 即屏幕像素
  • rpx 即响应式 px,一种根据屏幕宽度自适应的动态单位。以 750 宽的屏幕为基准,750rpx 恰好为屏幕宽度。屏幕变宽,rpx 实际显示效果会等比放大,但在 App(vue2 不含 nvue) 端和 H5(vue2) 端屏幕宽度达到 960px 时,默认将按照 375px 的屏幕宽度进行计算,具体配置参考:rpx 计算配置

vue 页面还支持rem,vw,vhnvue页面还不支持百分比单位。

样式导入

使用@import语句可以导入外联样式表,@import后跟需要导入的外联样式表的相对路径,用;表示语句结束。

在模块化开发环境中,还可以使用import "../../common/uni.css"的方式导入css文件。

示例代码:

1
2
3
4
5
6
<style>
@import "../../common/uni.css";
.uni-card {
box-shadow: none;
}
</style>

选择器

支持的选择器包括类选择器,id选择器,标签选择器,并集选择器,伪元素选择器。

注意:

  • uni-app 中不能使用 * 选择器。

  • 微信小程序自定义组件中仅支持 class 选择器

  • page 相当于 body 节点:

    1
    2
    3
    4
    <!-- 设置页面背景颜色,使用 scoped 会导致失效 -- >
    page {
    background-color: #ccc;
    }

全局样式与局部样式

定义在 App.vue 中的样式为全局样式,作用于每一个页面。在 pages 目录下 的 vue 文件中定义的样式为局部样式,只作用在对应的页面,并会覆盖 App.vue 中相同的选择器。

注意:

  • App.vue 中通过 @import 语句,可以导入外联样式,一样作用于每一个页面。
  • nvue 页面暂不支持全局样式

css变量

uni-app 提供内置 CSS 变量

CSS 变量描述App小程序H5
--status-bar-height系统状态栏高度系统状态栏高度25px0
--window-top内容区域距离顶部的距离00NavigationBar 的高度
--window-bottom内容区域距离底部的距离00TabBar 的高度

var(--status-bar-height) 此变量在微信小程序环境为固定 25px,在 App 里为手机实际状态栏高度。

当设置 "navigationStyle":"custom" 取消原生导航栏后,由于窗体为沉浸式,占据了状态栏位置。此时可以使用一个高度为 var(--status-bar-height) 的 view 放在页面顶部,使用sticky布局,避免页面内容压住状态栏。

由于在 H5 端,不存在原生导航栏和 tabbar,也是前端 div 模拟。如果设置了一个固定位置的居底 view,在小程序和 App 端是在 tabbar 上方,但在 H5 端会与 tabbar 重叠。此时可使用--window-bottom,不管在哪个端,都是固定在 tabbar 上方。

背景图片

uni-app 支持使用在 css 里设置背景图片,使用方式与普通 web 项目大体相同,但需要注意以下几点:

  • 支持 base64 格式图片。

  • 支持网络路径图片。

  • 小程序不支持在 css 中使用本地文件,包括本地的背景图和字体文件。需以 base64 方式方可使用。

  • 对于小程序,在css中使用本地图片是不被支持的,如果非要使用,当图片大小小于40kb,uni-app则会帮你把它转化成base64格式,否则需要手动转化成base64格式或者网络图片,否则就会出错。

字体文件同理。

组件

自定义组件

通过uni-app的easycom, 将组件引入精简为一步。只要自定义组件安装在项目的 components 目录下,并符合 components/组件名称/组件名称.vue 目录结构。就可以不用引用、不注册,直接在页面中使用,非常方便。

说明

easycom自动开启的,不需要手动开启,有需求时可以在pages.jsoneasycom节点进行个性化设置,如关闭自动扫描,或自定义扫描匹配组件的策略。

1
2
3
4
5
6
7
8
"easycom": {
"autoscan": true,//默认开启
//以正则方式自定义组件匹配规则。如果autoscan不能满足需求,可以使用custom自定义匹配规则
"custom": {
"^uni-(.*)": "@/components/uni-$1.vue", // 匹配components目录内的vue文件
"^vue-file-(.*)": "packageName/path/to/vue-file-$1.vue" // 匹配node_modules内的vue文件
}
}

easycom方式引入的组件无需在页面内import,也不需要在components内声明,即无需引入无需注册,可在任意页面使用。

easycom方式引入组件不是全局引入,而是局部引入。例如在H5端只有加载相应页面,才会加载使用的组件。

在组件名完全一致的情况下,easycom引入的优先级低于手动引入(区分连字符形式与驼峰形式)。

easycom只处理vue组件,不处理小程序专用组件(如微信的wxml格式组件)

pages.json 页面路由 | uni-app官网

下载组件

uni-app有许多内置的组件,比如view,scroll-view。还有许多官方提供的扩展组件。

如果使用的是uni-ui模板的项目,则这些扩展组件都在创建项目的时候下载好了(都下载到了根目录下的uni_modules文件中),这样的好处就是不需要手动下载组件,直接使用即可;而且这些组件并不是会无条件地,全部被打包进最终文件。具体来说,打包工具(如 Webpack 或 Vite)会根据你实际使用的组件,来决定哪些内容会被包含在最终的应用程序中(tree-shaking)。

如果你没有创建uni-ui项目模板,也可以在你的工程里,通过 uni_modules 单独安装需要的某个组件。导入指定项目后直接使用即可,这些组件也无需import和注册,就可以直接使用,也是得益于eazycom机制。简单的来说,下载的第三方组件和在components目录中定义的组件一样,不许要注册和导入,就可以直接使用

image

<image> 组件具有默认的宽高:320*240,通常令其宽度为100%,完全贴合父元素,然后高度会等于内部图片的高度。

<image> 组件支持多种缩放模式,用于控制图片在其容器中的显示方式。这些缩放模式通过 mode 属性来设置:

scaleToFill:不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素。

aspectFit:保持纵横比缩放图片,保证图片的全部内容可见,且尽可能大地显示图片。图片可能会被包含在一个矩形框内,同时保持其比例不变。

aspectFill:保持纵横比缩放图片,并且只保证图片的短边能完整显示出来,而长边部分区域可能会被裁剪掉。

widthFix:宽度固定不变,高度随宽度自适应变化,以保持图片的比例。这种模式通常用于需要确保图片宽度一致的情况下。

heightFix:与 widthFix 类似,但这里高度是固定的,宽度随高度调整。

video

uni-app中video组件不同于h5中的video标签,其内置了许多功能,比如弹幕系统(弹幕系统不完善,只能设置滚动弹幕,而且在app端添加弹幕系统会导致应用闪退),滑动页面会自动调节播放进度。

由于在uni-app中只能让video进入全屏状态,且全屏状态自定义的controls无法展示,所以全屏状态只能使用默认的controls。

video组件在app端存在高度溢出问题,需要使用max-height:100%来限制,而且即便不进入全屏,也会压住所有元素(如果有位置重合的话),需要通过cover-viewcover-image来解决

用来替换web开发中的a标签。使用url属性来表示跳转的地址,但只能跳转本地页面。目标页面必须在pages.json中注册,其中的open-type属性用来指定跳转的方式,模拟那几个路由跳转api。

swiper

swiper的子元素是一个个swiper-item组件,swiper-item组件宽高自动设置为100%。注意:宽高100%是相对于其父组件swiper,不是相对于子组件,不能被子组件自动撑开。

然后在swiper-item中放入image组件,设置mode=widthFill再设置image宽度为100%,即可实现一个简单美观的轮播图。

uni-popup

使用频率非常高的弹窗组件,在uni-popup未被展示出来前,其内容不的结构都不会被渲染,隐藏uni-popup后,其内部的结构(包括组件)又会被销毁。

常用api

概述

uni-app的 js API ,由标准JS 和 uni 扩展 API 这两部分组成。

标准 ECMAScript 的 js 仅是最基础的 js。浏览器基于它扩展了 window、document、navigator 等对象。小程序也基于标准 js 扩展了各种 wx.xx、my.xx、swan.xx 的 API。node 也扩展了 fs 等模块。

uni-app 基于标准js扩展了 uni 对象,uni-app的几乎所有API都包含在这个对象上,并且 API 命名与小程序保持兼容。

除了 uni-app 框架内置的跨端 API(会被自动编译成特定平台对应的api),各端自己的特色 API 也可通过条件编译自由使用。

API Promise 化

异步的方法,如果不传入 success、fail、complete 等 callback 参数,将以 Promise 返回数据。例如:uni.getImageInfo()

1
2
3
4
5
6
7
8
9
10
11
12
// 正常使用
const task = uni.connectSocket({
success(res){
console.log(res)
}
})
// Promise 化
uni.connectSocket().then(res => {
// 此处即为正常使用时 success 回调的 res
// uni.connectSocket() 正常使用时是会返回 task 对象的,如果想获取 task ,则不要使用 Promise 化
console.log(res)
})

原来不用自己new Promise()啊😂

网络请求uni.request

uni.request(obj)uni-app 跨端网络请求的核心 API,封装了各端(H5、小程序、App)的原生请求能力,提供统一的调用方式。-

1
2
3
4
5
6
7
8
9
10
11
12
uni.request({
url: 'https://api.example.com/data',
method: 'GET',
data: { id: 1 },
header: { 'Content-Type': 'application/json' },
success(res) {
console.log(res.data);
},
fail(err) {
console.error(err);
}
});

object(传入api的对象)的常用属性:

url

url (String) 必填,请求的目标地址

  • 必须是 HTTPS(小程序强制要求)
  • 域名必须在各平台白名单中配置(如微信小程序需在后台配置 request 域名)
  • 支持相对路径(需配合 baseURL 手动处理)

data

data (Object/String/ArrayBuffer),发送给服务器的参数(请求体),会自动序列化:

  • Object → 自动转为 application/x-www-form-urlencodedapplication/json(取决于 header)
  • String → 原样发送
  • ArrayBuffer → 用于上传二进制数据(如图片)

header

header (Object),设置请求头,比如

1
2
3
4
5
header: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token,
'X-Client': 'uni-app'
}

限制:

  • 不能设置 Referer(小程序禁止)
  • 小程序对 header 键名大小写不敏感

method

method (String) ,默认 GET
有效值:GET、POST、PUT、DELETE、HEAD、OPTIONS、TRACE、CONNECT
注意:

  • 小程序支持所有方法
  • 某些服务器可能限制方法
  • method有效值必须大写,每个平台支持的method有效值不同

dataType

dataType (String) , 默认 json,自动对返回数据调用 JSON.parse()
其他值(如 ‘text’):不解析,返回原始字符串,即使服务器返回 JSON,如果 dataType 不是 ‘json’,res.data 是字符串!

responseType

responseType (String) ,默认 text,指定响应数据类型

合法值:

  • 'text':字符串(默认)
  • 'arraybuffer':二进制数据(用于下载文件、图片处理)
1
2
3
4
5
6
7
8
9
// 下载图片并转为 base64
uni.request({
url: '/api/image',
responseType: 'arraybuffer',
success(res) {
const base64 = uni.arrayBufferToBase64(res.data);
this.imageSrc = 'data:image/png;base64,' + base64;
}
});

withCredentials

withCredentials (Boolean) ,默认 false,表示跨域请求时是否携带 Cookie,类似于 fetchcredentials: 'include'

用于需要登录态的接口,同时服务器需设置 Access-Control-Allow-Credentials: true

defer

defer (Boolean) , 默认 false(uni-app 3.0+)表示是否延迟请求,直到首屏内容渲染完成后再发送,用于优化首屏加载性能

适合非关键接口(如埋点、推荐列表)

建议:首屏关键接口(如用户信息)不要用 defer: true

success

success (Function),请求响应成功的回调函数,参数 res 对象包含:

1
2
3
4
5
6
{
data: {}, // 服务器返回数据(已解析)
statusCode: 200, // HTTP 状态码
header: {}, // 响应头
cookies: [] // 服务器 set-cookie 的 cookies(App 端)
}

fail

fail (Function),失败回调参数err对象包含:

1
2
3
4
5
6
7
{
errCode: 1, // 错误码(uni-app 定义)
errSubject: 'Request', // 错误主题
data: '', // 可能的响应数据(部分情况)
cause: 'timeout', // 原因(如 timeout, abort)
errMsg: 'request:fail timeout' // 错误信息
}

常见 errCode

  • 1:参数错误
  • 2:网络连接超时
  • 3:网络请求失败

complete

complete (Function),无论成功失败都会执行,适合做隐藏 loading结束 loading 动画

1
2
3
4
5
6
7
uni.showLoading();
uni.request({
// ... 配置
complete() {
uni.hideLoading(); // 无论成功失败都隐藏
}
});

uni.request的返回值

如果在调用uni.request时,传入 success / fail / complete 了参数中的一个,则返回的是一个普通的requestTask对象,可以用来中断请求。

1
2
3
4
5
var requestTask = uni.request({
url: 'https://www.example.com/request', //仅为示例,并非真实接口地址。
complete: ()=> {}
});
requestTask.abort();//中断请求

如果没有传入 success / fail / complete 参数,则会返回封装后的 Promise 对象,也可以用来中断请求

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var requestTask = uni.request({
url: 'https://api.example.com/data'
});
// 使用 Promise 风格处理
requestTask.then(([err, res]) => {
if (err) {
console.error('请求失败', err);
} else {
console.log('请求成功', res.data);
}
});
// 依然可以中断请求
setTimeout(() => {
requestTask.abort(); // ✅ 仍然可以中断!
}, 1000);

路由跳转

uni.navigateTo(object)

  • 只能跳转到非tabbar页面保留当前页面
  • 路由API的目标页面必须是在pages.json里注册的vue页面,如果想打开web url,参考官方文档。

object常用参数说明:

  • url(String): 必填 ,表示需要跳转的,应用内非 tabBar 的页面的路径 , 路径后可以带参数。参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔;如 'path?key=value&key2=value2',path为下一个页面的路径,下一个页面的onLoad函数可得到传递的参数。
  • success(Function) ,非必填,接口调用成功的回调函数
  • fail(Function),非必填,接口调用失败的回调函数
  • complete(Function),非必填,接口调用结束的回调函数(调用成功、失败都会执行)
1
2
3
4
//在起始页面跳转到test.vue页面并传递参数
uni.navigateTo({
url: 'test?id=1&name=uniapp'
});
1
2
3
4
5
6
7
// 在test.vue页面接受参数
export default {
onLoad: function (option) { //option为object类型,会序列化上个页面传递的参数
console.log(option.id); //打印出上个页面传递的参数。
console.log(option.name); //打印出上个页面传递的参数。
}
}

uni.navigateBack(object)

返回上一页面或多级页面,关闭当前页面uni.navigateBack 可以回退到 tabBar 页面,但前提是该 tabBar 页面必须存在于当前页面栈中。可通过 getCurrentPages() 获取当前的页面栈,决定需要返回几层。

object参数说明:

参数类型必填默认值说明
deltaNumber1返回的页面数,如果 delta 大于现有页面数,则返回到首页。
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
// 注意:调用 navigateTo 跳转时,调用该方法的页面会被加入堆栈,而 redirectTo 方法则不会。见下方示例代码
// 此处是A页面
uni.navigateTo({
url: 'B?id=1'
});
// 此处是B页面
uni.navigateTo({
url: 'C?id=1'
});
// 在C页面内 navigateBack,将返回A页面
uni.navigateBack({
delta: 2
});

uni.redirectTo(object)

跳转到应用内的某个页面,不能跳转到 tabBar 页面,关闭当前页面。和navigateTo的主要区别是这个api会关闭当前页面,而navigateTo不会

object参数说明:

参数类型必填说明
urlString需要跳转的应用内非 tabBar 的页面的路径,路径后可以带参数。参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔;如 ‘path?key=value&key2=value2’
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

uni.reLaunch(object)

关闭所有页面,打开到应用内的某个页面,包括tabbar页面。

object参数说明:

参数类型必填说明
urlString需要跳转的应用内页面路径 , 路径后可以带参数。参数与路径之间使用?分隔,参数键与参数值用=相连,不同参数用&分隔;如 ‘path?key=value&key2=value2’,如果跳转的页面路径是 tabBar 页面则不能带参数
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

uni.switchTab(object)

跳转到 tabBar 页面,并关闭其他所有非 tabBar 页面。在路径中不能携带参数。

object参数说明:

参数类型必填说明
urlString需要跳转的 tabBar 页面的路径(需同时在 pages.json 的 tabBar ,pages字段定义的页面),路径后不能带参数
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

总结

uni.navigateTo:不关闭当前页面跳转到非tabbar页面

uni.redirectTo:关闭当前页面跳转到非tabbar页面

uni.reLaunch:关闭所有页面跳转到任意页面

uni.switchTab:关闭所有非tabbar页面,跳转到tabbar页面

uni.navigateBack:关闭当前页面,回退到历史记录栈中的一个页面

媒体

uni.chooseImage(object)

从本地相册选择图片或使用相机拍照。

App端如需要更丰富的相机拍照API(如直接调用前置摄像头),参考plus.camera

object参数说明:

参数名类型必填说明
countNumber最多可以选择的图片张数,默认9
sizeTypeArray<String>original 原图,compressed 压缩图,默认二者都有
extensionArray<String>根据文件拓展名过滤,每一项都不能是空字符串。默认不过滤。
sourceTypeArray<String>album 从相册选图,camera 使用相机,默认二者都有。如需直接开相机或直接选相册,请只使用一个选项
cropObject图像裁剪参数,设置后 sizeType 失效
successFunction成功则返回图片的本地文件路径列表tempFilePaths
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

success 返回参数说明

参数类型说明
tempFilePathsArray<String>图片的本地文件路径列表
tempFilesArray<Object>、Array<File>图片的本地文件列表,每一项是一个 File 对象

示例:

1
2
3
4
5
6
7
8
uni.chooseImage({
count: 6, //默认9
sizeType: ['original', 'compressed'], //可以指定是原图还是压缩图,默认二者都有
sourceType: ['album'], //从相册选择
success: function (res) {
console.log(JSON.stringify(res.tempFilePaths));
}
});

File 对象结构如下

参数类型说明
pathString本地文件路径
sizeNumber本地文件大小,单位:B
nameString包含扩展名的文件名称,仅H5支持
typeString文件类型,仅H5支持

uni.previewImage(object)

预览图片。

OBJECT 参数说明:

参数名类型必填说明
currentString/Number详见下方说明详见下方说明
showmenuBoolean是否显示长按菜单,默认值为 true
urlsArray<String>需要预览的图片链接列表
indicatorString图片指示器样。可取值:”default” - 底部圆点指示器; “number” - 顶部数字指示器; “none” - 不显示指示器。
loopBoolean是否可循环预览,默认值为 false
longPressActionsObject长按图片显示操作菜单,如不填默认为保存相册
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

关于current详细介绍参考官方文档。

uni.closePreviewImage(object)

object 参数说明:

参数名类型必填说明
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

uni.getImageInfo(object)

获取图片信息。

小程序下获取网络图片信息需先配置download域名白名单才能生效。

object参数说明:

参数名类型必填说明
srcString图片的路径,可以是相对路径,临时文件路径,存储文件路径,网络图片路径
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

success 返回参数说明

参数名类型说明
widthNumber图片宽度,单位px
heightNumber图片高度,单位px
pathString返回图片的本地路径
typeString返回图片的格式

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
uni.chooseImage({
count: 1,
sourceType: ['album'],
success: function (res) {
uni.getImageInfo({
src: res.tempFilePaths[0],//传入第一个图片临时路径
success: function (image) {
console.log(image.width);
console.log(image.height);
}
});
}
});

uni.saveImageToPhotosAlbum(object)

保存图片到系统相册。

object参数说明:

参数名类型必填说明
filePathString图片文件路径,可以是临时文件路径也可以是永久文件路径,不支持网络图片路径
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

success 返回参数说明

参数名类型说明
pathString保存到相册的图片路径,仅 App 3.0.5+ 支持
errMsgString调用结果

注意

  • 可以通过用户授权API来判断用户是否给应用授予相册的访问权限https://uniapp.dcloud.io/api/other/authorize
  • H5没有API可触发保存到相册行为,下载图片时浏览器会询问图片存放地址。
  • 微信小程序在2023年10月17日之后,使用API需要配置隐私协议

示例

1
2
3
4
5
6
7
8
9
10
11
12
uni.chooseImage({
count: 1,
sourceType: ['camera'],
success: function (res) {
uni.saveImageToPhotosAlbum({
filePath: res.tempFilePaths[0],
success: function (res) {
console.log('save success',res.path);
}
});
}
});

文件

uni.saveFile(object)

OBJECT 参数说明:

参数名类型必填说明
tempFilePathString需要保存的文件的临时路径,只能是临时文件路径。
successFunction返回文件的保存路径,res = {savedFilePath: ‘文件的保存路径’}
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

success 返回参数说明:

参数说明
savedFilePath文件的保存路径

示例:

1
2
3
4
5
6
7
8
9
10
11
uni.chooseImage({
success: function (res) {
var tempFilePaths = res.tempFilePaths;
uni.saveFile({
tempFilePath: tempFilePaths[0],//选择第一个临时文件路径
success: function (res) {
var savedFilePath = res.savedFilePath;//保存成功后返回的文件路径
}
});
}
});

界面

uni.showToast(object)

显示消息提示框。

object参数说明:

参数类型必填说明
titleString提示的内容,长度与 icon 取值有关。
iconString图标,有效值详见下方说明,默认:success。
imageString自定义图标的本地路径(app端暂不支持gif)
maskBoolean是否显示透明蒙层,防止触摸穿透,默认:false
durationNumber提示的延迟时间,单位毫秒,默认:1500
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

uni.hideToast()

隐藏消息提示框。

uni.showLoading(object)

显示 loading 提示框, 需主动调用 uni.hideLoading 才能关闭提示框。

object参数说明:

参数类型必填说明
titleString提示的文字内容,显示在loading的下方
maskBoolean是否显示透明蒙层,防止触摸穿透,默认:false
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

uni.hideLoading()

隐藏 loading 提示框。

示例

1
2
3
4
5
6
7
uni.showLoading({
title: '加载中'
});

setTimeout(function () {
uni.hideLoading();
}, 2000);

uni.showModal(object)

显示模态弹窗,可以只有一个确定按钮,也可以同时有确定和取消按钮

object参数说明

参数类型必填说明
titleString提示的标题
contentString提示的内容
showCancelBoolean是否显示取消按钮,默认为 true
cancelTextString取消按钮的文字,默认为”取消”
cancelColorHexColor取消按钮的文字颜色,默认为”#000000”
confirmTextString确定按钮的文字,默认为”确定”
confirmColorHexColor确定按钮的文字颜色,H5平台默认为”#007aff”,微信小程序平台默认为”#576B95”,百度小程序平台默认为”#3c76ff”
editableBoolean是否显示输入框
placeholderTextString显示输入框时的提示文本
successFunction接口调用成功的回调函数
failFunction接口调用失败的回调函数
completeFunction接口调用结束的回调函数(调用成功、失败都会执行)

success返回参数说明

参数类型说明
confirmBoolean为 true 时,表示用户点击了确定按钮
cancelBoolean为 true 时,表示用户点击了取消(用于 Android 系统区分点击蒙层关闭还是点击取消按钮关闭)
contentStringeditable 为 true 时,值为用户输入的文本

示例

1
2
3
4
5
6
7
8
9
10
11
uni.showModal({
title: '提示',
content: '这是一个模态弹窗',
success: function (res) {
if (res.confirm) {
console.log('用户点击确定');
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});

扩展:微信小程序

微信小程序的一键登录怎么做

调用uni.login 接口需要用户已经完成小程序验证,否则无法调用,完成小程序验证要300元/年

  • 先调用uni.login 方法,这会发送一个请求给微信服务器,得到一个临时登录凭证code(临时登录凭证,有效期 5 分钟,只能使用一次,这个登录凭证和微信用户信息是绑定的)

  • 然后调用uni.request方法将这个临时登录凭证,发送到小程序的后端服务器,小程序后端服务器将这个临时登录凭证code,连同小程序的APPID和APPSECRET(小程序密钥,只能在后端使用,不能暴露在前端!)发送给微信服务器,换取openid, session_key,然后返回一个token给小程序,用户的后续请求都会携带这个token,返回的token中就包含openid

  • openid表示用户在当前小程序下唯一标识(每个用户 + 每个小程序,唯一),计算方式为:openid = hash(用户微信号 + 小程序 AppID),同一个用户在同一个微信小程序中,无论登录多少次,得到的 openid 都是完全一致的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
async wechatLogin() {
try {
// 调用微信登录 API
const [err, res] = await uni.login({
provider: 'weixin' // 注意:在小程序中可省略
});
if (err) {
throw err;
}
const code = res.code;
// 将 code 发送到你的小程序服务器
const loginRes = await uni.request({
url: 'https://your-api.com/api/login',
method: 'POST',
data: { code }
});
const { token, userInfo } = loginRes[1].data;
// 保存 token 到本地
uni.setStorageSync('token', token);
uni.setStorageSync('userInfo', userInfo);
// 跳转到首页
uni.switchTab({
url: '/pages/index/index'
});
} catch (error) {
uni.showToast({
title: '登录失败',
icon: 'error'
});
}
}

如果还需要用户昵称、头像等信息,则需要调用:

1
2
3
4
5
6
7
8
9
10
11
// 获取用户信息(需用户点击按钮授权)
uni.getUserProfile({
desc: '用于完善用户资料',
success: (res) => {
//res.userInfo中包含用户的昵称和头像
console.log('用户信息:', res.userInfo);
},
fail: () => {
uni.showToast({ title: '授权失败', icon: 'none' });
}
});

注意:getUserProfile 已被限制使用,推荐使用 <button open-type="getUserInfo"> 组件方式触发。

后端代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// server.js
const express = require('express');
const request = require('request');
const jwt = require('jsonwebtoken'); // 用于生成 token
const app = express();
app.use(express.json());

// 微信配置
const APPID = 'your-appid';
const APPSECRET = 'your-appsecret';
const WECHAT_LOGIN_URL = `https://api.weixin.qq.com/sns/jscode2session`;

app.post('/api/login', (req, res) => {
const { code } = req.body;
if (!code) {
return res.status(400).json({ error: '缺少 code' });
}

// 请求微信服务器换取 openid 和 session_key
const url = `${WECHAT_LOGIN_URL}?appid=${APPID}&secret=${APPSECRET}&js_code=${code}&grant_type=authorization_code`;

request.get(url, (error, response, body) => {
if (error) {
return res.status(500).json({ error: '网络错误' });
}
const result = JSON.parse(body);
const { openid, session_key } = result;
// 生成自定义登录态 token(例如 JWT)
const token = jwt.sign(
{ openid }, //返回用户在该小程序下的唯一凭证
'your-secret-key', // 加密token的密钥
{ expiresIn: '7d' } // 这个token在七天后过期
);
// (可选)查询或创建用户
// const user = findOrCreateUser(openid);
res.json({
token,
userInfo: {
openid
}
});
});
});

在微信小程序上怎么做微信支付

想在微信小程序中实现支付功能,需要先满足一下条件:

  • 小程序已经上线,个人小程序不支持支付
  • 个人类小程序上线,不需要支付 300 元认证费,但必须完成微信实名认证(绑定身份证)完成后可以: 提交代码审核
    , 发布上线, 使用基础功能但个人小程序功能受限,不支持:微信支付,获取用户手机号,广告组件,附近的小程序
  • pay.weixin.qq.com 申请微信商户号

然后执行如下步骤:

  • 前端请求后端创建订单,后端调用微信统下单 API,生成requestPayment所需参数并返回
  • 前端调用uni.requestPayment调起支付,用户输入密码完成支付
  • 支付成功后,微信服务器异步调用小程序后端的支付回调接口,后端更新订单状态,返回 SUCCESS
  • 前端跳转支付成功页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<template>
<view>
<button @click="handlePay">立即支付</button>
</view>
</template>

<script>
export default {
methods: {
async handlePay() {
try {
// 1. 获取用户token
const token = uni.getStorageSync('token');
// 2. 前端请求后端创建订单
const orderRes = await uni.request({
url: 'https://your-api.com/api/order/create',
method: 'POST',
header: { 'Authorization': token },
data: {
productId: 123,
totalFee: 100, // 1元 = 100分
body: '测试商品'
}
});
const payParams = orderRes[1].data;
// 3. 调起微信支付,支付成功后微信服务器触发小程序后端的支付回调接口
// 小程序后端的支付回调接口返回响应后,这个微信支付的请求才算成功
const [err, res] = await uni.requestPayment({
provider: 'weixin',
...payParams
});
if (err) {
uni.showToast({ title: '支付失败', icon: 'error' });
return;
}
// 4. 支付成功
uni.showToast({ title: '支付成功', icon: 'success' });
uni.redirectTo({ url: '/pages/order/success' });
} catch (error) {
uni.showToast({ title: '请求失败', icon: 'none' });
}
}
}
}
</script>

后端代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// Node.js 示例(Express)
app.post('/api/order/create', async (req, res) => {
const { productId, userId, totalFee, body } = req.body;

// 1. 生成订单号
const outTradeNo = `ORD${Date.now()}${Math.floor(Math.random() * 1000)}`;
// 2. 请求微信统一下单 API
const params = {
appid: 'your-appid', // 小程序 appid
mch_id: 'your-mch-id', // 商户号
nonce_str: Math.random().toString(36).substr(2, 15), // 随机字符串
body: body || '商品订单', // 商品描述
out_trade_no: outTradeNo, // 商户订单号
total_fee: totalFee, // 金额(单位:分)
spbill_create_ip: '127.0.0.1', // 用户 IP(实际获取)
notify_url: 'https://your-api.com/api/pay/callback', // 支付结果通知地址
trade_type: 'JSAPI', // 小程序支付
openid: 'user-openid' // 用户的 openid(必须从登录获取)
};
// 3. 生成签名
const sign = generateSign(params, 'your-api-key'); // 自定义签名方法
params.sign = sign;
// 4. 发送请求到微信
const xml = buildXml(params); // 构建 XML 请求体
// 在后端接口中发送请求
const result = await axios.post('https://api.mch.weixin.qq.com/pay/unifiedorder', xml, {
headers: { 'Content-Type': 'text/xml' }
});
const response = parseXml(result.data); // 解析 XML 响应
if (response.return_code === 'SUCCESS' && response.result_code === 'SUCCESS') {
// 返回给小程序的参数
res.json({
appId: params.appid,
timeStamp: Math.floor(Date.now() / 1000).toString(),
nonceStr: response.nonce_str,
package: `prepay_id=${response.prepay_id}`,
signType: 'MD5',
//
paySign: generateSign({
appId: params.appid,
timeStamp: Math.floor(Date.now() / 1000).toString(),
nonceStr: response.nonce_str,
package: `prepay_id=${response.prepay_id}`,
signType: 'MD5'
}, 'your-api-key')
});
} else {
res.status(400).json({ error: response.return_msg });
}
});
// 微信支付成功后,就会异步调用这个接口
app.post('/api/pay/callback', async (req, res) => {
const xmlData = await getRawBody(req); // 获取原始 XML 数据
const params = parseXml(xmlData);
if (params.return_code === 'SUCCESS' && params.result_code === 'SUCCESS') {
// 校验签名
// 更新订单状态
// 发货逻辑(可选)
// deliverProduct(params.out_trade_no);
}
// 返回成功响应,告诉微信不要再重试
res.send('<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>');
});

在微信小程序上怎么做分享功能

拿微信小程序举例,在 js 中定义 onShareAppMessage 处理函数(和 onLoad 等生命周期函数同级),设置该页面的分享信息,用户点击分享按钮的时候会调用。这个分享按钮可能是小程序右上角原生菜单自带的分享按钮,也可能是开发者在页面中放置的分享按钮(<button open-type="share">);此事件需要 return 一个Object,用于自定义分享内容。

现在想要使用微信小程序的分享功能,必须进行微信认证,认证需要不少的费用

如何发布微信小程序

扩展:App

在app上做如何做微信登录

在app上如何做微信支付

在app上如何做分享功能

拿分享到微信举例,微信要允许这个“外部 App”调用微信分享功能,就必须:验证这个 App 是合法的、受信任的,所以你需要在微信开放平台上注册一个“移动应用”,提交你的 App 名称、包名、签名等信息,微信审核通过后,给你一个 AppID(wx1234567890abcdef)
你在 manifest.json 中填写这个 AppID,云打包时集成进去。

第一步,进入微信开放平台https://open.weixin.qq.com,注意,不是公众平台(mp.weixin.qq.com),是 开放平台。

第二步,登录并完善开发者资质,使用你的微信扫码登录,需要 企业认证(个人账号无法创建移动应用
需要营业执照、对公账户等,个人开发者暂时无法使用微信分享(这是微信的限制)

第三步,创建“移动应用”,进入【管理中心】,点击【创建应用】→【移动应用】,填写:应用名称(如:我的视频App),应用简介,应用图标(512x512),Android 包名(如:com.yourcompany.app),Android 签名(SHA1,从你的 keystore 获取,iOS 类似,填 Bundle ID 和证书)

第四步,提交审核,审核通过后,你会获得:AppID(如:wx1234567890abcdef),AppSecret

第五步:在 HBuilderX 中配置,打开 manifest.json,在“模块权限配置”中勾选 Share(分享),点击“配置”,填入微信开放平台的:AppID,AppSecret(可选),最后进行云打包。