
uniapp 显示 svga
注意hbuilder编译要v3
在manifest.json 文件下设置
"app-plus" : {
"compilerVersion" : 3,
}
具体内容在附件
注意hbuilder编译要v3
在manifest.json 文件下设置
"app-plus" : {
"compilerVersion" : 3,
}
具体内容在附件

uniCloud云数据库增删改查
一个H5页面,让你看懂数据库增删改查。
写在最前面
我是自学前端的小白,所以只能演示基本的操作,关于复杂的连表增删改查,还没有研究过。 就以这个demo
数据库为原型,完成查询、新增、修改、删除的基本操作示例。
1、查询,①在网站初始化时需要调用查询;②用户查询电户号时,需要调用查询;③新增或删除电户信息时,需要比对数据,也需要调用查询。
2、新增,分为①在demo
下,再新增_id
,并添加字段和值;②在指定_id
下指定位置新增。
3、更新,分为①覆盖式更新;②在同一个_id
下指定位置更新数据。
4、删除,分为①指定_id
全部删除;②在指定_id
下,指定位置删除数据。
在云函数写好之后,还需页面调用处理返回的数据,文末有示例。
一、准备云数据库
- 云数据库有两个,'initial'和'demo',后面有展开数据演示。
- 'initial',用于初始化页面。
- 'demo',用于增删改查。
二、编写云函数
- uniCloud数据库,允许在网页端直接使用
uniCloud.callFunction
函数调用数据库的数据。- 但是,建议使用云函数调用,这样数据更安全。</font>
- 云函数的主流是路由——自动匹配对应的云函数去处理数据,我还不会,所以就写了五个云函数,在页面要用哪个就调用哪个。
2.1 云函数默认目录。
uniCloud/cloudfunctions/云函数名称/云函数名称.js
2.2 uniCloud数据库操作基于mongoDB,可以查询相关文档。
三、获取数据的云函数
await db.collection('demo').field({}).get();
3.1 初始化页面获取数据的方法
- 初始化页面,需要调用的云数据库,名称
initial
,展开如下:
{
"_id":"603a0b27c9e7be00013b5e33",
"ArrList":["沙沟村","大东沟村","罗家坡村","小山岔村","渡口村","七家洼村"]
}
---------------华丽的---------分割线-------------
{
"_id":"603a120b20be4e000120b8f6",
"groupArr": {
"0": ["沙沟一变","沙沟二变","沙沟三变","同心一变","双明一变","双明二变"],
"1": ["一变","二变","三变"],
"2": ["一变","二变","三变","四变"],
"3": ["一变","二变","三变","四变","五变"]
--------后面-------还有------内容-------省略------
}
}
- 初始化云函数,名称也叫
initial
,代码如下: - get()获取的数据存放在data数组中,第一个
_id
的数据,就是data[0],第二个_id
的数据,就是data[1]
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
let [str] = event;
if(str ==='initial'){
let res = await db.collection('initial').field({_id:0,ArrList:1,'groupArr.0':1}).get();
//get()获取的数据存放在data数组中,第一个`_id`数据,就是data[0]
let cunList = res.data[0].ArrList;
//get()获取的数据存放在data数组中,第二个`_id`数据,就是data[1]
let zuList = res.data[1].groupArr[0];
//code的数字,只是为了前端if判断时好用,并没有什么实际意义。
return {
code:888,
msg:'初始化数据成功',
cunList,
zuList
}
}else{
return{
code:400,
msg:'您请求的数据格式有误'
}
}
}
- 前端页面调用的代码,在
onLoad
生命周期内,将查询的数据赋值给两个数组,再渲染到页面中(两个列表中)。 - 页面端调用的代码
this.$api.HandleUnicloud(cloudName, mydata){res=>{}}
,如何而来,文末有详解
export default {
data() {
return {
cun_list: [],
zu_list: []
}
}
onLoad() {
let mydata = ['initial'];
let cloudName = 'initial';
this.$api.HandleUnicloud(cloudName, mydata).then(res => {
this.cun_list = res.cunList;
this.zu_list = res.zuList;
});
}
}
3.2 <font color="red">★重点★</font> 查询返回指定字段的数据
-
示例
demo
数据库,由六部分组成,每一个_id
独立为一个单元。 -
数据库展开
{
"_id":"603eeaa40daee300011f18f4",
"villages": {
"name": "沙沟村",
"id": 0,
"groupArr": ["沙沟一变","沙沟二变","沙沟三变","同心一变","双明一变","双明二变"],
"group-1": [{"3102568889": "翟**"},{"3105360550": "李**"},{"3105360619": "马**"},..省略..],
"group-2": [{"3078734642": "刘**"},..省略..],
"group-3": [],
"group-4": [],
"group-6": []
}
}
------------华丽的-----------分割线------------------
{
"_id":"603eeab4e857bd0001690475",
"villages": {
"name": "大东沟村",
"id": 1,
"groupArr": ["一变","二变","三变"]
}
}
------------华丽的-----------分割线----------------
{
"_id":"603eeab4e857bd0001690475",
"villages": {
"name": "XXX村",
"id": 2,
"groupArr": ["一变","二变","三变","四变"]
}
}
-------后面----还有-----数据-----不再-----展示-----
field({})
,可以返回指定字段的内容,必须使用get()
方法才能获取到数据。1
表示显示,没有标注的数据不显示。但是_id
是默认显示的。field({'ArrList':1,_id:0})
,不显示_id
,查询关于ArrList
的信息,field({'villages.group-2':1})
<font color="red">查询 villages 数组或对象内 group-2 的内容。- 与变量相结合的查询方式,有二种:
const groupNum = "group-3";
field({[`villages.${groupNum}`]:1})
const groupNum = "group-4";
field({['villages.'+ groupNum]:true})
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
//event==mydata: { cunIndex: 0, groupName: 'group-1', userObj: { username: '', usernum: '' }, pageNum: 0, rowsNum: 10 }
let {cunIndex,groupName,userObj,pageNum,rowsNum} = event;
let resData = await db.collection('demo')
.field({_id:0,[`villages.${groupName}`]:1})
.get();
//resList等于当前村下的,当前变电组的,全部用户的信息
let resList = resData.data[`${cunIndex}`].villages[`${groupName}`];
//如果当前村的变电组不存在,或者变电组内没有数据时,调用
if(!resList||resList.length===0){
return {
code:400,
msg:'该村变电组内还未收录用户信息',
}
}
//这一段为翻页时调用
//请求的数据条数(rowsNum默认10条)
//pageNum当前点击的页码(由于是H5的原因默认是1)
let pageStarNum = (pageNum - 1) * rowsNum;
let pageEndNum = pageStarNum + rowsNum;
let pagesTotal = resList.length;
//pages表示返回的数据,在页面中总共要显示多少页
let pages = parseInt(pagesTotal/rowsNum);
//前端页面点击页码后,显示从第几条数据到第几条数据的列表
let resArr = resList.slice(pageStarNum,pageEndNum);
//resObj用于存放,数据库查找到的电户信息
let resObj = {};
//遍历当前变电组内所有的用户信息,当遍历的姓名与查询的姓名一致时
//把结果追加给resObj,使用合并对象的方法Object.assign(resObj,item)也行
resList.forEach(item=>{
if(Object.values(item)==userObj.username){
resObj = {...item}
}
})
//当用户没有输入姓名,直接查询,会获取全部列表
if(userObj.username===''){
return {
code:200,
msg:'当前查询的是:该变电组内所有用户的信息',
resArr,
pages
}
}
//当输入的姓名,查询不到时调用
//返回当前村、变电组,全部列表数据
//pages是总页数
if(Object.keys(resObj).length===0){
return {
code:404,
msg:'抱歉!该变电组内未收录此用户的信息',
resArr,
pages
}
}
//当输入的姓名,数据库中有时调用
//msg的信息可以使用模板字符串
return {
code:200,
msg:`「 ${userObj.username} 」的电户号,查询结果如下:`,
resObj
}
}
四、新增数据的云函数
4.1 在原数据外,新增一个_id,add()
- 新增单一数据,用大括号包裹数据
{"name": "张三"}
。 - 新增多条数据,用中括号包裹数据
[{"name": "张三"},{"name": "李四"},{"name": "王五"}]
。
const db = uniCloud.database();
exports.main = async (event, context) => {
let resData = await db.collection('demo')
.add([{"name": "张三"},{"name": "李四"},{"name": "王五"}]);
let resList = await db.collection('demo').get();
return {
code:200,
msg:'添加数据成功',
resList
}
};//数据返回结果
{
"code": 200,
"msg": "添加数据成功",
"resList": {
"data": [{
"_id": "60210b53ef338d00016d2404",
....此处 .... 省略 ....一万字....
},
{
"_id": "6038d46620be4e00011e5cf4",
"name": "张三"
},
{
"_id": "6038d46620be4e00011e5cf5",
"name": "李四"
},
{
"_id": "6038d46620be4e00011e5cf6",
"name": "王五"
}
}]
}
4.2 在指定_id内,指定位置新增。
-
doc('_id').update();
方法可用于更改,也可用于新增。 -
普通数组添加、删除数据,官方文档中有示例,这里演示的是,数据为对象的数组
"group-1":[{key:value},{key:vlaue}]
如何新增。 -
update(),需紧随
doc()
之后,doc()
内写指定数据的_id
,因为一个集合内可能有多个_id
-
在
update()
内引用变量,需要使用[`${变量名}`]
-
在数组尾部新增对象,可以在中括号内写模板字符串
dbCmd.push([{...addUserObj}])
'use strict';
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
let {cunIndex,groupName,userObj} = event;
if(userObj.username==''||userObj.usernum==''){
return{
code:400,
msg:'新增数据不能为空'
}
}
let resData = await db.collection('demo').field({_id: 1,[`villages.${groupName}`]: 1}).get();
//用于获取_id,存放与data[{_id:***},{_id:***},{_id:***},***]中
let id = resData.data[`${cunIndex}`]['_id'];
let groupArr = resData.data[`${cunIndex}`].villages[`${groupName}`];
let isTrue = true;
//先查询,用户要新增的电户号码,在数据库中,有没有,有就赋值false
if (groupArr) {
groupArr.forEach(val=>{
if(Object.keys(val)==userObj.usernum){
isTrue = false;
}
});
//如果数据库中没有该电户号码,就在对应村、变电组的尾部新增该数据
if(isTrue){
let addUserObj = {[`${userObj.usernum}`]:userObj.username};
let resSucc = await db.collection('demo')
.doc(`${id}`)
.update({
villages: {
[`${groupName}`]: dbCmd.push([{...addUserObj}])
}
})
return {
code: 200,
msg: '数据添加成功',
resSucc
}
//当数据库中有该电户号码时,就提示已经存在
}else{
return {
code: 400,
msg: `抱歉!电户号「 ${userObj.usernum} 」已经存在`
}
}
}
//如果该村还未创建该变电组,就新建变电组,之后再新增用户信息
if (!groupArr) {
let resSucc = await db.collection('demo')
.doc(`${id}`)
.update({
villages: {
[`${groupName}`]: dbCmd.set([{
[`${userObj.usernum}`]: userObj.username
}])
}
})
return {
code: 200,
msg: '创建变电组成功,数据添加成功',
resSucc
}
}
}
五、更新数据的云函数
5.1 覆盖式更新
- 覆盖式更新的意义不大,会将原数据彻底清除,一般情况下用不到。
update({'villages':dbCmd.set({key:value})})
将villages做为字段,后面跟一个对象
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
let resSucc = await db.collection('demo')
.doc("600bfa4d7f06f30001b47259")
.update({
'villages':dbCmd.set({
"6640059601": "张三"
})
}
);
let resObj = await db.collection('demo').get();
return {
code:200,
resObj,
resSucc
}
}
//返回的数据结果 villages下只剩下一个对象了
"villages": {"6640059601":"张三"}
5.2 指定_id
下,指定位置更新。
- 指定位置更新,同上面的update()方法一致,这里省略。
六、删除数据的云函数
6.1 删除指定_id的数据库的全部数据
-
collection.doc(_id).remove()
-
doc()
内只能写字符串或者数字,可以使用ES6模板字符串doc(`${id}`)
// 删除指定_id的数据
const db = uniCloud.database();
exports.main = async (event, context) => {
const id = '60388f7eb6ce210001cbbf14';
await db.collection('demo')
.doc(`${id}`)
.remove();
let resData = await db.collection('demo').get();
return resData;
};
6.2 删除指定位置的数据
- 在update()内使用remove(),方法挂载在command下,不能用于数组,删除后,数组原索引位置会变成null。但是可以用于对象,删除key就等于删除了该对象。doc(`${id}`).update({villages:{name:dbCmd.remove()}})
- 更新数组,可以先获取数组,再修改数组,最后把新数组更新到原位置即可。update({villages:{[`${groupName}`]:[...groupArr]}})
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
let {cunIndex,groupName,userObj} = event;
let resData = await db.collection('demo').field({_id: 1,[`villages.${groupName}`]: 1}).get();
let id = resData.data[`${cunIndex}`]['_id'];
let groupArr = resData.data[`${cunIndex}`].villages[`${groupName}`];
let isTrue = false;
var index = 0;
if (!groupArr) {
return {
code: 400,
msg: '抱歉!该村还未创建此变电组数据',
resSucc
}
};
groupArr.forEach((val,i)=>{
if(Object.keys(val)==userObj.usernum){
isTrue = true;
index = i
}
});
//先查询数据在数组中的索引,使用splice,删除数据,把获得的新数组,更新到原位置处
if(isTrue){
groupArr.splice([`${index}`],1);
let resData = await db.collection('demo')
.doc(`${id}`)
.update({
villages:{
[`${groupName}`]:[...groupArr]
}
})
return {
code: 200,
msg: `「 ${userObj.usernum} 」数据删除成功`,
index,
groupArr,
resData
}
};
return {
code: 400,
msg: '数据删除失败,原因:电户号错误'
}
}
七、处理云函数返回数据的工具函数
- 工具函数的路径
common/api/index.js
- HandleUnicloud有两个参数,用于
uniCloud.callFunction({name,data})
传参。 cloudName
表示,需要调用哪个云函数。mydata
表示,用户向云函数传递的数据。
const HandleUnicloud = (cloudName,mydata)=>{
return new Promise((reslove,reject)=>{
uniCloud.callFunction({
name:cloudName,
data:mydata
}).then(res=>{
if(res.result){
reslove(res.result)
}else{
reject(res.result)
}
}).catch(res=>{
console.log(res);
})
});
};
export default{
HandleUnicloud
}
八、全局注册处理云函数数据的工具函数
- 先引入
import api from './common/api'
- 再注册
Vue.prototype.$api = api
import Vue from 'vue'
import App from './App'
import api from './common/api'
Vue.config.productionTip = false
Vue.prototype.$api = api
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
九、页面调用的方法
this.$api.HandleUnicloud()
addData() {
//用户传给云函数的数据,赋值给mydata
let mydata = this.mydata;
//当前需要调用的云函数名称
let cloudName = 'UpdateAdd';
//用于检查姓名和电户号码的正则函数checkName()和checkNum()
if (!this.checkName(this.mydata.userObj.username) || !this.checkNum(this.mydata.userObj.usernum)) {
this.tip = '抱歉,姓名和电户号,数据有误!';
return;
}
//有确定和取消按键的模态框
uni.showModal({
title: '请选择',
content: `您确定要添加${mydata.userObj.username}吗?`,
cancelText:'取消添加',
confirmText:'确定添加',
//这里应当使用箭头函数,否则影响this.$api和this.tip的使用
success: res=> {
if (res.confirm) {
this.$api.HandleUnicloud(cloudName, mydata).then(v => {
this.tip = v.msg;
uni.showLoading({
title: this.tip
});
setTimeout(() => {
uni.hideLoading();
}, 1000);
});
} else if (res.cancel) {
this.tip = '选择了取消!'
}
}
});
}
十、写在最后
非常感谢,DCloud公司提供如此强大的生态系统,让我们去学习进步的效率大大的提升。
最后附上H5白~嫖建站的最后一关,跨域配置问题,将前端网页托管——参数配置——默认网址,复制粘贴到跨域配置——新增域名中,即可手机,PC端都能正常访问了。
目前uniCloud</font>提供了,免费的阿里云空间,</font>完全可以满足个人用户的建站使用。
只需要在https://unicloud.dcloud.net.cn/login申请账号就可以获得:
1、一个免费的云数据库,用于保存JSON格式的数据
2、一个免费的存储云函数的空间,用于保存可以在云端调用修改云数据库数据的函数,空间环境为nodejs,操作基于mongoDB,有所改动。
3、一个免费的云存储,用于保存图片、视频、文件等,并能够提供对应的访问地址
4、一个免费的网页托管,自动生成可访问的网址,只需处理跨域配置即可。
个人博客地址
一个H5页面,让你看懂数据库增删改查。
写在最前面
我是自学前端的小白,所以只能演示基本的操作,关于复杂的连表增删改查,还没有研究过。 就以这个demo
数据库为原型,完成查询、新增、修改、删除的基本操作示例。
1、查询,①在网站初始化时需要调用查询;②用户查询电户号时,需要调用查询;③新增或删除电户信息时,需要比对数据,也需要调用查询。
2、新增,分为①在demo
下,再新增_id
,并添加字段和值;②在指定_id
下指定位置新增。
3、更新,分为①覆盖式更新;②在同一个_id
下指定位置更新数据。
4、删除,分为①指定_id
全部删除;②在指定_id
下,指定位置删除数据。
在云函数写好之后,还需页面调用处理返回的数据,文末有示例。
一、准备云数据库
- 云数据库有两个,'initial'和'demo',后面有展开数据演示。
- 'initial',用于初始化页面。
- 'demo',用于增删改查。
二、编写云函数
- uniCloud数据库,允许在网页端直接使用
uniCloud.callFunction
函数调用数据库的数据。- 但是,建议使用云函数调用,这样数据更安全。</font>
- 云函数的主流是路由——自动匹配对应的云函数去处理数据,我还不会,所以就写了五个云函数,在页面要用哪个就调用哪个。
2.1 云函数默认目录。
uniCloud/cloudfunctions/云函数名称/云函数名称.js
2.2 uniCloud数据库操作基于mongoDB,可以查询相关文档。
三、获取数据的云函数
await db.collection('demo').field({}).get();
3.1 初始化页面获取数据的方法
- 初始化页面,需要调用的云数据库,名称
initial
,展开如下:
{
"_id":"603a0b27c9e7be00013b5e33",
"ArrList":["沙沟村","大东沟村","罗家坡村","小山岔村","渡口村","七家洼村"]
}
---------------华丽的---------分割线-------------
{
"_id":"603a120b20be4e000120b8f6",
"groupArr": {
"0": ["沙沟一变","沙沟二变","沙沟三变","同心一变","双明一变","双明二变"],
"1": ["一变","二变","三变"],
"2": ["一变","二变","三变","四变"],
"3": ["一变","二变","三变","四变","五变"]
--------后面-------还有------内容-------省略------
}
}
- 初始化云函数,名称也叫
initial
,代码如下: - get()获取的数据存放在data数组中,第一个
_id
的数据,就是data[0],第二个_id
的数据,就是data[1]
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
let [str] = event;
if(str ==='initial'){
let res = await db.collection('initial').field({_id:0,ArrList:1,'groupArr.0':1}).get();
//get()获取的数据存放在data数组中,第一个`_id`数据,就是data[0]
let cunList = res.data[0].ArrList;
//get()获取的数据存放在data数组中,第二个`_id`数据,就是data[1]
let zuList = res.data[1].groupArr[0];
//code的数字,只是为了前端if判断时好用,并没有什么实际意义。
return {
code:888,
msg:'初始化数据成功',
cunList,
zuList
}
}else{
return{
code:400,
msg:'您请求的数据格式有误'
}
}
}
- 前端页面调用的代码,在
onLoad
生命周期内,将查询的数据赋值给两个数组,再渲染到页面中(两个列表中)。 - 页面端调用的代码
this.$api.HandleUnicloud(cloudName, mydata){res=>{}}
,如何而来,文末有详解
export default {
data() {
return {
cun_list: [],
zu_list: []
}
}
onLoad() {
let mydata = ['initial'];
let cloudName = 'initial';
this.$api.HandleUnicloud(cloudName, mydata).then(res => {
this.cun_list = res.cunList;
this.zu_list = res.zuList;
});
}
}
3.2 <font color="red">★重点★</font> 查询返回指定字段的数据
-
示例
demo
数据库,由六部分组成,每一个_id
独立为一个单元。 -
数据库展开
{
"_id":"603eeaa40daee300011f18f4",
"villages": {
"name": "沙沟村",
"id": 0,
"groupArr": ["沙沟一变","沙沟二变","沙沟三变","同心一变","双明一变","双明二变"],
"group-1": [{"3102568889": "翟**"},{"3105360550": "李**"},{"3105360619": "马**"},..省略..],
"group-2": [{"3078734642": "刘**"},..省略..],
"group-3": [],
"group-4": [],
"group-6": []
}
}
------------华丽的-----------分割线------------------
{
"_id":"603eeab4e857bd0001690475",
"villages": {
"name": "大东沟村",
"id": 1,
"groupArr": ["一变","二变","三变"]
}
}
------------华丽的-----------分割线----------------
{
"_id":"603eeab4e857bd0001690475",
"villages": {
"name": "XXX村",
"id": 2,
"groupArr": ["一变","二变","三变","四变"]
}
}
-------后面----还有-----数据-----不再-----展示-----
field({})
,可以返回指定字段的内容,必须使用get()
方法才能获取到数据。1
表示显示,没有标注的数据不显示。但是_id
是默认显示的。field({'ArrList':1,_id:0})
,不显示_id
,查询关于ArrList
的信息,field({'villages.group-2':1})
<font color="red">查询 villages 数组或对象内 group-2 的内容。- 与变量相结合的查询方式,有二种:
const groupNum = "group-3";
field({[`villages.${groupNum}`]:1})
const groupNum = "group-4";
field({['villages.'+ groupNum]:true})
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
//event==mydata: { cunIndex: 0, groupName: 'group-1', userObj: { username: '', usernum: '' }, pageNum: 0, rowsNum: 10 }
let {cunIndex,groupName,userObj,pageNum,rowsNum} = event;
let resData = await db.collection('demo')
.field({_id:0,[`villages.${groupName}`]:1})
.get();
//resList等于当前村下的,当前变电组的,全部用户的信息
let resList = resData.data[`${cunIndex}`].villages[`${groupName}`];
//如果当前村的变电组不存在,或者变电组内没有数据时,调用
if(!resList||resList.length===0){
return {
code:400,
msg:'该村变电组内还未收录用户信息',
}
}
//这一段为翻页时调用
//请求的数据条数(rowsNum默认10条)
//pageNum当前点击的页码(由于是H5的原因默认是1)
let pageStarNum = (pageNum - 1) * rowsNum;
let pageEndNum = pageStarNum + rowsNum;
let pagesTotal = resList.length;
//pages表示返回的数据,在页面中总共要显示多少页
let pages = parseInt(pagesTotal/rowsNum);
//前端页面点击页码后,显示从第几条数据到第几条数据的列表
let resArr = resList.slice(pageStarNum,pageEndNum);
//resObj用于存放,数据库查找到的电户信息
let resObj = {};
//遍历当前变电组内所有的用户信息,当遍历的姓名与查询的姓名一致时
//把结果追加给resObj,使用合并对象的方法Object.assign(resObj,item)也行
resList.forEach(item=>{
if(Object.values(item)==userObj.username){
resObj = {...item}
}
})
//当用户没有输入姓名,直接查询,会获取全部列表
if(userObj.username===''){
return {
code:200,
msg:'当前查询的是:该变电组内所有用户的信息',
resArr,
pages
}
}
//当输入的姓名,查询不到时调用
//返回当前村、变电组,全部列表数据
//pages是总页数
if(Object.keys(resObj).length===0){
return {
code:404,
msg:'抱歉!该变电组内未收录此用户的信息',
resArr,
pages
}
}
//当输入的姓名,数据库中有时调用
//msg的信息可以使用模板字符串
return {
code:200,
msg:`「 ${userObj.username} 」的电户号,查询结果如下:`,
resObj
}
}
四、新增数据的云函数
4.1 在原数据外,新增一个_id,add()
- 新增单一数据,用大括号包裹数据
{"name": "张三"}
。 - 新增多条数据,用中括号包裹数据
[{"name": "张三"},{"name": "李四"},{"name": "王五"}]
。
const db = uniCloud.database();
exports.main = async (event, context) => {
let resData = await db.collection('demo')
.add([{"name": "张三"},{"name": "李四"},{"name": "王五"}]);
let resList = await db.collection('demo').get();
return {
code:200,
msg:'添加数据成功',
resList
}
};//数据返回结果
{
"code": 200,
"msg": "添加数据成功",
"resList": {
"data": [{
"_id": "60210b53ef338d00016d2404",
....此处 .... 省略 ....一万字....
},
{
"_id": "6038d46620be4e00011e5cf4",
"name": "张三"
},
{
"_id": "6038d46620be4e00011e5cf5",
"name": "李四"
},
{
"_id": "6038d46620be4e00011e5cf6",
"name": "王五"
}
}]
}
4.2 在指定_id内,指定位置新增。
-
doc('_id').update();
方法可用于更改,也可用于新增。 -
普通数组添加、删除数据,官方文档中有示例,这里演示的是,数据为对象的数组
"group-1":[{key:value},{key:vlaue}]
如何新增。 -
update(),需紧随
doc()
之后,doc()
内写指定数据的_id
,因为一个集合内可能有多个_id
-
在
update()
内引用变量,需要使用[`${变量名}`]
-
在数组尾部新增对象,可以在中括号内写模板字符串
dbCmd.push([{...addUserObj}])
'use strict';
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
let {cunIndex,groupName,userObj} = event;
if(userObj.username==''||userObj.usernum==''){
return{
code:400,
msg:'新增数据不能为空'
}
}
let resData = await db.collection('demo').field({_id: 1,[`villages.${groupName}`]: 1}).get();
//用于获取_id,存放与data[{_id:***},{_id:***},{_id:***},***]中
let id = resData.data[`${cunIndex}`]['_id'];
let groupArr = resData.data[`${cunIndex}`].villages[`${groupName}`];
let isTrue = true;
//先查询,用户要新增的电户号码,在数据库中,有没有,有就赋值false
if (groupArr) {
groupArr.forEach(val=>{
if(Object.keys(val)==userObj.usernum){
isTrue = false;
}
});
//如果数据库中没有该电户号码,就在对应村、变电组的尾部新增该数据
if(isTrue){
let addUserObj = {[`${userObj.usernum}`]:userObj.username};
let resSucc = await db.collection('demo')
.doc(`${id}`)
.update({
villages: {
[`${groupName}`]: dbCmd.push([{...addUserObj}])
}
})
return {
code: 200,
msg: '数据添加成功',
resSucc
}
//当数据库中有该电户号码时,就提示已经存在
}else{
return {
code: 400,
msg: `抱歉!电户号「 ${userObj.usernum} 」已经存在`
}
}
}
//如果该村还未创建该变电组,就新建变电组,之后再新增用户信息
if (!groupArr) {
let resSucc = await db.collection('demo')
.doc(`${id}`)
.update({
villages: {
[`${groupName}`]: dbCmd.set([{
[`${userObj.usernum}`]: userObj.username
}])
}
})
return {
code: 200,
msg: '创建变电组成功,数据添加成功',
resSucc
}
}
}
五、更新数据的云函数
5.1 覆盖式更新
- 覆盖式更新的意义不大,会将原数据彻底清除,一般情况下用不到。
update({'villages':dbCmd.set({key:value})})
将villages做为字段,后面跟一个对象
const db = uniCloud.database();
const dbCmd = db.command;
exports.main = async (event, context) => {
let resSucc = await db.collection('demo')
.doc("600bfa4d7f06f30001b47259")
.update({
'villages':dbCmd.set({
"6640059601": "张三"
})
}
);
let resObj = await db.collection('demo').get();
return {
code:200,
resObj,
resSucc
}
}
//返回的数据结果 villages下只剩下一个对象了
"villages": {"6640059601":"张三"}
5.2 指定_id
下,指定位置更新。
- 指定位置更新,同上面的update()方法一致,这里省略。
六、删除数据的云函数
6.1 删除指定_id的数据库的全部数据
-
collection.doc(_id).remove()
-
doc()
内只能写字符串或者数字,可以使用ES6模板字符串doc(`${id}`)
// 删除指定_id的数据
const db = uniCloud.database();
exports.main = async (event, context) => {
const id = '60388f7eb6ce210001cbbf14';
await db.collection('demo')
.doc(`${id}`)
.remove();
let resData = await db.collection('demo').get();
return resData;
};
6.2 删除指定位置的数据
- 在update()内使用remove(),方法挂载在command下,不能用于数组,删除后,数组原索引位置会变成null。但是可以用于对象,删除key就等于删除了该对象。doc(`${id}`).update({villages:{name:dbCmd.remove()}})
- 更新数组,可以先获取数组,再修改数组,最后把新数组更新到原位置即可。update({villages:{[`${groupName}`]:[...groupArr]}})
'use strict';
const db = uniCloud.database();
exports.main = async (event, context) => {
let {cunIndex,groupName,userObj} = event;
let resData = await db.collection('demo').field({_id: 1,[`villages.${groupName}`]: 1}).get();
let id = resData.data[`${cunIndex}`]['_id'];
let groupArr = resData.data[`${cunIndex}`].villages[`${groupName}`];
let isTrue = false;
var index = 0;
if (!groupArr) {
return {
code: 400,
msg: '抱歉!该村还未创建此变电组数据',
resSucc
}
};
groupArr.forEach((val,i)=>{
if(Object.keys(val)==userObj.usernum){
isTrue = true;
index = i
}
});
//先查询数据在数组中的索引,使用splice,删除数据,把获得的新数组,更新到原位置处
if(isTrue){
groupArr.splice([`${index}`],1);
let resData = await db.collection('demo')
.doc(`${id}`)
.update({
villages:{
[`${groupName}`]:[...groupArr]
}
})
return {
code: 200,
msg: `「 ${userObj.usernum} 」数据删除成功`,
index,
groupArr,
resData
}
};
return {
code: 400,
msg: '数据删除失败,原因:电户号错误'
}
}
七、处理云函数返回数据的工具函数
- 工具函数的路径
common/api/index.js
- HandleUnicloud有两个参数,用于
uniCloud.callFunction({name,data})
传参。 cloudName
表示,需要调用哪个云函数。mydata
表示,用户向云函数传递的数据。
const HandleUnicloud = (cloudName,mydata)=>{
return new Promise((reslove,reject)=>{
uniCloud.callFunction({
name:cloudName,
data:mydata
}).then(res=>{
if(res.result){
reslove(res.result)
}else{
reject(res.result)
}
}).catch(res=>{
console.log(res);
})
});
};
export default{
HandleUnicloud
}
八、全局注册处理云函数数据的工具函数
- 先引入
import api from './common/api'
- 再注册
Vue.prototype.$api = api
import Vue from 'vue'
import App from './App'
import api from './common/api'
Vue.config.productionTip = false
Vue.prototype.$api = api
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
九、页面调用的方法
this.$api.HandleUnicloud()
addData() {
//用户传给云函数的数据,赋值给mydata
let mydata = this.mydata;
//当前需要调用的云函数名称
let cloudName = 'UpdateAdd';
//用于检查姓名和电户号码的正则函数checkName()和checkNum()
if (!this.checkName(this.mydata.userObj.username) || !this.checkNum(this.mydata.userObj.usernum)) {
this.tip = '抱歉,姓名和电户号,数据有误!';
return;
}
//有确定和取消按键的模态框
uni.showModal({
title: '请选择',
content: `您确定要添加${mydata.userObj.username}吗?`,
cancelText:'取消添加',
confirmText:'确定添加',
//这里应当使用箭头函数,否则影响this.$api和this.tip的使用
success: res=> {
if (res.confirm) {
this.$api.HandleUnicloud(cloudName, mydata).then(v => {
this.tip = v.msg;
uni.showLoading({
title: this.tip
});
setTimeout(() => {
uni.hideLoading();
}, 1000);
});
} else if (res.cancel) {
this.tip = '选择了取消!'
}
}
});
}
十、写在最后
非常感谢,DCloud公司提供如此强大的生态系统,让我们去学习进步的效率大大的提升。
最后附上H5白~嫖建站的最后一关,跨域配置问题,将前端网页托管——参数配置——默认网址,复制粘贴到跨域配置——新增域名中,即可手机,PC端都能正常访问了。
目前uniCloud</font>提供了,免费的阿里云空间,</font>完全可以满足个人用户的建站使用。
只需要在https://unicloud.dcloud.net.cn/login申请账号就可以获得:
1、一个免费的云数据库,用于保存JSON格式的数据
2、一个免费的存储云函数的空间,用于保存可以在云端调用修改云数据库数据的函数,空间环境为nodejs,操作基于mongoDB,有所改动。
3、一个免费的云存储,用于保存图片、视频、文件等,并能够提供对应的访问地址
4、一个免费的网页托管,自动生成可访问的网址,只需处理跨域配置即可。
个人博客地址

【分享】uniCloud云函数结合nodemailer发送邮件的方法
网上找的node发邮件,用到最多的是nodemailer,于是利用百度出来的代码,直接运行在uniCloud,本地运行发送成功,但是云端一直报错。
仔细看了报错内容,提到Thread,所以猜测代码里新建了线程去发邮件。
为了找到如何用当前线程发,第一时间想到的是nodemailer的官方文档:https://nodemailer.com/about/
结合官方文档,写了个可以在uniCloud云端正常发邮件,并且亲测成功的代码:
'use strict';
const nodemailer = require('nodemailer') // 记得在当前文件夹执行npm install nodemailer后才能使用
exports.main = async (event, context) => {
let transporter = nodemailer.createTransport({
host: 'smtp.126.com',
secureConnection: true,
port: 465,
secure: true,
auth: {
user: 'yourname@126.com',
pass: 'SMTP授权码'
}
})
const info = await transporter.sendMail({
from: '"邮箱昵称"<yourname@126.com>',
to: 'receiver@163.com',
subject: '主题',
html: '<h1>HTML代码</h1>',
text: '文本'
})
if (info.messageId) {
return {code: 0, msg: '发送成功'}
} else {
return {code: 1, msg: '发送失败', info}
}
}
代码里最关键的是await transporter.sendMail这行,这样写才是在当前线程发
百度上找到的大部分人是这么写的:
transporter.sendMail({
from: '"邮箱昵称"<yourname@126.com>',
to: 'receiver@163.com',
subject: '主题',
html: '<h1>HTML代码</h1>',
text: '文本'
}, (error, info) => {
if (error) {
return console.log(error);
}
console.log(info)
})
这样写的话会新建线程,这在普通的node环境当然没错,但是uniCloud却报了关于线程的错误,猜测是uniCloud不允许多线程
网上找的node发邮件,用到最多的是nodemailer,于是利用百度出来的代码,直接运行在uniCloud,本地运行发送成功,但是云端一直报错。
仔细看了报错内容,提到Thread,所以猜测代码里新建了线程去发邮件。
为了找到如何用当前线程发,第一时间想到的是nodemailer的官方文档:https://nodemailer.com/about/
结合官方文档,写了个可以在uniCloud云端正常发邮件,并且亲测成功的代码:
'use strict';
const nodemailer = require('nodemailer') // 记得在当前文件夹执行npm install nodemailer后才能使用
exports.main = async (event, context) => {
let transporter = nodemailer.createTransport({
host: 'smtp.126.com',
secureConnection: true,
port: 465,
secure: true,
auth: {
user: 'yourname@126.com',
pass: 'SMTP授权码'
}
})
const info = await transporter.sendMail({
from: '"邮箱昵称"<yourname@126.com>',
to: 'receiver@163.com',
subject: '主题',
html: '<h1>HTML代码</h1>',
text: '文本'
})
if (info.messageId) {
return {code: 0, msg: '发送成功'}
} else {
return {code: 1, msg: '发送失败', info}
}
}
代码里最关键的是await transporter.sendMail这行,这样写才是在当前线程发
百度上找到的大部分人是这么写的:
transporter.sendMail({
from: '"邮箱昵称"<yourname@126.com>',
to: 'receiver@163.com',
subject: '主题',
html: '<h1>HTML代码</h1>',
text: '文本'
}, (error, info) => {
if (error) {
return console.log(error);
}
console.log(info)
})
这样写的话会新建线程,这在普通的node环境当然没错,但是uniCloud却报了关于线程的错误,猜测是uniCloud不允许多线程
原文出处:https://coding3.com/archives/uniCloud-nodemailer.html
收起阅读 »
红包雨--简单实现,没有过度的修饰
<template>
<view class="home">
<view class="content">
<view @animationend="runend(index)" @animationstart="runstart(index)" v-for="(item,index) in packStyle" :key="index" class="envelope" :style="item">
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
packStyle: []
};
},
onLoad() {
// 随即生成红包
this.createRedPack();
},
mounted() {
},
onReady() {
},
methods: {
runstart(key){
if(key===0){
console.log('监听动画开始');
}
},
runend(key){
if(key===this.packStyle.length-1){
console.log('监听动画结束---下一波红包开始');
this.createRedPack()
}
},
createRedPack(){
// 随机生成30个红包
var initNumber = 0;
for (var i = 0; i < 60; i++) {
let lefts = (Math.floor(Math.random()*5+5)); // 随机边距
let delay = Math.floor(Math.random()*5+2); // 延迟时间
initNumber+=lefts; // 确保唯一,不让红包出现重叠现象
this.packStyle.push({
'left': initNumber+'%',
'top': lefts+'px',
'animation-delay': delay/2+'s'
})
}
}
}
};
</script>
<style>
.home {
width: 100%;
height: 100%;
}
.content {
position: relative;
height: 100%;
background: #fff;
overflow: hidden;
}
.envelope {
position: fixed;
opacity: 0;
animation: drops 1.2s cubic-bezier(.22,.22,.39,.26) 1;
width: 60px;
height: 60px;
background: url(../../static/hongbaotu.png) no-repeat;
/* background-color: #007AFF; */
background-size: 60px 60px;
}
@keyframes drops {
0% {
opacity: 0;
}
20% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
transform: translate3d(10px, 100vh, -10px);
}
}
</style>
<template>
<view class="home">
<view class="content">
<view @animationend="runend(index)" @animationstart="runstart(index)" v-for="(item,index) in packStyle" :key="index" class="envelope" :style="item">
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
packStyle: []
};
},
onLoad() {
// 随即生成红包
this.createRedPack();
},
mounted() {
},
onReady() {
},
methods: {
runstart(key){
if(key===0){
console.log('监听动画开始');
}
},
runend(key){
if(key===this.packStyle.length-1){
console.log('监听动画结束---下一波红包开始');
this.createRedPack()
}
},
createRedPack(){
// 随机生成30个红包
var initNumber = 0;
for (var i = 0; i < 60; i++) {
let lefts = (Math.floor(Math.random()*5+5)); // 随机边距
let delay = Math.floor(Math.random()*5+2); // 延迟时间
initNumber+=lefts; // 确保唯一,不让红包出现重叠现象
this.packStyle.push({
'left': initNumber+'%',
'top': lefts+'px',
'animation-delay': delay/2+'s'
})
}
}
}
};
</script>
<style>
.home {
width: 100%;
height: 100%;
}
.content {
position: relative;
height: 100%;
background: #fff;
overflow: hidden;
}
.envelope {
position: fixed;
opacity: 0;
animation: drops 1.2s cubic-bezier(.22,.22,.39,.26) 1;
width: 60px;
height: 60px;
background: url(../../static/hongbaotu.png) no-repeat;
/* background-color: #007AFF; */
background-size: 60px 60px;
}
@keyframes drops {
0% {
opacity: 0;
}
20% {
opacity: 1;
}
90% {
opacity: 1;
}
100% {
opacity: 0;
transform: translate3d(10px, 100vh, -10px);
}
}
</style>
收起阅读 »

安卓离线打包小米推送Register mipush appId or appKey is null or empty的解决方法
安卓离线打包,小米离线推送,打开页面,提示Register mipush appId or appKey is null or empty

问了官方人员,见这个帖子。https://ask.dcloud.net.cn/question/118126
我们分3步来分析可能会出什么问题。
第一步、引入lib:aps-release.aar和aps-unipush-release.aar
第二步、build配置key
第三步、工程的xml引入这个key
第一步,类库没有引入。
因为我的华为测试还好的,可以离线推送,所以我首先排除掉这个问题,我尝试把这两个类库去掉,安装好app,会提示push库不存在。
所以,排除掉这个问题。
第二步、build配置key
这步,似乎也没啥好看的,按照官方的离线打包文档,就是这么写的。
第三步、工程的xml引入这个key
后来读了push的安卓源代码。
发现是这步的问题,meta-data的位置放错了,要放到application后面,但奇怪的是华为的也在前面,不知道为什么能识别。
如果大家觉得受益匪浅的话,记得打赏一下哈。有问题加我QQ:13040
安卓离线打包,小米离线推送,打开页面,提示Register mipush appId or appKey is null or empty
问了官方人员,见这个帖子。https://ask.dcloud.net.cn/question/118126
我们分3步来分析可能会出什么问题。
第一步、引入lib:aps-release.aar和aps-unipush-release.aar
第二步、build配置key
第三步、工程的xml引入这个key
第一步,类库没有引入。
因为我的华为测试还好的,可以离线推送,所以我首先排除掉这个问题,我尝试把这两个类库去掉,安装好app,会提示push库不存在。
所以,排除掉这个问题。
第二步、build配置key
这步,似乎也没啥好看的,按照官方的离线打包文档,就是这么写的。
第三步、工程的xml引入这个key
后来读了push的安卓源代码。
发现是这步的问题,meta-data的位置放错了,要放到application后面,但奇怪的是华为的也在前面,不知道为什么能识别。
如果大家觉得受益匪浅的话,记得打赏一下哈。有问题加我QQ:13040

个推解读Android 12首个开发者预览版
引言
近日,Google发布了新系统 Android 12 的首个开发者预览版。根据谷歌官方消息,最终版本的Android 12预计于今年下半年正式上线。此次发布预览版的目的主要是帮助开发者提前了解Android 12的新变化,为后续进行应用适配提前做准备。
作为陪伴开发者多年的重要伙伴,个推一直密切关注和跟进行业发展趋势。在Android12首个开发者预览版发布后,个推快速对新系统的有关更新展开了调研。本文对Android 12预览版的部分新功能、新特性进行了解读,希望能帮助广大开发者对新系统有个快速了解。
Android 12行为变更:面向所有应用
用户体验升级
沉浸式手势导航改进
从Android 10 开始,Android 系统就已支持手势导航,致力于给用户带来沉浸式的全新体验。在之前版本的系统下,用户启动安卓的沉浸模式,默认需要的手势操作是:从屏幕侧方滑入,退出沉浸模式,然后再返回上一个界面。最新发布的Android 12首个开发者预览版则对手势导航模式进行了简化:用户不再需要滑动两次,只需一次滑动手势,即可退出全屏沉浸模式并返回上个界面,更加高效和便利。
隐私/安全保护增强
MAC 地址权限限制
为进一步保护用户隐私和数据安全,Android 11引入了“单次授权”“权限自动重置”“分区存储”等功能。Android 12则更进一步限制了所有非系统应用程序对设备MAC地址的访问,无论目标API级别如何。
相关API返回空值或占位符值,具体取决于应用程序的目标SDK版本:
➀ target = 12 ,返回 null
➁ target <12 ,返回 02:00:00:00:00:00
未被信任的触摸事件将被阻止
为了保护系统安全和更好的用户体验,Android 12将会阻止某些窗口的触摸。
例如:
➀ 申请了 SYSTEM_ALERT_WINDOW 权限的 windows
➁ Toast messages
应用不能关闭系统对话框
ACTION_CLOSE_SYSTEM_DIALOGS intent 在 Android 12 被废弃。
➀ target = 12, 将抛出 SecurityException 异常
➁ target < 12 ,该 intent 不会执行,会在 logcat 打印警告日志:
E ActivityTaskManager Permission Denial:
android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from
com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS,
dropping broadcast.
Android 12行为变更:针对Target = 12的应用
自定义通知栏
之前,开发者能自定义整个通知栏区域的布局和样式,这就导致了不同设备的兼容适配问题以及用户的浏览不适应问题。
Android 12更改了完全自定义通知的外观。对于 target = 12 的应用,在通知栏的消息展示均使用统一的模板。上面应用名字显示和折叠按钮都是相同的、固定的,下面折叠和展开状态呈现的区域是可自定义的:
折叠和展开的样式:
折叠状态
展开状态
若APP中存在自定义Notification.Style,亦或是使用了Notification.Builder中 setCustomContentView(RemoteViews), setCustomBigContentView(RemoteViews)和setCustomHeadsUpContentView(RemoteViews)方法,可能会受此影响。
隐私/安全
WebView 中的SameSite cookie行为
Android的WebView组件基于Chromium来提高安全性和隐私性,去年,Chromium对第三方Cookie的处理方式进行了更改,并已面向众多Chrome用户推出。从Android 12开始,这些更改将应用于WebView。
SameSitecookie的属性控制它是否可以与任何请求一起发送,还是只能与相同站点的请求一起发送。Android 12中的WebView基本版本(版本89.0.4385.0)改进了第三方Cookie的默认处理,将有助于防止意外的跨站点共享。
ADB backup 限制
Android 12 限制了 adb backup 命令行的默认行为 (该命令行是用来备份恢复数据的),对应用程序数据adb backup有依赖的开发者可以在清单文件中设置 android:debuggable 为 true。
组件需要添加 exported 配置
target=12时,使用的activity 、service或者广播有用到 intent filters ,则需声明 android:exported 属性。不配置的话,在安卓 12 设备上将不能安装,logcat 也会打印错误日志:
Targeting S+ (version 10000 and above) requires that an explicit value for
android:exported be defined when intent filters are present
Pending intents 必须声明意图
使用 PendingIntent 需要声明 PendingIntent.FLAG_MUTABLE 或者 PendingIntent.FLAG_IMMUTABLE flag,否则系统会抛出异常 IllegalArgumentException。
性能
前台服务启动限制
以 Android 12 为目标的应用程序,无法在后台运行时启动前台服务,应用程序在后台运行时,可考虑使用 WorkManager 执行任务。
ForegroundService通知延迟
前台服务启动后必须调用startForeground() 来显示前台通知,如果应用在5s内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。
在Android 12中,限制时间由5s改为了10s。这样一来,对于部分APP来讲,将会有更充分的处理时间。
通知跳转
services 或者 broadcast receivers 中创建的通知将不能调用 startActivity() !!!
logcat 会打印:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \this should be avoided for performance reasons.
总结
以上,是个推对Android 12首个开发者预览版本中几个重要更新点的解读。
除了以上内容外,Android 12 预览版还在视频、音频和图片处理方面做了很多有趣的更新。比如,通过手机的振动马达增加对触觉耦合音频效果的支持,可以帮助游戏类APP提升玩家体验;引入了兼容媒体转码功能,可以让那些不支持 HEVC的应用,也能将文件高效转码为 AVC 格式;同时还引入了对 AV1 图像文件格式(AVIF)的支持,使得开发者可以同样的文件大小,收获比 JPEG 图像更高的图像质量……感兴趣的开发者可以进入Android 12官网进一步详细了解。
https://developer.android.google.cn/about/versions/12
后续,个推还将在持续打磨开发者服务和SDK产品的同时,密切跟进移动开发领域的相关动态,为开发者升级产品功能、迭代服务体验提供有效建议。
也欢迎更多的开发者和我们一起交流和探索Android及移动开发新技术,共同建设更好的安卓开发生态。
*本文图片来源于Android官网
引言
近日,Google发布了新系统 Android 12 的首个开发者预览版。根据谷歌官方消息,最终版本的Android 12预计于今年下半年正式上线。此次发布预览版的目的主要是帮助开发者提前了解Android 12的新变化,为后续进行应用适配提前做准备。
作为陪伴开发者多年的重要伙伴,个推一直密切关注和跟进行业发展趋势。在Android12首个开发者预览版发布后,个推快速对新系统的有关更新展开了调研。本文对Android 12预览版的部分新功能、新特性进行了解读,希望能帮助广大开发者对新系统有个快速了解。
Android 12行为变更:面向所有应用
用户体验升级
沉浸式手势导航改进
从Android 10 开始,Android 系统就已支持手势导航,致力于给用户带来沉浸式的全新体验。在之前版本的系统下,用户启动安卓的沉浸模式,默认需要的手势操作是:从屏幕侧方滑入,退出沉浸模式,然后再返回上一个界面。最新发布的Android 12首个开发者预览版则对手势导航模式进行了简化:用户不再需要滑动两次,只需一次滑动手势,即可退出全屏沉浸模式并返回上个界面,更加高效和便利。
隐私/安全保护增强
MAC 地址权限限制
为进一步保护用户隐私和数据安全,Android 11引入了“单次授权”“权限自动重置”“分区存储”等功能。Android 12则更进一步限制了所有非系统应用程序对设备MAC地址的访问,无论目标API级别如何。
相关API返回空值或占位符值,具体取决于应用程序的目标SDK版本:
➀ target = 12 ,返回 null
➁ target <12 ,返回 02:00:00:00:00:00
未被信任的触摸事件将被阻止
为了保护系统安全和更好的用户体验,Android 12将会阻止某些窗口的触摸。
例如:
➀ 申请了 SYSTEM_ALERT_WINDOW 权限的 windows
➁ Toast messages
应用不能关闭系统对话框
ACTION_CLOSE_SYSTEM_DIALOGS intent 在 Android 12 被废弃。
➀ target = 12, 将抛出 SecurityException 异常
➁ target < 12 ,该 intent 不会执行,会在 logcat 打印警告日志:
E ActivityTaskManager Permission Denial:
android.intent.action.CLOSE_SYSTEM_DIALOGS broadcast from
com.package.name requires android.permission.BROADCAST_CLOSE_SYSTEM_DIALOGS,
dropping broadcast.
Android 12行为变更:针对Target = 12的应用
自定义通知栏
之前,开发者能自定义整个通知栏区域的布局和样式,这就导致了不同设备的兼容适配问题以及用户的浏览不适应问题。
Android 12更改了完全自定义通知的外观。对于 target = 12 的应用,在通知栏的消息展示均使用统一的模板。上面应用名字显示和折叠按钮都是相同的、固定的,下面折叠和展开状态呈现的区域是可自定义的:
折叠和展开的样式:
折叠状态
展开状态
若APP中存在自定义Notification.Style,亦或是使用了Notification.Builder中 setCustomContentView(RemoteViews), setCustomBigContentView(RemoteViews)和setCustomHeadsUpContentView(RemoteViews)方法,可能会受此影响。
隐私/安全
WebView 中的SameSite cookie行为
Android的WebView组件基于Chromium来提高安全性和隐私性,去年,Chromium对第三方Cookie的处理方式进行了更改,并已面向众多Chrome用户推出。从Android 12开始,这些更改将应用于WebView。
SameSitecookie的属性控制它是否可以与任何请求一起发送,还是只能与相同站点的请求一起发送。Android 12中的WebView基本版本(版本89.0.4385.0)改进了第三方Cookie的默认处理,将有助于防止意外的跨站点共享。
ADB backup 限制
Android 12 限制了 adb backup 命令行的默认行为 (该命令行是用来备份恢复数据的),对应用程序数据adb backup有依赖的开发者可以在清单文件中设置 android:debuggable 为 true。
组件需要添加 exported 配置
target=12时,使用的activity 、service或者广播有用到 intent filters ,则需声明 android:exported 属性。不配置的话,在安卓 12 设备上将不能安装,logcat 也会打印错误日志:
Targeting S+ (version 10000 and above) requires that an explicit value for
android:exported be defined when intent filters are present
Pending intents 必须声明意图
使用 PendingIntent 需要声明 PendingIntent.FLAG_MUTABLE 或者 PendingIntent.FLAG_IMMUTABLE flag,否则系统会抛出异常 IllegalArgumentException。
性能
前台服务启动限制
以 Android 12 为目标的应用程序,无法在后台运行时启动前台服务,应用程序在后台运行时,可考虑使用 WorkManager 执行任务。
ForegroundService通知延迟
前台服务启动后必须调用startForeground() 来显示前台通知,如果应用在5s内未调用 startForeground(),则系统将停止服务并声明此应用为 ANR。
在Android 12中,限制时间由5s改为了10s。这样一来,对于部分APP来讲,将会有更充分的处理时间。
通知跳转
services 或者 broadcast receivers 中创建的通知将不能调用 startActivity() !!!
logcat 会打印:
Indirect notification activity start (trampoline) from PACKAGE_NAME, \this should be avoided for performance reasons.
总结
以上,是个推对Android 12首个开发者预览版本中几个重要更新点的解读。
除了以上内容外,Android 12 预览版还在视频、音频和图片处理方面做了很多有趣的更新。比如,通过手机的振动马达增加对触觉耦合音频效果的支持,可以帮助游戏类APP提升玩家体验;引入了兼容媒体转码功能,可以让那些不支持 HEVC的应用,也能将文件高效转码为 AVC 格式;同时还引入了对 AV1 图像文件格式(AVIF)的支持,使得开发者可以同样的文件大小,收获比 JPEG 图像更高的图像质量……感兴趣的开发者可以进入Android 12官网进一步详细了解。
https://developer.android.google.cn/about/versions/12
后续,个推还将在持续打磨开发者服务和SDK产品的同时,密切跟进移动开发领域的相关动态,为开发者升级产品功能、迭代服务体验提供有效建议。
也欢迎更多的开发者和我们一起交流和探索Android及移动开发新技术,共同建设更好的安卓开发生态。
*本文图片来源于Android官网
收起阅读 »
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
因为升级v3 之前,web上是通过 .uni-checkbox-input 来修改的复选框样式,app是通过 .wx-checkbox-input 来修改的复选框样式
升级之后.wx-checkbox-input就失效了,统一使用.uni-checkbox-input了
uniapp升级v3之后,手机上的复选框样式没有了,h5还是正常
因为升级v3 之前,web上是通过 .uni-checkbox-input 来修改的复选框样式,app是通过 .wx-checkbox-input 来修改的复选框样式
升级之后.wx-checkbox-input就失效了,统一使用.uni-checkbox-input了
收起阅读 »
mui ajax ios wkwebview请求的问题
之前项目jquery+mui写的,脏乱差,现在进行改版,采用vue+mui,去掉沉重的html拼接,开始上手顺的一塌糊涂,后面打包发布真机,android的一切照旧,ios的ajax访问不到数据.心凉了一截,后面是所有代码放在mui.plusready里面,但是html里面却又不能实现vue的渲染,愁啊;调试都不方便;
后面又是百度又是啥的终于琢磨出一个办法,也不知道对大家有没有用,我这里在下面贴出来;
如下代码:
//业务页面js
mui.init();
mui.plusReady(function() {
my_packer_info.initPackageList(); //要写,否者会出现第一次打开页面没数据,因为当时plus还没被初始化,这里只要调一下vue里面的初始化数据的方法即可
});
var my_packer_info = new Vue({
el: "#my_packer_info",
data:{},
methods: {
//初始化包裹列表
initPackageList: function() {
//;
var req_data = {
PlatForm: this.PlatForm,
Shop: this.ClientInfo.ClientInfo.Shop,
Page: 1,
PageCount: 100,
}
let that = this;
//自己在mui.ajax外面封装了一层的方法,同时也要改一下mui.js里面的ajax模块的xhr的创建
AxiosPost('GetBillCode', req_data, (res) => {
if (res.State) {
....
} else {
mui.toast("系統繁忙,請稍後重試:" + res.MsgText);
}
}, (err) => {
mui.toast("系統繁忙,請稍後重試:" + err);
});
}
},
created: function() {
this.initPackageList();
}
});
//mui.js里面 $.ajax模块的修改,不是很完善,大家可以自行加点判断,我这里是从ajax下面有WKWebView提醒哪里直接拷贝过来用的
xhr: function(protocol) {
if(location.protocol === 'file:' && $.os.ios && window.webkit && window.webkit.messageHandlers && !(xhr instanceof plus.net.XMLHttpRequest)){
return plus.net.XMLHttpRequest;
}
else{
return new window.XMLHttpRequest();
}
}
之前项目jquery+mui写的,脏乱差,现在进行改版,采用vue+mui,去掉沉重的html拼接,开始上手顺的一塌糊涂,后面打包发布真机,android的一切照旧,ios的ajax访问不到数据.心凉了一截,后面是所有代码放在mui.plusready里面,但是html里面却又不能实现vue的渲染,愁啊;调试都不方便;
后面又是百度又是啥的终于琢磨出一个办法,也不知道对大家有没有用,我这里在下面贴出来;
如下代码:
//业务页面js
mui.init();
mui.plusReady(function() {
my_packer_info.initPackageList(); //要写,否者会出现第一次打开页面没数据,因为当时plus还没被初始化,这里只要调一下vue里面的初始化数据的方法即可
});
var my_packer_info = new Vue({
el: "#my_packer_info",
data:{},
methods: {
//初始化包裹列表
initPackageList: function() {
//;
var req_data = {
PlatForm: this.PlatForm,
Shop: this.ClientInfo.ClientInfo.Shop,
Page: 1,
PageCount: 100,
}
let that = this;
//自己在mui.ajax外面封装了一层的方法,同时也要改一下mui.js里面的ajax模块的xhr的创建
AxiosPost('GetBillCode', req_data, (res) => {
if (res.State) {
....
} else {
mui.toast("系統繁忙,請稍後重試:" + res.MsgText);
}
}, (err) => {
mui.toast("系統繁忙,請稍後重試:" + err);
});
}
},
created: function() {
this.initPackageList();
}
});
//mui.js里面 $.ajax模块的修改,不是很完善,大家可以自行加点判断,我这里是从ajax下面有WKWebView提醒哪里直接拷贝过来用的
xhr: function(protocol) {
if(location.protocol === 'file:' && $.os.ios && window.webkit && window.webkit.messageHandlers && !(xhr instanceof plus.net.XMLHttpRequest)){
return plus.net.XMLHttpRequest;
}
else{
return new window.XMLHttpRequest();
}
}
收起阅读 »

关于前端弹窗无法覆盖原生导航栏及tabbar的一个解决方案
直接简单的先说一下我的需求, 首页加载的时候有弹窗,可能会多个,但是需求不能一次性全部弹出需要队列弹出,就是关闭一个之后再弹出下一个。这边使用到了vuex来存储全局弹窗队列 最下方附带有效果视频
这边是再App.vue中监听一下vuex > state中的弹窗队列
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length >= 1 && oldVal.length == 0) { //该条件存在则证明是第一个弹窗,需要先跳转页面
this.myRouter({ //这边是跳转一个新页面
type: "navigateTo",
url: "/pages/public/alert/alert",
animationType:"fade-in",
animationDuration:300
})
}
}
}
///pages/public/alert/alert跳转的新页面中的page.json一定要这样设置,这样这个页面就把下面的页面给覆盖住包括tabbar与到导航栏,并且这个页面是透明的你可以再页面的style中自己给背景透明色
"style": {
"navigationStyle": "custom",
"app-plus": {
"animationType": "fade-in",
"animationDuration": "200",
"bounce": "none",
"backgroundColor": "rgba(0,0,0,0)" // 背景透明
}
}
在这个新页面中监听(也是这个队列)
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length == 0) { //如果是最后一个弹窗的情况下就返回也就是关闭这个弹窗容器
uni.navigateBack({})
}
}
},
这个就是你要显示的弹窗因为考虑到后期弹窗多的话我就做成了组件形式
说明:ALERT_LIST[0]就是拿这个队列中的第一项 name:就是你在往队列中添加新弹窗的时候定义的要与下面代码中的v-if后面对应的值一致这样在队列往前走的时候他就能显示对应的弹窗
<!-- 隐私的弹出 -->
<protocol-alert @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'protoco'"></protocol-alert>
<!-- //版本更新的弹出 -->
<versionup-date @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'version'" :versionInfo="ALERT_LIST[0].data"></versionup-date>
<!-- //淘宝客猜你想找的弹出 -->
<copy-search @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'copysearch'"></copy-search>
注:在弹窗中如果有按钮出发了关闭弹窗要将队列中的这一项删除掉 (代码只是提供了一个大概的思路具体写法看你自己)
直接简单的先说一下我的需求, 首页加载的时候有弹窗,可能会多个,但是需求不能一次性全部弹出需要队列弹出,就是关闭一个之后再弹出下一个。这边使用到了vuex来存储全局弹窗队列 最下方附带有效果视频
这边是再App.vue中监听一下vuex > state中的弹窗队列
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length >= 1 && oldVal.length == 0) { //该条件存在则证明是第一个弹窗,需要先跳转页面
this.myRouter({ //这边是跳转一个新页面
type: "navigateTo",
url: "/pages/public/alert/alert",
animationType:"fade-in",
animationDuration:300
})
}
}
}
///pages/public/alert/alert跳转的新页面中的page.json一定要这样设置,这样这个页面就把下面的页面给覆盖住包括tabbar与到导航栏,并且这个页面是透明的你可以再页面的style中自己给背景透明色
"style": {
"navigationStyle": "custom",
"app-plus": {
"animationType": "fade-in",
"animationDuration": "200",
"bounce": "none",
"backgroundColor": "rgba(0,0,0,0)" // 背景透明
}
}
在这个新页面中监听(也是这个队列)
watch: {
ALERT_LIST(newVal, oldVal) {
if (newVal.length == 0) { //如果是最后一个弹窗的情况下就返回也就是关闭这个弹窗容器
uni.navigateBack({})
}
}
},
这个就是你要显示的弹窗因为考虑到后期弹窗多的话我就做成了组件形式
说明:ALERT_LIST[0]就是拿这个队列中的第一项 name:就是你在往队列中添加新弹窗的时候定义的要与下面代码中的v-if后面对应的值一致这样在队列往前走的时候他就能显示对应的弹窗
<!-- 隐私的弹出 -->
<protocol-alert @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'protoco'"></protocol-alert>
<!-- //版本更新的弹出 -->
<versionup-date @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'version'" :versionInfo="ALERT_LIST[0].data"></versionup-date>
<!-- //淘宝客猜你想找的弹出 -->
<copy-search @closeAlert="_closeAlert" v-if="ALERT_LIST[0].name == 'copysearch'"></copy-search>
注:在弹窗中如果有按钮出发了关闭弹窗要将队列中的这一项删除掉 (代码只是提供了一个大概的思路具体写法看你自己)
收起阅读 »
UniPush从0到1
UniPush是DCloud联合个推公司推出的集成型统一推送服务,内建了苹果、华为、小米、OPPO、VIVO、魅族、谷歌FCM等手机厂商的系统级推送和个推等第三方推送。
推送的准备
- 1.CID 设备标识
// 最简单的推送把cid给到后端,后端通过cid来推送
const { clientid = '' } = plus.push.getClientInfo() || {} // 获取cid
- 2.alias 别名 给cid绑定一个别名(在个推服务器上)
// 别名推送后端不需要保存cid 前端打上别名后 后端通过别名进行推送 比如 用户ID 推送的时候只需要给相应的ID使用别名推送
ios
const GtSdk:any = plus.ios.importClass('GeTuiSdk')
// 为该设备绑定别名
GtSdk.bindAliasandSequenceNum(alias, alias)
Android
const PushManager:any = plus.android.importClass('com.igexin.sdk.PushManager')
const context:any = plus.android.runtimeMainActivity().getContext()
const Instance:any = PushManager.getInstance()
// 为该设备绑定别名
Instance.bindAlias(context, alias)
- 3.tag 把用户(cid)进行分组 (也是在个推服务器上)
// 后端通过标签进行推送 比如我们需要推送所有男性用户 首先要对用户打上标签(男) 后端只需要给个推服务器发送推送指令男 有效减少服务器压力
// uniPush 暂不支持前端打标签 (可由后端来打标签)
# 推送的接受
一般是可以写到App.vue文件里面
//消息的监听
plus.push.addEventListener(
'receive',
function (msg: any = {}) {
const isAndroid = plus.os.name === 'Android'
console.log(msg, !isAndroid)
if (!isAndroid) {
// 如果是IOS
const payload = msg.payload
// 【APP离线】收到消息,但没有提醒(发生在一次收到多个离线消息时,只有一个有提醒,但其他的没有提醒)
// 【APP在线】收到消息,不会触发系统消息,需要创建本地消息,但不能重复创建。必须加msg.type验证去除死循环
if (msg.aps == null && msg.type === 'receive') {
const messageTitle = payload.messageTitle || ''
const messageContent = payload.messageContent || ''
// 创建本地消息,发送的本地消息也会被receive方法接收到,但没有type属性,且aps是null
plus.push.createMessage(messageContent, JSON.stringify(payload), {
title: messageTitle
})
}
} else {
let payload:any = {}
console.log(typeof msg.payload === 'object', typeof msg.payload)
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
console.log(payload)
const messageTitle = payload.messageTitle || ''
const messageContent = payload.messageContent || ''
plus.push.createMessage(messageContent, JSON.stringify(payload), {
title: messageTitle
})
}
console.log(msg)
},
false
)
// 消息的点击事件
plus.push.addEventListener(
'click',
function (msg: any = {}) {
const isAndroid = plus.os.name === 'Android'
if (!isAndroid) {
// 如果是IOS
let payload
if (msg.type === 'click') {
// APP离线点击包含click属性,这时payload是JSON对象
payload = msg.payload
} else {
// APP在线,收到消息不会包含type属性,这时的payload是JSON字符串,需要转为JSON对象
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
}
// 点击后业务逻辑的处理
this.messageClick(payload)
} else {
// 如果是Android,收到playload均是是JSON字符串,需要转为JSON对象
let payload:any = {}
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
// 点击后业务逻辑的处理
this.messageClick(payload)
}
},
false
)
如果想离线推送需APP上架市场 申请厂商推送 把相应参数填写到Dcloud后台
UniPush是DCloud联合个推公司推出的集成型统一推送服务,内建了苹果、华为、小米、OPPO、VIVO、魅族、谷歌FCM等手机厂商的系统级推送和个推等第三方推送。
推送的准备
- 1.CID 设备标识
// 最简单的推送把cid给到后端,后端通过cid来推送
const { clientid = '' } = plus.push.getClientInfo() || {} // 获取cid
- 2.alias 别名 给cid绑定一个别名(在个推服务器上)
// 别名推送后端不需要保存cid 前端打上别名后 后端通过别名进行推送 比如 用户ID 推送的时候只需要给相应的ID使用别名推送
ios
const GtSdk:any = plus.ios.importClass('GeTuiSdk')
// 为该设备绑定别名
GtSdk.bindAliasandSequenceNum(alias, alias)
Android
const PushManager:any = plus.android.importClass('com.igexin.sdk.PushManager')
const context:any = plus.android.runtimeMainActivity().getContext()
const Instance:any = PushManager.getInstance()
// 为该设备绑定别名
Instance.bindAlias(context, alias)
- 3.tag 把用户(cid)进行分组 (也是在个推服务器上)
// 后端通过标签进行推送 比如我们需要推送所有男性用户 首先要对用户打上标签(男) 后端只需要给个推服务器发送推送指令男 有效减少服务器压力
// uniPush 暂不支持前端打标签 (可由后端来打标签)
# 推送的接受
一般是可以写到App.vue文件里面
//消息的监听
plus.push.addEventListener(
'receive',
function (msg: any = {}) {
const isAndroid = plus.os.name === 'Android'
console.log(msg, !isAndroid)
if (!isAndroid) {
// 如果是IOS
const payload = msg.payload
// 【APP离线】收到消息,但没有提醒(发生在一次收到多个离线消息时,只有一个有提醒,但其他的没有提醒)
// 【APP在线】收到消息,不会触发系统消息,需要创建本地消息,但不能重复创建。必须加msg.type验证去除死循环
if (msg.aps == null && msg.type === 'receive') {
const messageTitle = payload.messageTitle || ''
const messageContent = payload.messageContent || ''
// 创建本地消息,发送的本地消息也会被receive方法接收到,但没有type属性,且aps是null
plus.push.createMessage(messageContent, JSON.stringify(payload), {
title: messageTitle
})
}
} else {
let payload:any = {}
console.log(typeof msg.payload === 'object', typeof msg.payload)
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
console.log(payload)
const messageTitle = payload.messageTitle || ''
const messageContent = payload.messageContent || ''
plus.push.createMessage(messageContent, JSON.stringify(payload), {
title: messageTitle
})
}
console.log(msg)
},
false
)
// 消息的点击事件
plus.push.addEventListener(
'click',
function (msg: any = {}) {
const isAndroid = plus.os.name === 'Android'
if (!isAndroid) {
// 如果是IOS
let payload
if (msg.type === 'click') {
// APP离线点击包含click属性,这时payload是JSON对象
payload = msg.payload
} else {
// APP在线,收到消息不会包含type属性,这时的payload是JSON字符串,需要转为JSON对象
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
}
// 点击后业务逻辑的处理
this.messageClick(payload)
} else {
// 如果是Android,收到playload均是是JSON字符串,需要转为JSON对象
let payload:any = {}
if (typeof msg.payload === 'object') {
payload = msg.payload || {}
} else {
payload = JSON.parse(msg.payload) || {}
}
// 点击后业务逻辑的处理
this.messageClick(payload)
}
},
false
)
如果想离线推送需APP上架市场 申请厂商推送 把相应参数填写到Dcloud后台
收起阅读 »

【分享】小程序原生项目转成uni-app中的vue项目
本片文章将从以下三个方面进行讲解:
- 背景
- 使用转换
- 注意事项
一、背景
之前一段时间,调研过跨平台开发一系列框架,最终筛选出uni-app这款可以适用多端的开源框架。一套代码可以同时生成ios,Android,H5,微信小程序,支付宝小程序,百度小程序等。拓展能力很强,封装了H5+,支持nvue,也支持原生Android,ios开发。可以将原有的移动应用和H5应用改成uni-app应用。基于此,恰好可以将我们之前早期的小程序项目,转化成基于vue.js的uni-app组件
这将带来很多优势:
- 更优的组件渲染更新性能
- 更强的拓展性、和可维护性
- 更快的打包速度、和更便捷的打包方式
- 一套代码多端适用
- ...
二、使用转换
使用官方推荐的HBuilderX, github地址:https://github.com/zhangdaren/miniprogram-to-uniapp。该插件十分强大,通过简单的脚本命令,直接将我本地的小程序原生项目,clone生成了一份vue.js的项目。
诸如: 小程序setData的兼容、小程序生命周期的兼容,小程序各种api的封装,几乎涵盖了所有的小程序原生api。
但是尽管如此,仍是有很多需要注意的点,在实际开发中需要我们注意的。
事先声明,以下需要关注的点,是本人在迁移自己的小程序项目中遇到的切实存在 的问题,如果有更好的解决方案,欢迎讨论。
三、注意事项【踩坑】
- 对于wx.setData的处理:
HBuilderX转换过来之后,会在app.js中加入setData的重写,但这个是通过vue中this.$set和this.$get来实现的,也就是说尽管小程序中的setData没有被转化成this.xxx =...的方式(vue的语法),但仍然可以生效。可是如果setData处于原来小程序组件的生命周期中如attached,这种生命周期会被转换为beforeMount 这时setData就会有报错,解决办法:(1)修改成this.xx = ...的方式 (2) 删除生命周期,因为vue中不支持attache等生命周期函数, 如果非要在组件挂载或者更新的时候将prop或其他变量存储到data中,可以尝试通过watch函数。 - 对于wx.selectComponent
经过反复测试,uni-app不会转化这个api,但是转成vue组件后,这个api将不在生效,需要替换成ref获取节点。
解决方案:这点可以使用ref替换,如果转换前微信组件的生命周期中,可以直接this.$refs.xxx.api()。如果转换前是在微信lifetimes中定义的生命周期,uni-app会转换成vue的生命周期,这时用ref不能直接调用组件的api需要加一层 .$vm, 既this.$refs.xxx.$vm.api() - 由于小程序的语法不强制要求wxml必须最外层是单节点。组件中<page></page>节点下如果存在多节点,转换后会多一层dom节点, 当然如果在wxml中有最外层的container的话就忽略这个问题。
- 原生小程序:
<page> 1 2 3 </page>
- 转换后
<page> <view> 1 2 3 </view> </page>
这在组件中会非常致命,会直接引起之前的样式如滚动条超出,高度的一些100%设置,会失效。导致样式错乱,要格外小心。
- 原生小程序:
- 事件绑定
(1)catchtap="true" 在小程序支持这种写法,可以理解为绑定一个空函数,转换之后变成@tap.stop="true" 这个在uni-app中会报错 需要改成 @tap.stop="emptyFunc".
(2)很多catch方法会被自动转换,如catchtap ----> @tap.stop 但是有些方法如catchtouchmove,这个方法不会转为@touchmove.stop,需要手动修改 。 - 转换过程部分函数名会被修改,如果小程序中定义了xxx,以""开头得函数名都会被修改名字,在调用的时候就找不到对应的函数了,以及vue内置得hide、show等方法名字会被修改成hideFun:
- data中引用类型的状态,以“_”开头的状态均无法通过this访问,都是undefined,需要手动更改状态变量的名称。
- 通过ref获取子组件的data,需要去掉data,如this.$ref.modal.data.name 需要换成 this.$ref.modal.name
- 有时我们想要父组件访问子组件的属性和方法,会将子组件的this作用域回调给父组件,这种方式在转化为uni-app后不会直接报错,但是无法获取到,进行下一步操作会报错。解决方案:在父组件直接使用ref。
- 一些写死的属性数据转换过来可能会变成 字符串,如showTitle="{{true}}"转换之后变成 showTitle="true" 这个时候如果子组件还当作boolean类型处理,就会报错。
- 奇怪的input:如果需要动态绑定input的value,并针对用户输入做一些必要的过滤,如不允许输入数字。 需要在用户输入的时候更改对应的value值,同时在input事件中要把经过js处理的value值return,这样才可以成功过滤input的value,否则不管如果用正则拦截,input输入框上都会输入啥显示啥。
- . vue组件中data中的数据this.xxx去获取,小程序转过来极少的时候是this.data.xxx需要手动更改一下,这个问题具体什么时候出现还不太明确,但是需要注意一下。
四、小程序组件
pages.json文件,这块和小程序不同,不需要再页面级json中配置
{
"pages": [
{
"path": "slide-view/slide-view",
"style": {
"navigationBarTitleText": "slide-view",
"usingComponents": {
"slide-view": "/wxcomponents/miniprogram-slide-view/index"
}
}
}
]
}
.vue:
<slide-view></slide-view>可直接使用
需要注意:
• 小程序组件需要放在项目特殊文件夹 wxcomponents(或 mycomponents、swancomponents)。HBuilderX 建立的工程 wxcomponents 文件夹在 项目根目录下。vue-cli 建立的工程 wxcomponents 文件夹在 src 目录下。可以在 vue.config.js 中自定义其他目录
• 小程序组件的性能,不如vue组件。使用小程序组件,需要自己手动setData,很难自动管理差量数据更新。而使用vue组件会自动diff更新差量数据。所以如无明显必要,建议使用vue组件而不是小程序组件。比如某些小程序ui组件,完全可以用更高性能的uni ui替代。
结论
ok,以上就是本人在项目转换迁移过程中遇到的问题。总体感受还是不错的,该说不说的,HBuilderX还是帮我们做了更大部分的事情,但仍需要我们针对各自的业务需求,做一些写法上的调整,做一切事物之前,需要头脑中有一个概念,“小程序中的代码在转换过程会被修改,小程序的实现逻辑,不具有普适性”。
本片文章将从以下三个方面进行讲解:
- 背景
- 使用转换
- 注意事项
一、背景
之前一段时间,调研过跨平台开发一系列框架,最终筛选出uni-app这款可以适用多端的开源框架。一套代码可以同时生成ios,Android,H5,微信小程序,支付宝小程序,百度小程序等。拓展能力很强,封装了H5+,支持nvue,也支持原生Android,ios开发。可以将原有的移动应用和H5应用改成uni-app应用。基于此,恰好可以将我们之前早期的小程序项目,转化成基于vue.js的uni-app组件
这将带来很多优势:
- 更优的组件渲染更新性能
- 更强的拓展性、和可维护性
- 更快的打包速度、和更便捷的打包方式
- 一套代码多端适用
- ...
二、使用转换
使用官方推荐的HBuilderX, github地址:https://github.com/zhangdaren/miniprogram-to-uniapp。该插件十分强大,通过简单的脚本命令,直接将我本地的小程序原生项目,clone生成了一份vue.js的项目。
诸如: 小程序setData的兼容、小程序生命周期的兼容,小程序各种api的封装,几乎涵盖了所有的小程序原生api。
但是尽管如此,仍是有很多需要注意的点,在实际开发中需要我们注意的。
事先声明,以下需要关注的点,是本人在迁移自己的小程序项目中遇到的切实存在 的问题,如果有更好的解决方案,欢迎讨论。
三、注意事项【踩坑】
- 对于wx.setData的处理:
HBuilderX转换过来之后,会在app.js中加入setData的重写,但这个是通过vue中this.$set和this.$get来实现的,也就是说尽管小程序中的setData没有被转化成this.xxx =...的方式(vue的语法),但仍然可以生效。可是如果setData处于原来小程序组件的生命周期中如attached,这种生命周期会被转换为beforeMount 这时setData就会有报错,解决办法:(1)修改成this.xx = ...的方式 (2) 删除生命周期,因为vue中不支持attache等生命周期函数, 如果非要在组件挂载或者更新的时候将prop或其他变量存储到data中,可以尝试通过watch函数。 - 对于wx.selectComponent
经过反复测试,uni-app不会转化这个api,但是转成vue组件后,这个api将不在生效,需要替换成ref获取节点。
解决方案:这点可以使用ref替换,如果转换前微信组件的生命周期中,可以直接this.$refs.xxx.api()。如果转换前是在微信lifetimes中定义的生命周期,uni-app会转换成vue的生命周期,这时用ref不能直接调用组件的api需要加一层 .$vm, 既this.$refs.xxx.$vm.api() - 由于小程序的语法不强制要求wxml必须最外层是单节点。组件中<page></page>节点下如果存在多节点,转换后会多一层dom节点, 当然如果在wxml中有最外层的container的话就忽略这个问题。
- 原生小程序:
<page> 1 2 3 </page>
- 转换后
<page> <view> 1 2 3 </view> </page>
这在组件中会非常致命,会直接引起之前的样式如滚动条超出,高度的一些100%设置,会失效。导致样式错乱,要格外小心。
- 原生小程序:
- 事件绑定
(1)catchtap="true" 在小程序支持这种写法,可以理解为绑定一个空函数,转换之后变成@tap.stop="true" 这个在uni-app中会报错 需要改成 @tap.stop="emptyFunc".
(2)很多catch方法会被自动转换,如catchtap ----> @tap.stop 但是有些方法如catchtouchmove,这个方法不会转为@touchmove.stop,需要手动修改 。 - 转换过程部分函数名会被修改,如果小程序中定义了xxx,以""开头得函数名都会被修改名字,在调用的时候就找不到对应的函数了,以及vue内置得hide、show等方法名字会被修改成hideFun:
- data中引用类型的状态,以“_”开头的状态均无法通过this访问,都是undefined,需要手动更改状态变量的名称。
- 通过ref获取子组件的data,需要去掉data,如this.$ref.modal.data.name 需要换成 this.$ref.modal.name
- 有时我们想要父组件访问子组件的属性和方法,会将子组件的this作用域回调给父组件,这种方式在转化为uni-app后不会直接报错,但是无法获取到,进行下一步操作会报错。解决方案:在父组件直接使用ref。
- 一些写死的属性数据转换过来可能会变成 字符串,如showTitle="{{true}}"转换之后变成 showTitle="true" 这个时候如果子组件还当作boolean类型处理,就会报错。
- 奇怪的input:如果需要动态绑定input的value,并针对用户输入做一些必要的过滤,如不允许输入数字。 需要在用户输入的时候更改对应的value值,同时在input事件中要把经过js处理的value值return,这样才可以成功过滤input的value,否则不管如果用正则拦截,input输入框上都会输入啥显示啥。
- . vue组件中data中的数据this.xxx去获取,小程序转过来极少的时候是this.data.xxx需要手动更改一下,这个问题具体什么时候出现还不太明确,但是需要注意一下。
四、小程序组件
pages.json文件,这块和小程序不同,不需要再页面级json中配置
{
"pages": [
{
"path": "slide-view/slide-view",
"style": {
"navigationBarTitleText": "slide-view",
"usingComponents": {
"slide-view": "/wxcomponents/miniprogram-slide-view/index"
}
}
}
]
}
.vue:
<slide-view></slide-view>可直接使用
需要注意:
• 小程序组件需要放在项目特殊文件夹 wxcomponents(或 mycomponents、swancomponents)。HBuilderX 建立的工程 wxcomponents 文件夹在 项目根目录下。vue-cli 建立的工程 wxcomponents 文件夹在 src 目录下。可以在 vue.config.js 中自定义其他目录
• 小程序组件的性能,不如vue组件。使用小程序组件,需要自己手动setData,很难自动管理差量数据更新。而使用vue组件会自动diff更新差量数据。所以如无明显必要,建议使用vue组件而不是小程序组件。比如某些小程序ui组件,完全可以用更高性能的uni ui替代。
结论
ok,以上就是本人在项目转换迁移过程中遇到的问题。总体感受还是不错的,该说不说的,HBuilderX还是帮我们做了更大部分的事情,但仍需要我们针对各自的业务需求,做一些写法上的调整,做一切事物之前,需要头脑中有一个概念,“小程序中的代码在转换过程会被修改,小程序的实现逻辑,不具有普适性”。
收起阅读 »
uni-app UDP发送与接收
var DatagramSocket = plus.android.importClass("java.net.DatagramSocket");
var DatagramPacket = plus.android.importClass("java.net.DatagramPacket");
var InetAddress = plus.android.importClass("java.net.InetAddress");
var String = plus.android.importClass("java.lang.String");
var udp = new DatagramSocket();
//UDP发送
var sendBuffer = {
"test": 123
}
var SendBuff = JSON.stringify(sendBuffer)
var data = new String(SendBuff).getBytes(); //发送中文需要指定编码
console.log((new String(data)).toString())
var packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.10.100.254"), 9091);
udp.send(packet);
//UDP接收
let charArr = new Array(100).fill(0) //100为接收数据长度,按需修改
var receivePacket = new DatagramPacket(charArr, charArr.length);
udp.setSoTimeout(5000); //新增,一定要设置超时,否则在没有收到返回数据的时候容易卡死
udp.receive(receivePacket);
var receiveData = (new String(receivePacket.getData())).toString().substring(0,receivePacket.getLength()); //receivePacket.getLength()可获取到接收数据长度,根据数据长度剪切出接收到的真实数据
console.log(receiveData)
udp.close();
var DatagramSocket = plus.android.importClass("java.net.DatagramSocket");
var DatagramPacket = plus.android.importClass("java.net.DatagramPacket");
var InetAddress = plus.android.importClass("java.net.InetAddress");
var String = plus.android.importClass("java.lang.String");
var udp = new DatagramSocket();
//UDP发送
var sendBuffer = {
"test": 123
}
var SendBuff = JSON.stringify(sendBuffer)
var data = new String(SendBuff).getBytes(); //发送中文需要指定编码
console.log((new String(data)).toString())
var packet = new DatagramPacket(data, data.length, InetAddress.getByName("10.10.100.254"), 9091);
udp.send(packet);
//UDP接收
let charArr = new Array(100).fill(0) //100为接收数据长度,按需修改
var receivePacket = new DatagramPacket(charArr, charArr.length);
udp.setSoTimeout(5000); //新增,一定要设置超时,否则在没有收到返回数据的时候容易卡死
udp.receive(receivePacket);
var receiveData = (new String(receivePacket.getData())).toString().substring(0,receivePacket.getLength()); //receivePacket.getLength()可获取到接收数据长度,根据数据长度剪切出接收到的真实数据
console.log(receiveData)
udp.close();
收起阅读 »