一、基础
01.目录结构
components 组件
pages 页面
static 静态资源
- images
App.vue APP运行的根组件
main.js APP运行的JS文件
manifest.json 全局文件:应用的配置
pages.json 全局文件:页面的配置
uni.scss 全局样式文件
index.html
02.生命周期
1. 页面生命周期
import { onLoad, onShow, onHide, onReady, onUnload } from '@dcloudio/uni-app'
// 页面第一次加载时触发。(该生命周期内,可以接收传参)
onLoad((options) => {
console.log('onLoad');
})
// 每次页面显示时触发
onShow(() => {
console.log('onShow');
})
// 每次页面隐藏时触发
onHide(() => {
console.log('onHide');
})
// 页面初次渲染完成时触发(这里可以获取DOM)
onReady(() => {
console.log('onReady');
})
// 页面卸载时触发
onUnload(() => {
console.log('onUnload');
})
2. 组件生命周期(和微信小程序一样)
组件中,只能使用组件的生命周期。
3. 应用生命周期
// 注意: 在APP.vue文件是编写
import { onLaunch, onShow, onHide } from '@dcloudio/uni-app'
// APP第一次加载时触发。
onLaunch(() => {
console.log('onLoad');
})
// APP显示时触发
onShow(() => {
console.log('onShow');
})
// APP隐藏时触发
onHide(() => {
console.log('onHide');
})
03.路由跳转
方法1:通过JS调用api跳转。
// 1.非tabBar,有历史记录。
uni.navigateTo({
url: 'xxx'
})
// 2.非tabBar,没有历史记录。
uni.redirectTo({
url: 'xxx'
})
// 3.非tabBar,并关闭全部页面。
uni.relaunch({
url: 'xxx'
})
// 4.tabBar页面
uni.switchTab({
url: 'xxx'
})
// 5.返回上一页
uni.navigateBack()
// 获取当前页面是处于历史记录中的第几个记录。
getCurrentPages()
方法2:通过标签按钮跳转
04.跳转传参
路径传参
// 1.传参
uni.navigateTo({
path: '/user.vue?username=思密达'
})
// 2.接收参数(只能在OnLoad生命周期中获取)
<script setup>
onLoad(e) {
const {username} = e
}
<script>
05.组件通信
方法1:父子通信。
(1)父传子:uni.$emit('count',参数)
(2)子传父:uni.$on('count', (resp) => { })
06.条件编译
情况1:在某端生效。
// H5
<!-- #ifdef H5 -->
1234
<!-- #endif -->
// 微信小程序
<!-- #ifdef MP-WEIXIN -->
1234
<!-- #endif -->
情况2:在某端不要生效
// H5
<!-- #ifndef H5 -->
1234
<!-- #endif -->
情况3:APP端又分为ios和andriod(因为条件编译无法识别ios还是Android)
// 区分ios和android
const system = uni.getSystemInfoSync();
<view v-if='system.platform === android'></view>
-----------------------------
二、项目配置
1.配置页面、导航栏、tabBar
记住:tabBar页面需要在pages中声明,tabBar的list的第一个路径要和pages的第一个路径相同。
// 1.在哪里配置
答:新建页面、导航栏、tabBar都是在pages.json文件里面配置。
// 2.可配置的属性:
(1)pages(配置页面信息)
- path: 页面路径
- style: 配置页面的导航栏(属性和globalStyle是一样的。)
- needLogin: 是否登录后才能访问
(2)globalStyle(配置全局导航栏)
- navigationBarBackgroundColor:导航栏背景颜色
- navigationBarTextStyle:导航栏标题颜色(仅支持 black/white)
- navigationBarTitleText:导航栏文字内容
- navigationBarShadow: 导航栏下方阴影
- colorType: 阴影颜色,支持blue、grey、green、orange、red、yellow
- navigationStyle: 自定义导航栏(default表示默认,custom表示自定义)
- backgroundColor: 窗口的背景颜色
- backgroundTextStyle: 下拉loading的背景颜色
- backgroundColorTop: 顶部窗口的背景色(bounce回弹区域)
- backgroundColorBottom: 底部窗口的背景色(bounce回弹区域)
- enablePullDownRefresh: 是否开启下拉刷新
- onReachBottomDistance: 触底刷新的距离(默认50)
// 注意配置tabBar有坑:首页必须在tabBar中,否则tabBar不会显示出来。
(3)tabBar(配置全局tabBar)
- color:tabBar的文字颜色
- selectedColor: 被选中的tabBar的文字颜色
- backgroundColor:tabBar的背景颜色
- borderStyle: tabBar的上边框颜色(可选的有black/white,还可以自定义颜色)
- position: tabBar的位置(默认是bottom,可选的有bottom/top)
- fontSize: tabBar的文字大小
- iconWidth:icon图标的宽度(默认24px)
- spacing: icon和文字的间距(默认3px)
- height: tabBar的高度(默认50px)
- midButton:中间按钮 仅在 list 项为偶数时有效(对象)
- text:
- height:
- iconPath:
- iconfontSrc:list设置 iconfont 属性时,要指定字体文件路径(一般list都是数组)
- backgroundImage: 背景图片(优先级高于backgroundColor)
- backgroundRepeat: 背景平铺
- redDotColor:tabBar上面红点的颜色
- list:配置每一个tabBar的icon(数组包对象)
- pagePath:页面路径
- text: tab上按钮文字,在 App 和 H5 平台为非必填
- iconPath:icon路径
- selectedIconPath:被选中的icon的路径
2.APP配置
在manifest.json配置文件中,勾选需要的功能即可。
3.配置全局样式
好处:在该uni.scss中声明的scss变量,在使用时无需导入。
4.配置分包(早期)
1. 分包的意义
// 1.微信小程序分包的限制如下:
(1)单个分包、主包大小不超过2M。(tabBar必须在主包内。)
(2)总体分包大小不超过16M。
(3)总包大小不超过20M。
// 2.单个分包、主包
pages下的每一个目录都是一个分包,一般把index或者tabBar作为主包。
pages
index
list
user
tabBar
// 3.总体分包
正常情况下,在pages目录下,一个目录就放一个.vue文件,表示一个页面;
但是有时候,一个目录里面放了好几个.vue文件,他们的总和就是总体分包。
2. 如何配置分包?
步骤1:开启分包(在manifes.json中)。
/* 小程序特有相关 */
"mp-weixin": {
"appid": "",
"setting": {
"urlCheck": false
},
// 配置分包(这里开启分包)
"optimization": {
"subPackages": true
}
步骤2:配置分包目录(在pages.json中)。
// 假设项目目录结构如下:
main.js
APP.vue
// 主包目录:一般是tabBar。
pages
└── tabBar
├── index.vue
├── middle.vue
└── my.vue
// 分包目录:配置多个子包,子包内又有页面。
package
├── commonPackage // 公共分包
│ └── pages
│ ├── cat.vue
│ └── dog.vue
├── login // 登录页
│ └── pages
│ └── login.vue
│
└── userConfig // 设置页
└── pages
└── userConfig.vue
***********************************************************
// 在pages.json中配置主包、分包。
{
// 配置主包
"pages":[
"pages/index",
"pages/logs"
],
// 配置分包
"subPackages": [
{
"root": "packageA",
"pages": [
{
"path": "pages/cat",
"navigationBarTitleText": "设置"
},
{
"path": "pages/dog",
"navigationBarTitleText": "设置"
},
]
},
{
"root": "packageB",
"name": "pack2",
"pages": [
{
"path": "pages/apple",
"navigationBarTitleText": "设置"
},
{
"path": "pages/banana",
"navigationBarTitleText": "设置"
},
]
}
]
}
5.配置请求模块(必须)
方法1:使用uView提供的api。
步骤1:新建utils文件夹,创建request.js文件。
// 此vm参数为页面的实例,可以通过它引用vuex中的变量
module.exports = (vm) => {
// 初始化请求配置
uni.$u.http.setConfig((config) => {
/* config 为默认全局配置*/
config.baseURL = 'http://uat.banlu.xuexiluxian.cn'; /* 根域名 */
return config
})
// 请求拦截
uni.$u.http.interceptors.request.use((config) => { // 可使用async await 做异步操作
return config
}, config => { // 可使用async await 做异步操作
return Promise.reject(config)
})
// 响应拦截
uni.$u.http.interceptors.response.use((response) => { /* 对响应成功做点什么 可使用async await 做异步操作*/
return response.data
}, (response) => {
// 对响应错误做点什么 (statusCode !== 200)
return Promise.reject(response)
})
}
步骤2:在main.js中导入request.js模块。(切记放要在new Vue之后)
const app = new Vue({
...App
})
// 引入请求封装,将app参数传递到配置中
require('./utils/request.js')(app)
app.$mount()
步骤3:在api目录下,配置请求。(请求参数的写法和axios一致)
// get请求
export const loginByWechatApi = (data) => {
return http.get('/u/loginByWechat', {
params: {
code: data
}
})
}
// post请求
export const loginByWechatApi = (data) => {
return http.get('/u/loginByWechat', data)
}
方法2:手动封装。
了解uniapp原生请求方法:uni.request(参数)。
uni.request({
url: 'http:www.baidu.com',
method: 'GET',
// GET请求参数一般放url后面,如果放data中,将来会被自动放到url后面,也是可以的。
data: {},
header: {},
// 成功的回调
success: () => {
},
// 失败的回调
fail: () => {
},
// 无论成功或者失败,都会触发的回调
complete: () => {
}
})
封装的整个过程:
const TIMEOUT = 6000 // 超时时间
/****************************************************************/
export default {
// 定义请求拦截器。
requestIntercepted(config) {
return config
},
// 响应拦截器。
responseInterceoted(resp) {
return resp
},
// 取消重复的请求。
removePendingRequest(config) {},
// 发请求:该方法返回一个Promise对象。
request(options) {
return new Promise((resolve, reject) => {
// 处理请求参数
const finalOptions = {
...options,
timeout: TIMEOUT,
header: {
'Content-type': 'application/json',
...options.header
}
}
// 调用请求拦截器
const handlerOptions = this.requestIntercepted(finalOptions)
// 调用取消重复的请求
this.removePendingRequest(handlerOptions)
// 调用响应拦截器
const requestTask = uni.request({
...handlerOptions,
success: (res) => {
const response = this.responseInterceoted(res)
if (response.statusCode >= 200 && response.statusCode < 300) {
resolve(response.data)
} else {
reject(response)
}
},
fail: (error) => {
reject(error, '网络出现问题!')
}
})
})
},
// 便捷POST请求。
post(url, data={}, config={}) {
return this.request({
method: 'POST',
url,
data,
...config
})
},
// 便捷GET请求。
get(url, data={}, config={}) {
return this.request({
method: 'GET',
url,
data,
...config
})
}
}
6.配置webView页面(做活动)
1. web-view的使用姿势
// 1.使用方式:必须v-if,不能v-show。
<web-view v-if="isShow" src="https://www.baidu.com"></web-view>
// 2.使用场景
做一个短期活动,比如7月-9月之间搞活动,不需要把每一个端的代码都重新写一遍再发布,
直接做H5网页即可,使用web-view标签嵌入即可。
// 3.缺点
(1)打开是全屏的,无法设置大小。
(2)通过小程序打开的,是没有返回按钮的,所以要做一个返回操作。
利用uni.navigateTo()来跳转到webView页面,这样就可以有返回了,
并且给webView单独配置成一个分包页面。
2. 微信小程序使用web-view的注意事项
在上线之前,需要去微信小程序后台,把webView的url地址,在业务域名中配置,如下图:
3. webview传值问题
传参:小程序跳转到webview,可以在webview的src中以?拼接参数形式传参。
<web-view v-if="showActivity" src="https://www.baidu.com?username=思密达"></web-view>
注意事项:小程序和APP的区别。
(1)小程序可以传参给webview没问题,但是webview无法传参回去给小程序。
(2) APP可以可以传参给webview,也可以通过webview的@postMessage方法
把参数从webview回传给APP。
7.解决跨域问题
// 情况1:微信小程序
开发阶段:不校验合法域名。
生产阶段:去微信小程序后台页面,配置合法域名。
// 情况2:H5
开发阶段:和vue3一样,去vite.config.js中配置代理。
生产阶段:需要后端解决好跨域。
// 情况3:APP
开发阶段:和vue3一样,去vite.config.js中配置代理。
生产阶段:需要后端解决好跨域。
// 注意:小程序不支持vite.config.js文件的配置,所以代理配置不生效。
8.打包上线
1. 微信小程序打包上线
前提:
(1) 保证包,不能超过最大限制。
(2) 不校验url取消勾选。
(3) request合法域名加上,必须是https。
(4) 如果用webview,也必须加上到业务域名,同样是https。
(5) 拿到本小程序的AppId。
上架流程:
(1) 在微信开发者工具,点击上传。
(2) 进入小程序后台
(3) 管理 => 版本控制 => 开发版本(本次上传的小程序包)
2. H5打包上线
1. 在HBuilder的最上方找到并点《发行》 => 选中《网站-PC,web、H5》
2. 提前在manifest.json中找到《web配置》,把运行基础路径改为:./ 其他选项自定义。
3. 打包后的文件,给后端或者运维部署即可。
3. Android打包上线
前提:
(1) 准备好:包名、别名、证书密码、证书文件。
包名:公司给。
别名:公司给。
证书密码:公司给。
证书文件:公司给。(一般是用工具生成的,如:香蕉云编)
例如:
包名:cn.xuexiluxian.ai
别名:xiaoluxianai
密码:xiaoluxianai96
证书文件:已发放
(2) 网站备案,pc端网站还有,要准备一个用户隐私协议的url。
(3) 要做好公司app产品类目的公司资质。
(4) 软件著作权。
(5) 上传到应用商店需要提供以下内容:
1. 软件著作权
2. 隐私政策
3. 应用图标
4. app内截图
5. 应用简介
6. 应用分类
提交到应用商店的过程:
(1) 发行云打包 => 填写对应内容(证书文件与开发阶段一模一样) => 生成apk包
(2) 申请对应的app应用商店的开放平台,比如:oppo、小米、华为、vivo等...
(3) 填写app的信息,然后上传即可。
4. ios打包上线
前提:
(1) 准备好:包名、别名、证书密码、证书文件、证书profile文件。
包名:公司给。
别名:公司给。
证书密码:公司给。
证书文件:公司给。(证书文件与开发阶段不一样,需要重新生成)
证书profile文件:公司给。
例如:
包名:cn.xuexiluxian.ai
别名:xiaoluxianai
证书密码:luxianai
证书文件:已发放
打包完后生成ipa包下载地址
去ios开放平台
填写app信息、上传ipa包即可。
注意:所有app第一次上架时,必须提供账号密码登录的方式,并提供账号密码给开放平台;
否则微信登录、手机验证码登录无效。
-----------------------------
三、功能模块
01.登录功能
02.支付功能
03.分享朋友圈、好友
步骤1:创建mixins目录,新建share.js文件,将来混入到main.js中。
export default {
// 分享给好友
onShareAppMessage() {
return {
title: '分享给好友的标题',
imageUrl: '../../static/logo.png',
path: '/pages/my/my'
}
},
// 分享到朋友圈
onShareTimeLine() {
return {
title: '分享给朋友圈的标题',
imageUrl: '../../static/logo.png',
query: ' '
}
}
}
步骤2:去main.js中导入混入。
import share from '@/mixins/share.js'
Vue.mixin(share)
04.虚拟滚动
xxx
05.文件上传
原始http消息格式
1. 单文件
async function fileUpload (e) {
// 在这里先对要上传的文件进行验证(文件大小、文件格式是否正确)
// FormData对象的好处:自动帮我们设置了content-Type: 'mutipart/form-data'
const formData = new FormData();
// 键名:是看接口文档规定的。 值:文件数据。
formData.append('file', e.target.files[0])
// 请求后端接口
const resp = await fetch('https:www.baidu.com/api/upload', {
method: 'POST',
body: formData
})
}
06.数据加密
-----------------------------
四、兼容性问题
1.动态组件
(1)h5端:支持。
(2)app端:组件注册后才能支持。
(3)小程序:不支持。
2.fetch(SSE)
1. 为什么用SSE?
(1)如果使用webSocket,性能不如SSE。
(1)h5端:必须使用wss协议
new webSocket('wss://')
(2)小程序、app端:必须使用wss协议
uni.connetSocket({
url: 'wss://'
})
(2)如果使用fetch,只有h5端支持,其他端都不支持。
(3)使用SSE:默认不支持post,只支持get。
如果要支持post,需要一个库,示例如下:
import { fetchEventSource } from @microsoft/fetch-event-source。
(1)h5端:支持@microsoft/fetch-event-source。
(2)小程序端:不支持@microsoft/fetch-event-source,需要
uni.request({
enableChunked: true // 让微信小程序支持SSE
success(res) {
console.log('连接成功')
}
})
(3)app端:本身不支持@microsoft/fetch-event-source,可以通过xxx方式支持。
2. 如何使用SSE?
微信小程序端:
(1)安装一个库:npm i @microsoft/fetch-event-source
import { fetchEventSource } from @microsoft/fetch-event-source。
(2)小程序是通过uni.request中,配置enableChunked: true来支持SSE的。
(3)要使用onHeadersReceived、onChunkReceived,那么uni.request中必须有success
或者complete的其中一个。
【后端返回数据格式】:
【代码实现】:
wx_send() {
const that = this;
let content = '' //收集ai的回答
let len = this.msgList.length
let requestTask = uni.request({
url: 'https://ai.xuexiluxian.cn/api/xlx-chat/chatMessage/sendMessageSSE',
enableChunked: true, //微信小程序支持SSE
method: 'post',
header: {
'Content-Type': 'application/json',
'Authorization': this.token
},
data: JSON.stringify({
id:this.chatId,
userId:this.userId,
question:this.msgList.at(-1).content
}),
success: (resp) => { //相当于onclose
console.log('连接关闭')
}
})
requestTask.onHeadersReceived(res => { //相对于onopen
console.log('onHeadersReceived');
})
requestTask.onChunkReceived(res => { //相当于onmessage(这里解析后端返回的内容)
const arrayBuffer = res.data;
const uint8Array = new Uint8Array(arrayBuffer);
let data = uni.arrayBufferToBase64(uint8Array);
data = new Buffer(data, 'base64');
data = data.toString('utf8');
const lines = data.split("\n\n");
lines.forEach(line => {
if( line.indexOf('{') != -1 ){
//使用正则表达式提取 data 部分
const dataMatch = line.match(/data:(\{.*\})/);
//检查是否成功匹配
if (dataMatch && dataMatch[1]) {
const dataString = dataMatch[1];
//将 JSON 字符串转换为对象
let data;
try {
data = JSON.parse(dataString);
content += data.content
that.$set(that.msgList,len,{
role:'system',
content:content
});
} catch (error) {
console.error('解析 JSON 失败:', error);
}
} else {
console.error('未找到 data 部分');
}
}
})
})
}
H5、APP端:其中APP不分ios和android。
(1)安装一个库:npm i @microsoft/fetch-event-source
import { fetchEventSource } from @microsoft/fetch-event-source。
h5_send() {
const that = this
let content = '' //收集ai的回答
const ctrl = new AbortController() //用于中断请求
const len = this.msgList.length
fetchEventSource('/api/xlx-chat/chatMessage/sendMessageSSE', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': this.token
},
signal: ctrl.signal,
body: JSON.stringify({
id:this.chatId,
userId:this.userId,
question: this.msgList.at(-1).content
}),
onopen: (resp) => {
},
onmessage: (msg) => {
const { data } = msg
//校验返回的内容
if( data && !data.includes("<br/><br/>tokens") && !data.includes("[DONE]")) {
const res = JSON.parse(data)
content += res.content
that.$set(that.msgList,len,{
role:'system',
content:content
})
}
},
onclose: () => {
console.log('连接关闭!');
}
})
}
3. 如果使用SSE,发现数据一次性返回,需要去nginx中取消缓存!