app base64 转本地文件
//将base64编码转换成录音文件
dataURL2Audio (base64Str, callback) {
var myArray=new Array();
var myArray = base64Str.split(";base64,");
// console.log(myArray[1]+"base64Str");
base64Str=myArray[1];
// var base64Str = base64Str.replace('data:audio/amr;base64,', '');
var audioName = (new Date()).valueOf() + '.jpg'; // 替换为要转换的文件名即可
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {
fs.root.getFile(audioName, {
create: true
}, function(entry) {
// 获得平台绝对路径
var fullPath = entry.fullPath;
if(false) {
// 读取音频
var Base64 = plus.android.importClass("android.util.Base64");
var FileOutputStream = plus.android.importClass("java.io.FileOutputStream");
try {
var out = new FileOutputStream(fullPath);
var bytes = Base64.decode(base64Str, Base64.DEFAULT);
console.log(bytes+"-------")
out.write(bytes);
out.close();
// 回调
callback && callback(entry);
} catch(e) {
console.log(e.message);
}
} else if(true) {
var NSData = plus.ios.importClass('NSData');
var nsData = new NSData();
nsData = nsData.initWithBase64EncodedStringoptions(base64Str,0);
nsData.plusCallMethod({writeToFile:fullPath,atomically:true});
plus.ios.deleteObject(nsData);
// 回调
callback && callback(entry);
}
})
})
},
aaa () {
// let base64Str = ''
let base64Str = ''
this.dataURL2Audio(base64Str, (entry) => {
var content = entry.toURL();
console.log(content) // 直接使用
})
}, //将base64编码转换成录音文件
dataURL2Audio (base64Str, callback) {
var myArray=new Array();
var myArray = base64Str.split(";base64,");
// console.log(myArray[1]+"base64Str");
base64Str=myArray[1];
// var base64Str = base64Str.replace('data:audio/amr;base64,', '');
var audioName = (new Date()).valueOf() + '.jpg'; // 替换为要转换的文件名即可
plus.io.requestFileSystem(plus.io.PRIVATE_DOC, function(fs) {
fs.root.getFile(audioName, {
create: true
}, function(entry) {
// 获得平台绝对路径
var fullPath = entry.fullPath;
if(false) {
// 读取音频
var Base64 = plus.android.importClass("android.util.Base64");
var FileOutputStream = plus.android.importClass("java.io.FileOutputStream");
try {
var out = new FileOutputStream(fullPath);
var bytes = Base64.decode(base64Str, Base64.DEFAULT);
console.log(bytes+"-------")
out.write(bytes);
out.close();
// 回调
callback && callback(entry);
} catch(e) {
console.log(e.message);
}
} else if(true) {
var NSData = plus.ios.importClass('NSData');
var nsData = new NSData();
nsData = nsData.initWithBase64EncodedStringoptions(base64Str,0);
nsData.plusCallMethod({writeToFile:fullPath,atomically:true});
plus.ios.deleteObject(nsData);
// 回调
callback && callback(entry);
}
})
})
},
aaa () {
// let base64Str = ''
let base64Str = ''
this.dataURL2Audio(base64Str, (entry) => {
var content = entry.toURL();
console.log(content) // 直接使用
})
}, 收起阅读 »
uniapp 集成方法,请求类,分页请求,详情请求,数据渲染,验签请求等方式,让你的开发更简单
文件存储于根目录下common里,当然大家也可以按照自己的想法存储在不同的位置
本集成方法,共设置三个文件依赖md5加密插件
请求接口API模块【api.js】单文件管理接口后期维护成本降低,如更换同一接口地址不用每个文件挨个找了
export const api=($api)=>{
const api={
index:'接口具体参数'
}
return api[$api];
}
集成方法模块【func.js】
此处可以根据个人需求增加所需的方法,让方法可以在多处使用
/* 输出json */
function toJson(option) {
if (typeof(option) == 'object') {
return option;
} else if (typeof(option) == 'string') {
try {
return JSON.parse(option);
} catch (e) {
return option;
}
} else {
return option;
}
}
/** 获取列表数据,支持分页加载*/
function list(othis, param, loading = {
is_loading: false,
loading_title: ''
}, k = 'list', p = 'page', i = 'is_list') {
if (typeof(loading) == 'object' && loading.is_loading == true) {
uni.showLoading({
title: loading.loading_title
})
}
othis.request(param.url, param.data).then(res => {
uni.hideLoading();
othis['is_loading'] = 1;
if (res.code == 1) {
var data = res.data.data;
var page = othis[p];
if (data.length < 1) othis[i] = 0;
var list = page < 2 ? [] : othis[k];
list = list.concat(data);
console.log(list);
othis[k] = list;
uni.stopPullDownRefresh();
} else {
uni.showModal({
title: res.info,
icon: 'none'
})
}
})
}
/**获取详细数据*/
function details(othis, param, k = null, loading = {
is_loading: false,
loading_title: ''
}) {
if (typeof(loading) == 'object' && loading.is_loading == true) {
uni.showLoading({
title: loading.loading_title
})
}
othis.request(param.url, param.data).then(res => {
uni.hideLoading();
othis['is_loading'] = 1;
if (res.code == 1) {
var data = res.data;
if (k == null) {
for (var i in data) {
othis[i] = data[i];
}
} else {
othis[k] = data;
}
} else {
uni.showModal({
title: res.info,
icon: 'none'
})
}
})
}
/**操作请求*/
function action(othis, param, type, confirm, loading = {
is_loading: true,
loading_title: ''
}) {
return new Promise((resolve, reject) => {
if (typeof(confirm) == 'object') {
uni.showModal({
title: confirm.title || '提醒',
content: confirm.content || '确定要操作吗?',
cancelColor: confirm.cancelColor || '#000',
cancelText: confirm.cancelText || '取消',
confirmColor: confirm.confirmColor || '#000',
confirmText: confirm.confirmText || '确认',
success: function(r) {
if (r.confirm) {
do_action(othis, param, type, loading, resolve);
}
}
})
} else {
do_action(othis, param, type, loading, resolve);
}
})
}
function do_action(othis, param, type, loading = {
is_loading: 1,
loading_title: ''
}, resolve) {
if (typeof(loading) == 'object' && loading.is_loading == true) {
uni.showLoading({
title: loading.loading_title
})
}
othis.request(param.url, param.data).then(res => {
uni.hideLoading();
othis['is_loading'] = 1;
if (res.code == 1) {
if (type == undefined || type == '') type = 'back'
var action = ['back', 'remind'];
if (typeof(type) != 'string' || action.indexOf(type) < 0) {
if (typeof(type) != 'object') type = {
type: type
};
var result = Object.assign(type, param, res)
resolve(result);
} else {
if (type == 'back') {
uni.showToast({
title: res.info,
icon: 'none'
})
setTimeout(function() {
uni.navigateBack();
}, 1500)
}
if (type == 'remind') {
uni.showToast({
title: res.info,
icon: 'none'
})
}
}
} else {
uni.showToast({
title: res.info,
icon: 'none'
})
}
})
}
/* 登录 */
function login(othis, link = 'login') {
// #ifdef H5
var pages = getCurrentPages();
var curr_page = pages[pages.length - 1].route;
uni.navigateTo({
url: link
})
uni.setStorageSync('history_page', pages);
// #endif
// #ifdef APP-PLUS
// #endif
// #ifdef MP-WEIXIN
return new Promise((resolve, reject) => {
if(uni.getStorageSync('access_token').length>10){
resolve({code:1,is_login:1})
uni.hideLoading()
return false;
}
var code = uni.getStorageSync('login_code');
uni.login({
success:function(res){
if(code.length<10) code=res.code;
action(othis, {
url: othis.api('wx_' + link),
data: {
code: code
}
}, 'then').then(res => {
uni.showToast({
title: res.info,
icon: 'none'
})
resolve(res);
if (res.code == 1) {
uni.setStorageSync('access_token', res.data.user_token);
}
})
}
})
})
// #endif
}
/* h5 页面登录成功跳转 */
function h5_login(link) {
link = link == undefined ? uni.getStorageSync('history_page') : link;
uni.navigateTo({
url: link
})
}
module.exports = {
toJson: toJson,
fetch: fetch,
list: list,
details: details,
action: action,
login: login
}
请求类库【支持后台验签,让您的数据更安全】
// #ifdef H5
var server = "/";
// #endif
// #ifndef H5
var server = "请求服务器主域";
// #endif
//服务器通过此两个参数验证接口的正确性
const session_id = '服务器加密session_id';
const code = '服务器加密code';
import md5 from '@/js_sdk/js-md5/build/md5.min.js';
import func from '@/common/func.js';
import {api} from '@/common/api.js';
export const _md5=md5;
export const _func=func;
export const _api=api
export const request = (url, data, method) => {
return new Promise((resolve, reject) => {
//生成验签参数
let sign = get_sign(data, code, session_id);
var access_token = uni.getStorageSync('access_token');
var header = {
'content-type': 'application/x-www-form-urlencoded',
sign: sign,
accesstoken: access_token
};
var form_data = data || {};
console.log(form_data);
form_data['at'] = (new Date()).getTime();
uni.request({
url: server + url,
method: method || 'POST',
data: form_data,
header: header,
success: (res) => {
resolve(func.toJson(res.data));
},
fail: (err) => {
console.log(err);
uni.showToast({
title: '嘤嘤嘤!!!网络出错了哦',
icon: 'none'
})
reject(err)
}
})
})
}
export const uploadFile = (url, file, data) => {
return new Promise((resolve, reject) => {
let sign = get_sign(data, code, session_id);
var access_token = uni.getStorageSync('access_token');
var header = {
'Content-Type': 'multipart/form-data',
sign: sign,
accesstoken: access_token
};
uni.uploadFile({
url: server + url,
method: "POST",
header: header,
filePath: file['value'],
name: file['field'],
success: (res) => {
resolve(func.toJson(res.data));
},
fail: (err) => {
uni.showToast({
title: '嘤嘤嘤!!!网络出错了哦',
icon: 'none'
})
reject(err)
}
})
})
}
function get_sign(data, code, session_id) {
var str = '';
for (var a in data) {
str += a + '=' + data[a] + '&';
}
str +=code;
var sign = md5(str);
return session_id + '&' + sign;
}
main.js 设置以下内容
import { request,uploadFile,_md5,_func,_api } from './common/request.js';
Vue.prototype.request= request;
Vue.prototype.uploadFile=uploadFile;
Vue.prototype.md5=_md5;
Vue.prototype.func=_func;
Vue.prototype.api=_api;
这样即可在其他页面调用
请求服务器分页数据仅需要简单代码即可完成
export default {
data() {
return {
page:1,
list:[],
is_list:1
}
},
onLoad() {
this.func.list(this,this.api('index'),{page:this.page})
},
methods: {
}
}
其他方法也是如此请求带弹窗的请求如下
this.func.action(this,
{url:this.api('test'),
data:{abc:'abc'}},'then',{title:'确认提交',content:'提交楼'}).then(res=>{
console.log(res);
})
请求详细数据如下
//接收参数abc直接渲染即可
this.func.details(this,{id:1},'abc');
登录代码,方便开发,也方便直接验证登录操作,如已经登录会直接执行then内方法
this.func.login(this).then(res=>{
console.log(res);
}) 文件存储于根目录下common里,当然大家也可以按照自己的想法存储在不同的位置
本集成方法,共设置三个文件依赖md5加密插件
请求接口API模块【api.js】单文件管理接口后期维护成本降低,如更换同一接口地址不用每个文件挨个找了
export const api=($api)=>{
const api={
index:'接口具体参数'
}
return api[$api];
}
集成方法模块【func.js】
此处可以根据个人需求增加所需的方法,让方法可以在多处使用
/* 输出json */
function toJson(option) {
if (typeof(option) == 'object') {
return option;
} else if (typeof(option) == 'string') {
try {
return JSON.parse(option);
} catch (e) {
return option;
}
} else {
return option;
}
}
/** 获取列表数据,支持分页加载*/
function list(othis, param, loading = {
is_loading: false,
loading_title: ''
}, k = 'list', p = 'page', i = 'is_list') {
if (typeof(loading) == 'object' && loading.is_loading == true) {
uni.showLoading({
title: loading.loading_title
})
}
othis.request(param.url, param.data).then(res => {
uni.hideLoading();
othis['is_loading'] = 1;
if (res.code == 1) {
var data = res.data.data;
var page = othis[p];
if (data.length < 1) othis[i] = 0;
var list = page < 2 ? [] : othis[k];
list = list.concat(data);
console.log(list);
othis[k] = list;
uni.stopPullDownRefresh();
} else {
uni.showModal({
title: res.info,
icon: 'none'
})
}
})
}
/**获取详细数据*/
function details(othis, param, k = null, loading = {
is_loading: false,
loading_title: ''
}) {
if (typeof(loading) == 'object' && loading.is_loading == true) {
uni.showLoading({
title: loading.loading_title
})
}
othis.request(param.url, param.data).then(res => {
uni.hideLoading();
othis['is_loading'] = 1;
if (res.code == 1) {
var data = res.data;
if (k == null) {
for (var i in data) {
othis[i] = data[i];
}
} else {
othis[k] = data;
}
} else {
uni.showModal({
title: res.info,
icon: 'none'
})
}
})
}
/**操作请求*/
function action(othis, param, type, confirm, loading = {
is_loading: true,
loading_title: ''
}) {
return new Promise((resolve, reject) => {
if (typeof(confirm) == 'object') {
uni.showModal({
title: confirm.title || '提醒',
content: confirm.content || '确定要操作吗?',
cancelColor: confirm.cancelColor || '#000',
cancelText: confirm.cancelText || '取消',
confirmColor: confirm.confirmColor || '#000',
confirmText: confirm.confirmText || '确认',
success: function(r) {
if (r.confirm) {
do_action(othis, param, type, loading, resolve);
}
}
})
} else {
do_action(othis, param, type, loading, resolve);
}
})
}
function do_action(othis, param, type, loading = {
is_loading: 1,
loading_title: ''
}, resolve) {
if (typeof(loading) == 'object' && loading.is_loading == true) {
uni.showLoading({
title: loading.loading_title
})
}
othis.request(param.url, param.data).then(res => {
uni.hideLoading();
othis['is_loading'] = 1;
if (res.code == 1) {
if (type == undefined || type == '') type = 'back'
var action = ['back', 'remind'];
if (typeof(type) != 'string' || action.indexOf(type) < 0) {
if (typeof(type) != 'object') type = {
type: type
};
var result = Object.assign(type, param, res)
resolve(result);
} else {
if (type == 'back') {
uni.showToast({
title: res.info,
icon: 'none'
})
setTimeout(function() {
uni.navigateBack();
}, 1500)
}
if (type == 'remind') {
uni.showToast({
title: res.info,
icon: 'none'
})
}
}
} else {
uni.showToast({
title: res.info,
icon: 'none'
})
}
})
}
/* 登录 */
function login(othis, link = 'login') {
// #ifdef H5
var pages = getCurrentPages();
var curr_page = pages[pages.length - 1].route;
uni.navigateTo({
url: link
})
uni.setStorageSync('history_page', pages);
// #endif
// #ifdef APP-PLUS
// #endif
// #ifdef MP-WEIXIN
return new Promise((resolve, reject) => {
if(uni.getStorageSync('access_token').length>10){
resolve({code:1,is_login:1})
uni.hideLoading()
return false;
}
var code = uni.getStorageSync('login_code');
uni.login({
success:function(res){
if(code.length<10) code=res.code;
action(othis, {
url: othis.api('wx_' + link),
data: {
code: code
}
}, 'then').then(res => {
uni.showToast({
title: res.info,
icon: 'none'
})
resolve(res);
if (res.code == 1) {
uni.setStorageSync('access_token', res.data.user_token);
}
})
}
})
})
// #endif
}
/* h5 页面登录成功跳转 */
function h5_login(link) {
link = link == undefined ? uni.getStorageSync('history_page') : link;
uni.navigateTo({
url: link
})
}
module.exports = {
toJson: toJson,
fetch: fetch,
list: list,
details: details,
action: action,
login: login
}
请求类库【支持后台验签,让您的数据更安全】
// #ifdef H5
var server = "/";
// #endif
// #ifndef H5
var server = "请求服务器主域";
// #endif
//服务器通过此两个参数验证接口的正确性
const session_id = '服务器加密session_id';
const code = '服务器加密code';
import md5 from '@/js_sdk/js-md5/build/md5.min.js';
import func from '@/common/func.js';
import {api} from '@/common/api.js';
export const _md5=md5;
export const _func=func;
export const _api=api
export const request = (url, data, method) => {
return new Promise((resolve, reject) => {
//生成验签参数
let sign = get_sign(data, code, session_id);
var access_token = uni.getStorageSync('access_token');
var header = {
'content-type': 'application/x-www-form-urlencoded',
sign: sign,
accesstoken: access_token
};
var form_data = data || {};
console.log(form_data);
form_data['at'] = (new Date()).getTime();
uni.request({
url: server + url,
method: method || 'POST',
data: form_data,
header: header,
success: (res) => {
resolve(func.toJson(res.data));
},
fail: (err) => {
console.log(err);
uni.showToast({
title: '嘤嘤嘤!!!网络出错了哦',
icon: 'none'
})
reject(err)
}
})
})
}
export const uploadFile = (url, file, data) => {
return new Promise((resolve, reject) => {
let sign = get_sign(data, code, session_id);
var access_token = uni.getStorageSync('access_token');
var header = {
'Content-Type': 'multipart/form-data',
sign: sign,
accesstoken: access_token
};
uni.uploadFile({
url: server + url,
method: "POST",
header: header,
filePath: file['value'],
name: file['field'],
success: (res) => {
resolve(func.toJson(res.data));
},
fail: (err) => {
uni.showToast({
title: '嘤嘤嘤!!!网络出错了哦',
icon: 'none'
})
reject(err)
}
})
})
}
function get_sign(data, code, session_id) {
var str = '';
for (var a in data) {
str += a + '=' + data[a] + '&';
}
str +=code;
var sign = md5(str);
return session_id + '&' + sign;
}
main.js 设置以下内容
import { request,uploadFile,_md5,_func,_api } from './common/request.js';
Vue.prototype.request= request;
Vue.prototype.uploadFile=uploadFile;
Vue.prototype.md5=_md5;
Vue.prototype.func=_func;
Vue.prototype.api=_api;
这样即可在其他页面调用
请求服务器分页数据仅需要简单代码即可完成
export default {
data() {
return {
page:1,
list:[],
is_list:1
}
},
onLoad() {
this.func.list(this,this.api('index'),{page:this.page})
},
methods: {
}
}
其他方法也是如此请求带弹窗的请求如下
this.func.action(this,
{url:this.api('test'),
data:{abc:'abc'}},'then',{title:'确认提交',content:'提交楼'}).then(res=>{
console.log(res);
})
请求详细数据如下
//接收参数abc直接渲染即可
this.func.details(this,{id:1},'abc');
登录代码,方便开发,也方便直接验证登录操作,如已经登录会直接执行then内方法
this.func.login(this).then(res=>{
console.log(res);
}) 收起阅读 »
ios打包后,<image>标签读取不到本地上传的临时图片地址,报404 Not Found
<image :src="imageUrl" @error="loadImgError" @click="choseImage" />
vue-cli 命令创建的项目
@dcloudio/uvm版本 --- 3.8.11 alpha
根据uni.chooseImage上传图片,拿到临时路径放到image标签展示,ios打包后在某些操作下,报如下错误,见附件, @error事件捕获(本地基座调试无发复现)
复现1:一开始给与全部权限,可上传成功,杀掉进程后,重新进入APP上传,image报404
复现2:一开始基于部分图片权限,可上传成功,后续从上传提示中进入手机设置给予全部权限,image报404
解决方法:把@dcloudio/uvm降级到3.8.7
<image :src="imageUrl" @error="loadImgError" @click="choseImage" />
vue-cli 命令创建的项目
@dcloudio/uvm版本 --- 3.8.11 alpha
根据uni.chooseImage上传图片,拿到临时路径放到image标签展示,ios打包后在某些操作下,报如下错误,见附件, @error事件捕获(本地基座调试无发复现)
复现1:一开始给与全部权限,可上传成功,杀掉进程后,重新进入APP上传,image报404
复现2:一开始基于部分图片权限,可上传成功,后续从上传提示中进入手机设置给予全部权限,image报404
解决方法:把@dcloudio/uvm降级到3.8.7
收起阅读 »6年全栈开发_全职接单_寻求合作机会
个人全职全栈开发,拥有6年丰富开发经验。目前时间充裕,怀着诚意寻求合作机会。
作为个人全职开发者,我熟练掌握vue、uniapp、thinkphp、fastadmin等框架。
另外:还比较擅长原型及UI设计,技能全面覆盖
如果您有开发各端小程序、APP、网页登需求,欢迎随时联系我细聊:V:yzhua006。
个人全职全栈开发,拥有6年丰富开发经验。目前时间充裕,怀着诚意寻求合作机会。
作为个人全职开发者,我熟练掌握vue、uniapp、thinkphp、fastadmin等框架。
另外:还比较擅长原型及UI设计,技能全面覆盖
如果您有开发各端小程序、APP、网页登需求,欢迎随时联系我细聊:V:yzhua006。
收起阅读 »聚合查询,多表统计数据的demo
个人也是小白,想实现多表统计功能,一直看不懂官方文档,今天找了大佬写了一个demo,有需要的可以了解一下。
我想实现的效果
user表
[
{
"_id": "64c22b3e189f866d65c3d0eb",
"name": "张三",
"sex": 1
},
{
"_id": "64c22b5a337a9f4db7882114",
"name": "王五",
"sex": 1
},
{
"_id": "64c22b5055b337825753d977",
"name": "李娇",
"sex": 0
},
{
"_id": "64c22b6bbd0220bf8c7051c5",
"name": "六花",
"sex": 0
}
]
sp表
[
{
"_id": "64c22cbe99c6246543bbfe9d",
"je": 20,
"name_id": "64c22b5055b337825753d977",
"sp": "李娇的商品1"
},
{
"_id": "64c22ce36e5d2d8f64af3f8e",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品4"
},
{
"_id": "64c22cd97ad52ddc6482f40a",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品2"
},
{
"_id": "64c22c766e5d2d8f64af2804",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品2"
},
{
"_id": "64c22cf7f08210d515b35a86",
"je": 20,
"name_id": "64c22b6bbd0220bf8c7051c5",
"sp": "六花的商品1"
},
{
"_id": "64c22cd421821b2af5b6b48a",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品1"
},
{
"_id": "64c22cdf337a9f4db78875c8",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品3"
},
{
"_id": "64c22c71e0ec19bea1b13e15",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品1"
},
{
"_id": "64c22cc27ad52ddc6482eecb",
"je": 20,
"name_id": "64c22b5055b337825753d977",
"sp": "李娇的商品2"
},
{
"_id": "64c22c7aa09a9bd68ba01823",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品3"
}
]
wz表
[
{
"_id": "64c22f43337a9f4db788fdd8",
"name_id": "64c22b3e189f866d65c3d0eb",
"wzdz": 12,
"wzname": "六花的文章2"
},
{
"_id": "64c22f5ffe975fba5a5e51ef",
"name_id": "64c22b5055b337825753d977",
"wzdz": 34,
"wzname": "李娇的文章2"
},
{
"_id": "64c22f57337a9f4db7890253",
"name_id": "64c22b5055b337825753d977",
"wzdz": 3,
"wzname": "李娇的文章1"
},
{
"_id": "64c22f3ef08210d515b3e212",
"name_id": "64c22b3e189f866d65c3d0eb",
"wzdz": 12,
"wzname": "六花的文章1"
},
{
"_id": "64c22f7b6e5d2d8f64afdba0",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 54,
"wzname": "王五的文章2"
},
{
"_id": "64c22f739755e344ab9c51ce",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 11,
"wzname": "王五的文章1"
},
{
"_id": "64c235cd466d416d30844f4e",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 13,
"wzname": "王五的文章3"
}
]
最终结果
张三 商品3 点赞24
李 商品2 点赞37
王五 商品4 点赞65
六花 商品1 点赞0
聚合查询代码
const db = uniCloud.database()
const dbCmd = db.command
const $ = dbCmd.aggregate
exports.main = async (event, context) => {
const {data}=await db.collection(`user`).aggregate()
.lookup({
from:`sp`,
let:{
user_id:`$_id`,
},
pipeline:$.pipeline()
.match(
dbCmd.expr(
$.eq(['$name_id','$$user_id'])
)
)
.group({
_id:'$name_id',
count: $.sum(1),
})
.done(),
as: 'sp_list',
})
.lookup({
from:`wz`,
let:{
user_id:`$_id`,
},
pipeline:$.pipeline()
.match(
dbCmd.expr(
$.eq(['$name_id','$$user_id'])
)
)
.group({
_id:'$name_id',
wzdz:$.sum('$wzdz'),
})
.done(),
as: 'wz_list',
})
.end();
return {
data:data
}
}; 个人也是小白,想实现多表统计功能,一直看不懂官方文档,今天找了大佬写了一个demo,有需要的可以了解一下。
我想实现的效果
user表
[
{
"_id": "64c22b3e189f866d65c3d0eb",
"name": "张三",
"sex": 1
},
{
"_id": "64c22b5a337a9f4db7882114",
"name": "王五",
"sex": 1
},
{
"_id": "64c22b5055b337825753d977",
"name": "李娇",
"sex": 0
},
{
"_id": "64c22b6bbd0220bf8c7051c5",
"name": "六花",
"sex": 0
}
]
sp表
[
{
"_id": "64c22cbe99c6246543bbfe9d",
"je": 20,
"name_id": "64c22b5055b337825753d977",
"sp": "李娇的商品1"
},
{
"_id": "64c22ce36e5d2d8f64af3f8e",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品4"
},
{
"_id": "64c22cd97ad52ddc6482f40a",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品2"
},
{
"_id": "64c22c766e5d2d8f64af2804",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品2"
},
{
"_id": "64c22cf7f08210d515b35a86",
"je": 20,
"name_id": "64c22b6bbd0220bf8c7051c5",
"sp": "六花的商品1"
},
{
"_id": "64c22cd421821b2af5b6b48a",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品1"
},
{
"_id": "64c22cdf337a9f4db78875c8",
"je": 20,
"name_id": "64c22b5a337a9f4db7882114",
"sp": "王五的商品3"
},
{
"_id": "64c22c71e0ec19bea1b13e15",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品1"
},
{
"_id": "64c22cc27ad52ddc6482eecb",
"je": 20,
"name_id": "64c22b5055b337825753d977",
"sp": "李娇的商品2"
},
{
"_id": "64c22c7aa09a9bd68ba01823",
"je": 20,
"name_id": "64c22b3e189f866d65c3d0eb",
"sp": "张三的商品3"
}
]
wz表
[
{
"_id": "64c22f43337a9f4db788fdd8",
"name_id": "64c22b3e189f866d65c3d0eb",
"wzdz": 12,
"wzname": "六花的文章2"
},
{
"_id": "64c22f5ffe975fba5a5e51ef",
"name_id": "64c22b5055b337825753d977",
"wzdz": 34,
"wzname": "李娇的文章2"
},
{
"_id": "64c22f57337a9f4db7890253",
"name_id": "64c22b5055b337825753d977",
"wzdz": 3,
"wzname": "李娇的文章1"
},
{
"_id": "64c22f3ef08210d515b3e212",
"name_id": "64c22b3e189f866d65c3d0eb",
"wzdz": 12,
"wzname": "六花的文章1"
},
{
"_id": "64c22f7b6e5d2d8f64afdba0",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 54,
"wzname": "王五的文章2"
},
{
"_id": "64c22f739755e344ab9c51ce",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 11,
"wzname": "王五的文章1"
},
{
"_id": "64c235cd466d416d30844f4e",
"name_id": "64c22b5a337a9f4db7882114",
"wzdz": 13,
"wzname": "王五的文章3"
}
]
最终结果
张三 商品3 点赞24
李 商品2 点赞37
王五 商品4 点赞65
六花 商品1 点赞0
聚合查询代码
const db = uniCloud.database()
const dbCmd = db.command
const $ = dbCmd.aggregate
exports.main = async (event, context) => {
const {data}=await db.collection(`user`).aggregate()
.lookup({
from:`sp`,
let:{
user_id:`$_id`,
},
pipeline:$.pipeline()
.match(
dbCmd.expr(
$.eq(['$name_id','$$user_id'])
)
)
.group({
_id:'$name_id',
count: $.sum(1),
})
.done(),
as: 'sp_list',
})
.lookup({
from:`wz`,
let:{
user_id:`$_id`,
},
pipeline:$.pipeline()
.match(
dbCmd.expr(
$.eq(['$name_id','$$user_id'])
)
)
.group({
_id:'$name_id',
wzdz:$.sum('$wzdz'),
})
.done(),
as: 'wz_list',
})
.end();
return {
data:data
}
}; 收起阅读 »
uniapp编译小程序 slot出现将异常
自定义组件中使用了slot插槽,page页使用自定义组件时,将page页面的其他标签元素都渲染进自定义组件插槽
自定义组件中使用了slot插槽,page页使用自定义组件时,将page页面的其他标签元素都渲染进自定义组件插槽
关于HBuilder X打包的APP按返回键退出的问题(解决办法)
前一段时间,我也是被这个问题困扰了很多次,在论坛和百度上也找了很多,只是都没有仔细的说明怎么调用,
今天我在这里给大家说明一下怎么调用
首先先创建一个文件,命名为:houtui.js
放在文件的JS目录里,或者随意,记住路径就行,
js文件内容
document.addEventListener('plusready', function() {
var first = null;
var webview = plus.webview.currentWebview();
plus.key.addEventListener('backbutton', function() {
webview.canBack(function(e) {
if (e.canBack) {
webview.back(); //这里不建议修改自己跳转的路径
} else {
//首次按键,提示‘再按一次退出应用’
if (!first) {
first = new Date().getTime(); //获取第一次点击的时间戳
// console.log('再按一次退出应用');//用自定义toast提示最好
// toast('双击返回键退出应用'); //调用自己写的吐丝提示 函数
plus.nativeUI.toast("再按一次退出应用", {
duration: 'short'
}); //通过H5+ API 调用Android 上的toast 提示框
setTimeout(function() {
first = null;
}, 1000);
} else {
if (new Date().getTime() - first < 1000) { //获取第二次点击的时间戳, 两次之差 小于 1000ms 说明1s点击了两次,
plus.runtime.quit(); //退出应用
}
}
}
})
});
});
之后再H5网站的首页,添加
<body>
<script src=/h5/static/js/houtui01.js></script>
</body>
需要注意的是,你5+app 调用的是 域名链接,就得在网站源码里下载
你要是调用的app,要在HBuilder X设置调用好就行
前一段时间,我也是被这个问题困扰了很多次,在论坛和百度上也找了很多,只是都没有仔细的说明怎么调用,
今天我在这里给大家说明一下怎么调用
首先先创建一个文件,命名为:houtui.js
放在文件的JS目录里,或者随意,记住路径就行,
js文件内容
document.addEventListener('plusready', function() {
var first = null;
var webview = plus.webview.currentWebview();
plus.key.addEventListener('backbutton', function() {
webview.canBack(function(e) {
if (e.canBack) {
webview.back(); //这里不建议修改自己跳转的路径
} else {
//首次按键,提示‘再按一次退出应用’
if (!first) {
first = new Date().getTime(); //获取第一次点击的时间戳
// console.log('再按一次退出应用');//用自定义toast提示最好
// toast('双击返回键退出应用'); //调用自己写的吐丝提示 函数
plus.nativeUI.toast("再按一次退出应用", {
duration: 'short'
}); //通过H5+ API 调用Android 上的toast 提示框
setTimeout(function() {
first = null;
}, 1000);
} else {
if (new Date().getTime() - first < 1000) { //获取第二次点击的时间戳, 两次之差 小于 1000ms 说明1s点击了两次,
plus.runtime.quit(); //退出应用
}
}
}
})
});
});
之后再H5网站的首页,添加
<body>
<script src=/h5/static/js/houtui01.js></script>
</body>
需要注意的是,你5+app 调用的是 域名链接,就得在网站源码里下载
你要是调用的app,要在HBuilder X设置调用好就行
收起阅读 »nvue 做首页时候,会出现空白
只要在onLoad(){setTimeout(()=>{
uni.switchTab({
url:'/pages/discover_index/index'
})
},1000);} 重新进一次当前启动页就好了
只要在onLoad(){setTimeout(()=>{
uni.switchTab({
url:'/pages/discover_index/index'
})
},1000);} 重新进一次当前启动页就好了
#creatcavascontext这个是小程序的废弃接口
creatcavascontext这个是小程序的废弃接口
https://uniapp.dcloud.net.cn/api/canvas/createCanvasContext.html#createcanvascontext
https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.createCanvasContext.html


creatcavascontext这个是小程序的废弃接口
https://uniapp.dcloud.net.cn/api/canvas/createCanvasContext.html#createcanvascontext
https://developers.weixin.qq.com/miniprogram/dev/api/canvas/wx.createCanvasContext.html


uniapp实现IM即时通讯仿微信聊天功能
本文介绍如何基于 UNIAPP 使用 即时通讯SDK ZIM SDK 快速实现基本的消息收发功能。
1 uniapp im 即时通讯功能 方案介绍
即时通讯SDK ZIM SDK 提供了如下接入方案:
在此方案中,您需要通过您自己的业务系统实现以下业务逻辑:
- 搭建客户端的用户管理逻辑,并下发用户 ID 用于客户端登录。
- 鉴权 Token,建议由您的业务后台自行实现,保证鉴权数据安全。
uni-app SDK 是一个基于原生 iOS/Android 平台 ZIM SDK 的 uni-app Wrapper。开发者如需使用 uni-app 开发 Web 或小程序平台的应用,请下载对应的 SDK 集成使用:下载 Web SDK 和 下载小程序 SDK。
2 集成 IM 即时通讯SDK 的前提条件
在使用 IM即时通讯 SDK ZIM SDK 前,请确保:
-
已在 ZEGO控制台 创建项目,获取到了接入 IM即时通讯 ZIM SDK 服务所需的 AppID 和 ServerSecret。ZIM 服务权限不是默认开启的,使用前,请先在 ZEGO 控制台 自助开通 ZIM 即时通讯 服务(详情请参考 项目管理 - 即时通讯),若无法开通 ZIM即时通讯 服务,请联系 ZEGO 技术支持开通。
-
已获取登录 即时通讯 SDK 所需的 Token,详情请参考 使用 Token 鉴权。
3 集成 uniapp IM 即时通讯 SDK
3.1 (可选)新建项目
此步骤以如何创建新项目为例,如果是集成到已有项目,可忽略此步。
1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。
2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。
3.单击“创建”,即可创建项目。
3.2 导入ZIM 即时通讯 SDK
以下两种方式可以任选一种导入。
方式一:在 ZEGO即构科技 官网下载 ZIM 即时通讯 SDK
-
请参考 下载 页面,获取最新版本的 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。
-
将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。
方式二:通过 uni-app 插件市场获取 ZIM uni-app SDK
通过 uni-app 插件市场也有两种方式导入,请任选一种:
-
单击 “购买(0元) for 云打包”,选择相应的项目导入。
-
单击 “下载 for 离线打包”,离线导入。
-
下载 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。
-
将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。
3.3 在 uni-app 项目中导入插件
1.单击项目目录的 “manifest.json” 文件后,单击 “App原生插件配置” 中的 “选择本地插件” 或 “选择云端插件”。
2.在弹出的选择框中,选择 “Zego ZIM 即时通讯 SDK” 后,单击“确认”,即添加成功。
3.4 自定义调试基座
3.4.1 制作自定义调试基座
uni-app 官方自定义调试基座使用说明,请参考 什么是自定义调试基座及使用说明 。
1.选择 “运行 > 运行到手机或模拟器 > 制作自定义调试基座” 菜单。
2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。
打包成功后,控制台会收到 uni-app 的相关提示。
3.4.2 切换运行基座为自定义调试基座
自定义调试基座,请选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。
3.5 集成 JS 封装层
3.5.1 导入 JS 封装层
以下两种方式可以任选一种导入。
- 方式一:请参考 下载 页面,获取最新版本的 JS 封装层到本地,并将得到的 “zego-ZIMUniPlugin-JS.zip” 文件解压缩。
下载的 JS 封装层可以拷贝到 HBuilderX 的 “js_sdk” 目录中。(如无该目录,请创建一个)
- 方式二:在插件市场的 Zego ZIM 即时通讯原生插件(JS 封装层) 界面单击右侧的 “使用 HBuilderX 导入插件”。
导入的 JS 封装层将存储在 “js_sdk” 目录中。
本文介绍如何基于 UNIAPP 使用 即时通讯SDK ZIM SDK 快速实现基本的消息收发功能。
1 uniapp im 即时通讯功能 方案介绍
即时通讯SDK ZIM SDK 提供了如下接入方案:
在此方案中,您需要通过您自己的业务系统实现以下业务逻辑:
- 搭建客户端的用户管理逻辑,并下发用户 ID 用于客户端登录。
- 鉴权 Token,建议由您的业务后台自行实现,保证鉴权数据安全。
uni-app SDK 是一个基于原生 iOS/Android 平台 ZIM SDK 的 uni-app Wrapper。开发者如需使用 uni-app 开发 Web 或小程序平台的应用,请下载对应的 SDK 集成使用:下载 Web SDK 和 下载小程序 SDK。
2 集成 IM 即时通讯SDK 的前提条件
在使用 IM即时通讯 SDK ZIM SDK 前,请确保:
-
已在 ZEGO控制台 创建项目,获取到了接入 IM即时通讯 ZIM SDK 服务所需的 AppID 和 ServerSecret。ZIM 服务权限不是默认开启的,使用前,请先在 ZEGO 控制台 自助开通 ZIM 即时通讯 服务(详情请参考 项目管理 - 即时通讯),若无法开通 ZIM即时通讯 服务,请联系 ZEGO 技术支持开通。
-
已获取登录 即时通讯 SDK 所需的 Token,详情请参考 使用 Token 鉴权。
3 集成 uniapp IM 即时通讯 SDK
3.1 (可选)新建项目
此步骤以如何创建新项目为例,如果是集成到已有项目,可忽略此步。
1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。
2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。
3.单击“创建”,即可创建项目。
3.2 导入ZIM 即时通讯 SDK
以下两种方式可以任选一种导入。
方式一:在 ZEGO即构科技 官网下载 ZIM 即时通讯 SDK
-
请参考 下载 页面,获取最新版本的 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。
-
将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。
方式二:通过 uni-app 插件市场获取 ZIM uni-app SDK
通过 uni-app 插件市场也有两种方式导入,请任选一种:
-
单击 “购买(0元) for 云打包”,选择相应的项目导入。
-
单击 “下载 for 离线打包”,离线导入。
-
下载 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。
-
将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。
3.3 在 uni-app 项目中导入插件
1.单击项目目录的 “manifest.json” 文件后,单击 “App原生插件配置” 中的 “选择本地插件” 或 “选择云端插件”。
2.在弹出的选择框中,选择 “Zego ZIM 即时通讯 SDK” 后,单击“确认”,即添加成功。
3.4 自定义调试基座
3.4.1 制作自定义调试基座
uni-app 官方自定义调试基座使用说明,请参考 什么是自定义调试基座及使用说明 。
1.选择 “运行 > 运行到手机或模拟器 > 制作自定义调试基座” 菜单。
2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。
打包成功后,控制台会收到 uni-app 的相关提示。
3.4.2 切换运行基座为自定义调试基座
自定义调试基座,请选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。
3.5 集成 JS 封装层
3.5.1 导入 JS 封装层
以下两种方式可以任选一种导入。
- 方式一:请参考 下载 页面,获取最新版本的 JS 封装层到本地,并将得到的 “zego-ZIMUniPlugin-JS.zip” 文件解压缩。
下载的 JS 封装层可以拷贝到 HBuilderX 的 “js_sdk” 目录中。(如无该目录,请创建一个)
- 方式二:在插件市场的 Zego ZIM 即时通讯原生插件(JS 封装层) 界面单击右侧的 “使用 HBuilderX 导入插件”。
导入的 JS 封装层将存储在 “js_sdk” 目录中。
3.5.2 在项目中引入 JS 封装层
导入后,可以在业务代码中引入 JS 封装层,并调用 ZIM 相关接口,示例如下:
import { ZIM } from 'zego-zim-react-native';
或
const ZIM = require('zego-zim-react-native').ZIM;
4 实现仿微信的基本收发消息聊天功能
以下流程中,我们以客户端 A 和 B 的消息交互为例,实现 1v1 通信功能:
4.1 uniapp 即时通讯 实现流程
请参考 跑通示例源码 获取源码。
-
导入ZIM 即时通讯 SDK 文件
请参考 3.2 导入 SDK,导入 SDK 文件。 -
创建 ZIM即时通讯 实例
首先我们需要在项目中创建 ZIM 实例,一个实例对应的是一个用户,表示一个用户以客户端的身份登录系统。
例如,客户端 A、B 分别调用 create(@create) 接口,传入在 2 前提条件 中获取到的 AppID,创建了 A、B 的实例:
var appID = xxxx;
// 静态同步方法,创建 zim 实例,传入 AppID
var zim = ZIM.create(appID);
- 监听回调事件
在客户端登录前,开发者可以通过调用 on 接口,自定义 ZIM 中的事件回调,接收到 SDK 异常、消息通知回调等的通知。
// 注册监听“运行时错误信息”的回调
zim.on('error', function (zim, errorInfo) {
console.log('error', errorInfo.code, errorInfo.message);
});
// 注册监听“网络连接状态变更”的回调
zim.on('connectionStateChanged', function (zim, { state, event, extendedData }) {
console.log('connectionStateChanged', state, event, extendedData);
});
// 注册监听“收到单聊消息”的回调
zim.on('receivePeerMessage', function (zim, { messageList, fromConversationID }) {
console.log('receivePeerMessage', messageList, fromConversationID);
});
// 注册监听“令牌即将过期”的回调
zim.on('tokenWillExpire', function (zim, { second }) {
console.log('tokenWillExpire', second);
// 可以在这里调用 renewToken 接口来更新 token
// 新 token 生成可以参考上文
zim.renewToken(token)
.then(function({ token }){
// 更新成功
})
.catch(function(err){
// 更新失败
})
});
4. 登录 ZIM 即时通讯
创建实例后,客户端 A 和 B 需要登录 ZIM,只有登录成功后才可以开始发送、接收消息、更新 Token 等。
客户端需要使用各自的用户信息和 Token 进行登录。调用 login 接口进行登录,传入用户信息 ZIMUserInfo 对象,以及在 2 前提条件 中获取到的 Token 进行鉴权,鉴权通过后才能登录成功。
- “userID”、“userName” 支持开发者自定义规则生成。建议开发者将 “userID” 设置为一个有意义的值,可将其与自己的业务账号系统进行关联。
- 如果 Token 过期,需要在 tokenWillExpire 即将过期回调接口中,调用 renewToken 接口,更新 Token 后才能正常使用 SDK。
// 登录时,需要开发者 按照 "使用 Token 鉴权" 文档生成 token 即可
// userID 和 userName,最大 32 字节的字符串。仅支持数字,英文字符 和 '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '=', '-', '`', ';', '’', ',', '.', '<', '>', '/', '\'。
var userInfo = { userID: 'xxxx', userName: 'xxxx' };
var token = '';
zim.login(userInfo, token)
.then(function () {
// 登录成功
})
.catch(function (err) {
// 登录失败
});
5. 发送消息聊天验证
客户端 A 登录成功后,可以向客户端 B 发送消息。
目前 ZIM 支持的消息类型如下:
以下为发送单聊文本消息为例:客户端 A 可以调用 sendPeerMessage 接口,传入客户端 B 的 userID、消息内容等信息,即可发送一条文本消息到 B 的客户端。
var toUserID = 'xxxx1';
var config = {
priority: 1 // 消息优先级,取值为 低:1 默认,中:2,高:3
};
// 发送单聊 Text 信息
var messageTextObj = { type: 1, message: '文本消息内容' };
zim.sendPeerMessage(messageTextObj, toUserID, config)
.then(function ({ message }) {
// 发送成功
})
.catch(function (err) {
// 发送失败
});
6. 接收消息聊天验证
客户端 B 登录 ZIM 后,将会收到在 on 回调中设置的 receivePeerMessage 监听接口,收到客户端 A 发送过来的消息。
// 注册监听“收到单聊消息”的回调
zim.on('receivePeerMessage', function (zim, { messageList, fromConversationID }) {
console.log('receivePeerMessage', messageList, fromConversationID);
});
7. 退出登录
如果客户端需要退出登录,直接调用 logout 接口即可。
zim.logout();
8. 销毁 ZIM 实例
如果不需要 ZIM 实例,可直接调用 destroy 接口,销毁实例。
zim.destroy();
4.2 API 时序图
通过以上的实现流程描述,API 接口调用时序图如下:
5 uniapp跨平台框架接入ZIM 即时通讯 SDK更多帮助
获取本文即时通讯框架接入IM 即时通讯聊天的开发文档、技术支持,访问即构文档中心IM即时通讯开发文档页 ,可多平台实现聊天社交IM即时通讯功能;
本文介绍如何基于 UNIAPP 使用 即时通讯SDK ZIM SDK 快速实现基本的消息收发功能。
1 uniapp im 即时通讯功能 方案介绍
即时通讯SDK ZIM SDK 提供了如下接入方案:
在此方案中,您需要通过您自己的业务系统实现以下业务逻辑:
- 搭建客户端的用户管理逻辑,并下发用户 ID 用于客户端登录。
- 鉴权 Token,建议由您的业务后台自行实现,保证鉴权数据安全。
uni-app SDK 是一个基于原生 iOS/Android 平台 ZIM SDK 的 uni-app Wrapper。开发者如需使用 uni-app 开发 Web 或小程序平台的应用,请下载对应的 SDK 集成使用:下载 Web SDK 和 下载小程序 SDK。
2 集成 IM 即时通讯SDK 的前提条件
在使用 IM即时通讯 SDK ZIM SDK 前,请确保:
-
已在 ZEGO控制台 创建项目,获取到了接入 IM即时通讯 ZIM SDK 服务所需的 AppID 和 ServerSecret。ZIM 服务权限不是默认开启的,使用前,请先在 ZEGO 控制台 自助开通 ZIM 即时通讯 服务(详情请参考 项目管理 - 即时通讯),若无法开通 ZIM即时通讯 服务,请联系 ZEGO 技术支持开通。
-
已获取登录 即时通讯 SDK 所需的 Token,详情请参考 使用 Token 鉴权。
3 集成 uniapp IM 即时通讯 SDK
3.1 (可选)新建项目
此步骤以如何创建新项目为例,如果是集成到已有项目,可忽略此步。
1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。
2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。
3.单击“创建”,即可创建项目。
3.2 导入ZIM 即时通讯 SDK
以下两种方式可以任选一种导入。
方式一:在 ZEGO即构科技 官网下载 ZIM 即时通讯 SDK
-
请参考 下载 页面,获取最新版本的 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。
-
将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。
方式二:通过 uni-app 插件市场获取 ZIM uni-app SDK
通过 uni-app 插件市场也有两种方式导入,请任选一种:
-
单击 “购买(0元) for 云打包”,选择相应的项目导入。
-
单击 “下载 for 离线打包”,离线导入。
-
下载 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。
-
将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。
3.3 在 uni-app 项目中导入插件
1.单击项目目录的 “manifest.json” 文件后,单击 “App原生插件配置” 中的 “选择本地插件” 或 “选择云端插件”。
2.在弹出的选择框中,选择 “Zego ZIM 即时通讯 SDK” 后,单击“确认”,即添加成功。
3.4 自定义调试基座
3.4.1 制作自定义调试基座
uni-app 官方自定义调试基座使用说明,请参考 什么是自定义调试基座及使用说明 。
1.选择 “运行 > 运行到手机或模拟器 > 制作自定义调试基座” 菜单。
2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。
打包成功后,控制台会收到 uni-app 的相关提示。
3.4.2 切换运行基座为自定义调试基座
自定义调试基座,请选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。
3.5 集成 JS 封装层
3.5.1 导入 JS 封装层
以下两种方式可以任选一种导入。
- 方式一:请参考 下载 页面,获取最新版本的 JS 封装层到本地,并将得到的 “zego-ZIMUniPlugin-JS.zip” 文件解压缩。
下载的 JS 封装层可以拷贝到 HBuilderX 的 “js_sdk” 目录中。(如无该目录,请创建一个)
- 方式二:在插件市场的 Zego ZIM 即时通讯原生插件(JS 封装层) 界面单击右侧的 “使用 HBuilderX 导入插件”。
导入的 JS 封装层将存储在 “js_sdk” 目录中。
本文介绍如何基于 UNIAPP 使用 即时通讯SDK ZIM SDK 快速实现基本的消息收发功能。
1 uniapp im 即时通讯功能 方案介绍
即时通讯SDK ZIM SDK 提供了如下接入方案:
在此方案中,您需要通过您自己的业务系统实现以下业务逻辑:
- 搭建客户端的用户管理逻辑,并下发用户 ID 用于客户端登录。
- 鉴权 Token,建议由您的业务后台自行实现,保证鉴权数据安全。
uni-app SDK 是一个基于原生 iOS/Android 平台 ZIM SDK 的 uni-app Wrapper。开发者如需使用 uni-app 开发 Web 或小程序平台的应用,请下载对应的 SDK 集成使用:下载 Web SDK 和 下载小程序 SDK。
2 集成 IM 即时通讯SDK 的前提条件
在使用 IM即时通讯 SDK ZIM SDK 前,请确保:
-
已在 ZEGO控制台 创建项目,获取到了接入 IM即时通讯 ZIM SDK 服务所需的 AppID 和 ServerSecret。ZIM 服务权限不是默认开启的,使用前,请先在 ZEGO 控制台 自助开通 ZIM 即时通讯 服务(详情请参考 项目管理 - 即时通讯),若无法开通 ZIM即时通讯 服务,请联系 ZEGO 技术支持开通。
-
已获取登录 即时通讯 SDK 所需的 Token,详情请参考 使用 Token 鉴权。
3 集成 uniapp IM 即时通讯 SDK
3.1 (可选)新建项目
此步骤以如何创建新项目为例,如果是集成到已有项目,可忽略此步。
1.启动 HBuilderX,选择“文件 > 新建 > 项目”菜单。
2.在出现的表单中,选择 “uni-app” 平台,并填写项目名称。
3.单击“创建”,即可创建项目。
3.2 导入ZIM 即时通讯 SDK
以下两种方式可以任选一种导入。
方式一:在 ZEGO即构科技 官网下载 ZIM 即时通讯 SDK
-
请参考 下载 页面,获取最新版本的 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。
-
将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。
方式二:通过 uni-app 插件市场获取 ZIM uni-app SDK
通过 uni-app 插件市场也有两种方式导入,请任选一种:
-
单击 “购买(0元) for 云打包”,选择相应的项目导入。
-
单击 “下载 for 离线打包”,离线导入。
-
下载 SDK 到本地,并将得到的 “zego-ZIMUniPlugin.zip” 文件解压缩。
-
将解压缩后的文件夹,直接复制到项目工程根目录下的 “nativeplugins” 文件夹,如果没有该目录,请手动创建。
3.3 在 uni-app 项目中导入插件
1.单击项目目录的 “manifest.json” 文件后,单击 “App原生插件配置” 中的 “选择本地插件” 或 “选择云端插件”。
2.在弹出的选择框中,选择 “Zego ZIM 即时通讯 SDK” 后,单击“确认”,即添加成功。
3.4 自定义调试基座
3.4.1 制作自定义调试基座
uni-app 官方自定义调试基座使用说明,请参考 什么是自定义调试基座及使用说明 。
1.选择 “运行 > 运行到手机或模拟器 > 制作自定义调试基座” 菜单。
2.在弹出的界面中,按照 uni-app 教程,填写相关信息,并单击“打包”进行云打包。
打包成功后,控制台会收到 uni-app 的相关提示。
3.4.2 切换运行基座为自定义调试基座
自定义调试基座,请选择“运行 > 运行到手机或模拟器 > 运行基座选择 > 自定义调试基座”菜单。
3.5 集成 JS 封装层
3.5.1 导入 JS 封装层
以下两种方式可以任选一种导入。
- 方式一:请参考 下载 页面,获取最新版本的 JS 封装层到本地,并将得到的 “zego-ZIMUniPlugin-JS.zip” 文件解压缩。
下载的 JS 封装层可以拷贝到 HBuilderX 的 “js_sdk” 目录中。(如无该目录,请创建一个)
- 方式二:在插件市场的 Zego ZIM 即时通讯原生插件(JS 封装层) 界面单击右侧的 “使用 HBuilderX 导入插件”。
导入的 JS 封装层将存储在 “js_sdk” 目录中。
3.5.2 在项目中引入 JS 封装层
导入后,可以在业务代码中引入 JS 封装层,并调用 ZIM 相关接口,示例如下:
import { ZIM } from 'zego-zim-react-native';
或
const ZIM = require('zego-zim-react-native').ZIM;
4 实现仿微信的基本收发消息聊天功能
以下流程中,我们以客户端 A 和 B 的消息交互为例,实现 1v1 通信功能:
4.1 uniapp 即时通讯 实现流程
请参考 跑通示例源码 获取源码。
-
导入ZIM 即时通讯 SDK 文件
请参考 3.2 导入 SDK,导入 SDK 文件。 -
创建 ZIM即时通讯 实例
首先我们需要在项目中创建 ZIM 实例,一个实例对应的是一个用户,表示一个用户以客户端的身份登录系统。
例如,客户端 A、B 分别调用 create(@create) 接口,传入在 2 前提条件 中获取到的 AppID,创建了 A、B 的实例:
var appID = xxxx;
// 静态同步方法,创建 zim 实例,传入 AppID
var zim = ZIM.create(appID);
- 监听回调事件
在客户端登录前,开发者可以通过调用 on 接口,自定义 ZIM 中的事件回调,接收到 SDK 异常、消息通知回调等的通知。
// 注册监听“运行时错误信息”的回调
zim.on('error', function (zim, errorInfo) {
console.log('error', errorInfo.code, errorInfo.message);
});
// 注册监听“网络连接状态变更”的回调
zim.on('connectionStateChanged', function (zim, { state, event, extendedData }) {
console.log('connectionStateChanged', state, event, extendedData);
});
// 注册监听“收到单聊消息”的回调
zim.on('receivePeerMessage', function (zim, { messageList, fromConversationID }) {
console.log('receivePeerMessage', messageList, fromConversationID);
});
// 注册监听“令牌即将过期”的回调
zim.on('tokenWillExpire', function (zim, { second }) {
console.log('tokenWillExpire', second);
// 可以在这里调用 renewToken 接口来更新 token
// 新 token 生成可以参考上文
zim.renewToken(token)
.then(function({ token }){
// 更新成功
})
.catch(function(err){
// 更新失败
})
});
4. 登录 ZIM 即时通讯
创建实例后,客户端 A 和 B 需要登录 ZIM,只有登录成功后才可以开始发送、接收消息、更新 Token 等。
客户端需要使用各自的用户信息和 Token 进行登录。调用 login 接口进行登录,传入用户信息 ZIMUserInfo 对象,以及在 2 前提条件 中获取到的 Token 进行鉴权,鉴权通过后才能登录成功。
- “userID”、“userName” 支持开发者自定义规则生成。建议开发者将 “userID” 设置为一个有意义的值,可将其与自己的业务账号系统进行关联。
- 如果 Token 过期,需要在 tokenWillExpire 即将过期回调接口中,调用 renewToken 接口,更新 Token 后才能正常使用 SDK。
// 登录时,需要开发者 按照 "使用 Token 鉴权" 文档生成 token 即可
// userID 和 userName,最大 32 字节的字符串。仅支持数字,英文字符 和 '~', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', '_', '+', '=', '-', '`', ';', '’', ',', '.', '<', '>', '/', '\'。
var userInfo = { userID: 'xxxx', userName: 'xxxx' };
var token = '';
zim.login(userInfo, token)
.then(function () {
// 登录成功
})
.catch(function (err) {
// 登录失败
});
5. 发送消息聊天验证
客户端 A 登录成功后,可以向客户端 B 发送消息。
目前 ZIM 支持的消息类型如下:
以下为发送单聊文本消息为例:客户端 A 可以调用 sendPeerMessage 接口,传入客户端 B 的 userID、消息内容等信息,即可发送一条文本消息到 B 的客户端。
var toUserID = 'xxxx1';
var config = {
priority: 1 // 消息优先级,取值为 低:1 默认,中:2,高:3
};
// 发送单聊 Text 信息
var messageTextObj = { type: 1, message: '文本消息内容' };
zim.sendPeerMessage(messageTextObj, toUserID, config)
.then(function ({ message }) {
// 发送成功
})
.catch(function (err) {
// 发送失败
});
6. 接收消息聊天验证
客户端 B 登录 ZIM 后,将会收到在 on 回调中设置的 receivePeerMessage 监听接口,收到客户端 A 发送过来的消息。
// 注册监听“收到单聊消息”的回调
zim.on('receivePeerMessage', function (zim, { messageList, fromConversationID }) {
console.log('receivePeerMessage', messageList, fromConversationID);
});
7. 退出登录
如果客户端需要退出登录,直接调用 logout 接口即可。
zim.logout();
8. 销毁 ZIM 实例
如果不需要 ZIM 实例,可直接调用 destroy 接口,销毁实例。
zim.destroy();
4.2 API 时序图
通过以上的实现流程描述,API 接口调用时序图如下:
5 uniapp跨平台框架接入ZIM 即时通讯 SDK更多帮助
获取本文即时通讯框架接入IM 即时通讯聊天的开发文档、技术支持,访问即构文档中心IM即时通讯开发文档页 ,可多平台实现聊天社交IM即时通讯功能;
收起阅读 »uni-app开发小程序:项目架构以及经验分享
uni-app开发小程序:项目架构以及经验分享
2022年的时候,公司为了快速完成产品并上线,所以选用微信小程序为载体;由于后期还是打算开发App;虽然公司有ios和Android,但是如果能一套代码打包多端,一定程度上可以解决成本。前端技术栈也是vue,在考察选择了uni-app。后来多个小程序项目都采用了uni-app开发,积累了一定的经验以及封装了较多业务组件,这里就分享一下uni-app项目的整体架构、方法封装组件库选择以及注意事项。全文代码都会放到github,先赞后看,月入百万!
创建项目
uni-app提供了两种创建项目的方式:
⚠️需要注意的是,一定要根据项目需求来选择项目的创建方式;如果只是单独的开发
小程序或App,且开发环境单一,可以使用HBuilderX可视化工具创建。如果多端开发,以及同一套代码可能会打包生成多个小程序建议使用vue-cli进行创建,不然后期想搞自动化构建以及按指定条件进行编译比较痛苦。关于按条件编译,文章后面会有详细说明。
使用vue-cli安装和运行:
1.全局安装 vue-cli
npm install -g @vue/cli
2.创建uni-app
vue create -p dcloudio/uni-preset-vue 项目名称
3.进入项目文件夹
cd 项目名称
4.运行项目,如果是已微信小程序为主,可以在package.json中的命令改为:
"scripts": {
"serve": "npm run dev:mp-weixin"
}
然后执行
npm run serve
使用cli创建项目默认不带css预编译,需要手动安装一下,这里已sass为例:
npm i sass --save-dev
npm i sass-loader --save-dev
整体项目架构
通过HBuilderX或者vue-cli创建的项目,目录结构有稍许不同,但基本没什么差异,这里就按vue-cli创建的项目为例,整体架构配置如下:
├──dist 编译后的文件路径
├──package.json 配置项
├──src 核心内容
├──api 项目接口
├──components 全局公共组件
├──config 项目配置文件
├──pages 主包
├──static 全局静态资源
├──store vuex
├──mixins 全局混入
├──utils 公共方法
├──App.vue 应用配置,配置App全局样式以及监听
├──main.js Vue初始化入口文件
├──manifest.json 配置应用名称、appid等打包信息
├──pages.json 配置页面路由、导航条、选项卡等页面类信息
└──uni.scss 全局样式
封装方法
工欲善其事,必先利其器。在开发之前,我们可以把一些全局通用的方法进行封装,以及把uni-app提供的api进行二次封装,方便使用。全局的公共方法我们都会放到/src/utils文件夹下。
封装常用方法
下面这些方法都放在/src/utils/utils.js中,文章末尾会提供github链接方便查看。如果项目较大,建议把方法根据功能定义不同的js文件。
小程序Toast提示
/**
* 提示方法
* @param {String} title 提示文字
* @param {String} icon icon图片
* @param {Number} duration 提示时间
*/
export function toast(title, icon = 'none', duration = 1500) {
if(title) {
uni.showToast({
title,
icon,
duration
})
}
}
缓存操作(设置/获取/删除/清空)
/**
* 缓存操作
* @param {String} val
*/
export function setStorageSync(key, data) {
uni.setStorageSync(key, data)
}
export function getStorageSync(key) {
return uni.getStorageSync(key)
}
export function removeStorageSync(key) {
return uni.removeStorageSync(key)
}
export function clearStorageSync() {
return uni.clearStorageSync()
}
页面跳转
/**
* 页面跳转
* @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url 转跳路径
* @param {String} params 跳转时携带的参数
* @param {String} type 转跳方式
**/
export function useRouter(url, params = {}, type = 'navigateTo') {
try {
if (Object.keys(params).length) url = `${url}?data=${encodeURIComponent(JSON.stringify(params))}`
if (type === 'navigateBack') {
uni[type]({ delta: url })
} else {
uni[type]({ url })
}
} catch (error) {
console.error(error)
}
}
图片预览
/**
* 预览图片
* @param {Array} urls 图片链接
*/
export function previewImage(urls, itemList = ['发送给朋友', '保存图片', '收藏']) {
uni.previewImage({
urls,
longPressActions: {
itemList,
fail: function (error) {
console.error(error,'===previewImage')
}
}
})
}
图片下载
/**
* 保存图片到本地
* @param {String} filePath 图片临时路径
**/
export function saveImage(filePath) {
if (!filePath) return false
uni.saveImageToPhotosAlbum({
filePath,
success: (res) => {
toast('图片保存成功', 'success')
},
fail: (err) => {
if (err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' || err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {
uni.showModal({
title: '提示',
content: '需要您授权保存相册',
showCancel: false,
success: (modalSuccess) => {
uni.openSetting({
success(settingdata) {
if (settingdata.authSetting['scope.writePhotosAlbum']) {
uni.showModal({
title: '提示',
content: '获取权限成功,再次点击图片即可保存',
showCancel: false
})
} else {
uni.showModal({
title: '提示',
content: '获取权限失败,将无法保存到相册哦~',
showCancel: false
})
}
},
fail(failData) {
console.log('failData', failData)
}
})
}
})
}
}
})
}
更多函数就不在文章中展示了,已经放到/src/utils/utils,js里面,具体可以到github查看。
请求封装
为了减少在页面中的请求代码,所以我们要对uni-app提供的请求方式进行二次封装,在/src/utils文件夹下建立request.js,具体代码如下:
import {toast, clearStorageSync, getStorageSync, useRouter} from './utils'
import {BASE_URL} from '@/config/index'
const baseRequest = async (url, method, data, loading = true) =>{
header.token = getStorageSync('token') || ''
return new Promise((reslove, reject) => {
loading && uni.showLoading({title: 'loading'})
uni.request({
url: BASE_URL + url,
method: method || 'GET',
header: header,
timeout: 10000,
data: data || {},
success: (successData) => {
const res = successData.data
uni.hideLoading()
if(successData.statusCode == 200){
// 这里根据自己的业务逻辑去调整
if(res.resultCode == 'PA-G998'){
clearStorageSync()
useRouter('/pages/login/index', 'reLaunch')
}else{
reslove(res.data)
}
}else{
toast('网络连接失败,请稍后重试')
reject(res)
}
},
fail: (msg) => {
uni.hideLoading()
toast('网络连接失败,请稍后重试')
reject(msg)
}
})
})
}
const request = {};
['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
request[method] = (api, data, loading) => baseRequest(api, method, data, loading)
})
export default request
请求封装好以后,我们在/src/api文件夹下按业务模块建立对应的api文件,拿获取用户信息接口举例子:
在/src/api文件夹下建立user.js,然后引入request.js
import request from '@/utils/request'
//个人信息
export const info = data => request.post('/v1/api/info', data)
在页面中直接使用:
import {info} from '@/api/user.js'
export default {
methods: {
async getUserinfo() {
let info = await info()
console.log('用户信息==', info)
}
}
}
自定义tabBar
写uni-app或者小程序基本避不开这个话题了,很多情况下,官方提供的tabBar方案并不能满足产品需求/ui要求,官方也提供了自定义tabBar的方案,但此方案有很多弊端,比如:切换时候会tabBar会有明显的闪动。可以参考之前写的文章小程序自定义TabBar 如何实现“keep-alive”,文章中是原生小程序,但思路在uni-app中同样适用,如果感兴趣,可以评论区提问。
版本切换
很多场景下,需要根据不同的环境去切换不同的请求域名、APPID等字段,这时候就需要通过环境变量来进行区分。下面案例我们就分为三个环境:开发环境(dev)、测试环境(test)、生产环境(prod)。
建立env文件
在项目根目录建立下面三个文件并写入内容(常量名要以VUE开头命名):
.env.dev(开发环境)
VUE_APP_MODE=dev
VUE_APP_ID=wxbb53ae105735a06b
VUE_APP_BASE=https://www.baidu.dev.com
.env.test(测试环境)
VUE_APP_MODE=test
VUE_APP_ID=wxbb53ae105735a06c
VUE_APP_BASE=https://www.baidu.test.com
.env.prod(生产环境)
VUE_APP_MODE=wxbb53ae105735a06d
VUE_APP_ID=prod
VUE_APP_BASE=https://www.baidu.prod.com
修改package.json文件
"scripts": {
"dev:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode dev",
"build:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode prod"
},
然后执行
npm run dev:mp-weixin
在/src/pages/index/index.vue下,打印:
onLoad() {
console.log(process.env.VUE_APP_MODE, '====VUE_APP_BASE')
console.log(process.env.VUE_APP_BASE, '====VUE_APP_BASE')
},
此时输出结果就是
// dev ====VUE_APP_BASE
// https://www.baidu.dev.com ====VUE_APP_BASE
动态修改appid
如果同一套代码,需要打包生成多个小程序,就需要动态修改appid了;文章开头说过appid在/src/manifest.json文件中配置,但json文件又不能直接写变量,这时候就可以参考官方 提出的解决方案:建立vue.config.js文件,具体操作如下。
在根目录下建立vue.config.js文件写入以下内容:
// 读取 manifest.json ,修改后重新写入
const fs = require('fs')
const manifestPath = './src/manifest.json'
let Manifest = fs.readFileSync(manifestPath, { encoding: 'utf-8' })
function replaceManifest(path, value) {
const arr = path.split('.')
const len = arr.length
const lastItem = arr[len - 1]
let i = 0
let ManifestArr = Manifest.split(/\n/)
for (let index = 0; index < ManifestArr.length; index++) {
const item = ManifestArr[index]
if (new RegExp(`"${arr[i]}"`).test(item)) ++i
if (i === len) {
const hasComma = /,/.test(item)
ManifestArr[index] = item.replace(
new RegExp(`"${lastItem}"[\\s\\S]*:[\\s\\S]*`),
`"${lastItem}": ${value}${hasComma ? ',' : ''}`
)
break
}
}
Manifest = ManifestArr.join('\n')
}
// 读取环境变量内容
replaceManifest('mp-weixin.appid', `"${process.env.VUE_APP_ID}"`)
fs.writeFileSync(manifestPath, Manifest, {
flag: 'w'
})
如果是通过HBuilderX可视化工具创建的项目,则无法去自动根据环境去修改appid,只能去手动修改。
组件库
uni-app最受欢迎的可能就是插件市场了,插件市场提供了很多优秀的插件/组件库供我们选择,比较火的就是自家的uni-ui以及uView UI,大部分组件还是比较好用的,如果做中大型项目以及UI要求较高的情况下,还是比较推荐自己搭一套组件库,方便扩展以及维护。
结尾
关于uni-app项目的起步工作就到这里了,后面有机会写一套完整的uni搭建电商小程序项目,记得关注。代码已经提交到github,如果对你有帮助,记得点个star!
uni-app开发小程序:项目架构以及经验分享
2022年的时候,公司为了快速完成产品并上线,所以选用微信小程序为载体;由于后期还是打算开发App;虽然公司有ios和Android,但是如果能一套代码打包多端,一定程度上可以解决成本。前端技术栈也是vue,在考察选择了uni-app。后来多个小程序项目都采用了uni-app开发,积累了一定的经验以及封装了较多业务组件,这里就分享一下uni-app项目的整体架构、方法封装组件库选择以及注意事项。全文代码都会放到github,先赞后看,月入百万!
创建项目
uni-app提供了两种创建项目的方式:
⚠️需要注意的是,一定要根据项目需求来选择项目的创建方式;如果只是单独的开发
小程序或App,且开发环境单一,可以使用HBuilderX可视化工具创建。如果多端开发,以及同一套代码可能会打包生成多个小程序建议使用vue-cli进行创建,不然后期想搞自动化构建以及按指定条件进行编译比较痛苦。关于按条件编译,文章后面会有详细说明。
使用vue-cli安装和运行:
1.全局安装 vue-cli
npm install -g @vue/cli
2.创建uni-app
vue create -p dcloudio/uni-preset-vue 项目名称
3.进入项目文件夹
cd 项目名称
4.运行项目,如果是已微信小程序为主,可以在package.json中的命令改为:
"scripts": {
"serve": "npm run dev:mp-weixin"
}
然后执行
npm run serve
使用cli创建项目默认不带css预编译,需要手动安装一下,这里已sass为例:
npm i sass --save-dev
npm i sass-loader --save-dev
整体项目架构
通过HBuilderX或者vue-cli创建的项目,目录结构有稍许不同,但基本没什么差异,这里就按vue-cli创建的项目为例,整体架构配置如下:
├──dist 编译后的文件路径
├──package.json 配置项
├──src 核心内容
├──api 项目接口
├──components 全局公共组件
├──config 项目配置文件
├──pages 主包
├──static 全局静态资源
├──store vuex
├──mixins 全局混入
├──utils 公共方法
├──App.vue 应用配置,配置App全局样式以及监听
├──main.js Vue初始化入口文件
├──manifest.json 配置应用名称、appid等打包信息
├──pages.json 配置页面路由、导航条、选项卡等页面类信息
└──uni.scss 全局样式
封装方法
工欲善其事,必先利其器。在开发之前,我们可以把一些全局通用的方法进行封装,以及把uni-app提供的api进行二次封装,方便使用。全局的公共方法我们都会放到/src/utils文件夹下。
封装常用方法
下面这些方法都放在/src/utils/utils.js中,文章末尾会提供github链接方便查看。如果项目较大,建议把方法根据功能定义不同的js文件。
小程序Toast提示
/**
* 提示方法
* @param {String} title 提示文字
* @param {String} icon icon图片
* @param {Number} duration 提示时间
*/
export function toast(title, icon = 'none', duration = 1500) {
if(title) {
uni.showToast({
title,
icon,
duration
})
}
}
缓存操作(设置/获取/删除/清空)
/**
* 缓存操作
* @param {String} val
*/
export function setStorageSync(key, data) {
uni.setStorageSync(key, data)
}
export function getStorageSync(key) {
return uni.getStorageSync(key)
}
export function removeStorageSync(key) {
return uni.removeStorageSync(key)
}
export function clearStorageSync() {
return uni.clearStorageSync()
}
页面跳转
/**
* 页面跳转
* @param {'navigateTo' | 'redirectTo' | 'reLaunch' | 'switchTab' | 'navigateBack' | number } url 转跳路径
* @param {String} params 跳转时携带的参数
* @param {String} type 转跳方式
**/
export function useRouter(url, params = {}, type = 'navigateTo') {
try {
if (Object.keys(params).length) url = `${url}?data=${encodeURIComponent(JSON.stringify(params))}`
if (type === 'navigateBack') {
uni[type]({ delta: url })
} else {
uni[type]({ url })
}
} catch (error) {
console.error(error)
}
}
图片预览
/**
* 预览图片
* @param {Array} urls 图片链接
*/
export function previewImage(urls, itemList = ['发送给朋友', '保存图片', '收藏']) {
uni.previewImage({
urls,
longPressActions: {
itemList,
fail: function (error) {
console.error(error,'===previewImage')
}
}
})
}
图片下载
/**
* 保存图片到本地
* @param {String} filePath 图片临时路径
**/
export function saveImage(filePath) {
if (!filePath) return false
uni.saveImageToPhotosAlbum({
filePath,
success: (res) => {
toast('图片保存成功', 'success')
},
fail: (err) => {
if (err.errMsg === 'saveImageToPhotosAlbum:fail:auth denied' || err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {
uni.showModal({
title: '提示',
content: '需要您授权保存相册',
showCancel: false,
success: (modalSuccess) => {
uni.openSetting({
success(settingdata) {
if (settingdata.authSetting['scope.writePhotosAlbum']) {
uni.showModal({
title: '提示',
content: '获取权限成功,再次点击图片即可保存',
showCancel: false
})
} else {
uni.showModal({
title: '提示',
content: '获取权限失败,将无法保存到相册哦~',
showCancel: false
})
}
},
fail(failData) {
console.log('failData', failData)
}
})
}
})
}
}
})
}
更多函数就不在文章中展示了,已经放到/src/utils/utils,js里面,具体可以到github查看。
请求封装
为了减少在页面中的请求代码,所以我们要对uni-app提供的请求方式进行二次封装,在/src/utils文件夹下建立request.js,具体代码如下:
import {toast, clearStorageSync, getStorageSync, useRouter} from './utils'
import {BASE_URL} from '@/config/index'
const baseRequest = async (url, method, data, loading = true) =>{
header.token = getStorageSync('token') || ''
return new Promise((reslove, reject) => {
loading && uni.showLoading({title: 'loading'})
uni.request({
url: BASE_URL + url,
method: method || 'GET',
header: header,
timeout: 10000,
data: data || {},
success: (successData) => {
const res = successData.data
uni.hideLoading()
if(successData.statusCode == 200){
// 这里根据自己的业务逻辑去调整
if(res.resultCode == 'PA-G998'){
clearStorageSync()
useRouter('/pages/login/index', 'reLaunch')
}else{
reslove(res.data)
}
}else{
toast('网络连接失败,请稍后重试')
reject(res)
}
},
fail: (msg) => {
uni.hideLoading()
toast('网络连接失败,请稍后重试')
reject(msg)
}
})
})
}
const request = {};
['options', 'get', 'post', 'put', 'head', 'delete', 'trace', 'connect'].forEach((method) => {
request[method] = (api, data, loading) => baseRequest(api, method, data, loading)
})
export default request
请求封装好以后,我们在/src/api文件夹下按业务模块建立对应的api文件,拿获取用户信息接口举例子:
在/src/api文件夹下建立user.js,然后引入request.js
import request from '@/utils/request'
//个人信息
export const info = data => request.post('/v1/api/info', data)
在页面中直接使用:
import {info} from '@/api/user.js'
export default {
methods: {
async getUserinfo() {
let info = await info()
console.log('用户信息==', info)
}
}
}
自定义tabBar
写uni-app或者小程序基本避不开这个话题了,很多情况下,官方提供的tabBar方案并不能满足产品需求/ui要求,官方也提供了自定义tabBar的方案,但此方案有很多弊端,比如:切换时候会tabBar会有明显的闪动。可以参考之前写的文章小程序自定义TabBar 如何实现“keep-alive”,文章中是原生小程序,但思路在uni-app中同样适用,如果感兴趣,可以评论区提问。
版本切换
很多场景下,需要根据不同的环境去切换不同的请求域名、APPID等字段,这时候就需要通过环境变量来进行区分。下面案例我们就分为三个环境:开发环境(dev)、测试环境(test)、生产环境(prod)。
建立env文件
在项目根目录建立下面三个文件并写入内容(常量名要以VUE开头命名):
.env.dev(开发环境)
VUE_APP_MODE=dev
VUE_APP_ID=wxbb53ae105735a06b
VUE_APP_BASE=https://www.baidu.dev.com
.env.test(测试环境)
VUE_APP_MODE=test
VUE_APP_ID=wxbb53ae105735a06c
VUE_APP_BASE=https://www.baidu.test.com
.env.prod(生产环境)
VUE_APP_MODE=wxbb53ae105735a06d
VUE_APP_ID=prod
VUE_APP_BASE=https://www.baidu.prod.com
修改package.json文件
"scripts": {
"dev:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode dev",
"build:mp-weixin": "cross-env UNI_PLATFORM=mp-weixin vue-cli-service uni-build --watch --mode prod"
},
然后执行
npm run dev:mp-weixin
在/src/pages/index/index.vue下,打印:
onLoad() {
console.log(process.env.VUE_APP_MODE, '====VUE_APP_BASE')
console.log(process.env.VUE_APP_BASE, '====VUE_APP_BASE')
},
此时输出结果就是
// dev ====VUE_APP_BASE
// https://www.baidu.dev.com ====VUE_APP_BASE
动态修改appid
如果同一套代码,需要打包生成多个小程序,就需要动态修改appid了;文章开头说过appid在/src/manifest.json文件中配置,但json文件又不能直接写变量,这时候就可以参考官方 提出的解决方案:建立vue.config.js文件,具体操作如下。
在根目录下建立vue.config.js文件写入以下内容:
// 读取 manifest.json ,修改后重新写入
const fs = require('fs')
const manifestPath = './src/manifest.json'
let Manifest = fs.readFileSync(manifestPath, { encoding: 'utf-8' })
function replaceManifest(path, value) {
const arr = path.split('.')
const len = arr.length
const lastItem = arr[len - 1]
let i = 0
let ManifestArr = Manifest.split(/\n/)
for (let index = 0; index < ManifestArr.length; index++) {
const item = ManifestArr[index]
if (new RegExp(`"${arr[i]}"`).test(item)) ++i
if (i === len) {
const hasComma = /,/.test(item)
ManifestArr[index] = item.replace(
new RegExp(`"${lastItem}"[\\s\\S]*:[\\s\\S]*`),
`"${lastItem}": ${value}${hasComma ? ',' : ''}`
)
break
}
}
Manifest = ManifestArr.join('\n')
}
// 读取环境变量内容
replaceManifest('mp-weixin.appid', `"${process.env.VUE_APP_ID}"`)
fs.writeFileSync(manifestPath, Manifest, {
flag: 'w'
})
如果是通过HBuilderX可视化工具创建的项目,则无法去自动根据环境去修改appid,只能去手动修改。
组件库
uni-app最受欢迎的可能就是插件市场了,插件市场提供了很多优秀的插件/组件库供我们选择,比较火的就是自家的uni-ui以及uView UI,大部分组件还是比较好用的,如果做中大型项目以及UI要求较高的情况下,还是比较推荐自己搭一套组件库,方便扩展以及维护。
结尾
关于uni-app项目的起步工作就到这里了,后面有机会写一套完整的uni搭建电商小程序项目,记得关注。代码已经提交到github,如果对你有帮助,记得点个star!





