删除文件 src
24
src/App.vue
|
|
@ -1,24 +0,0 @@
|
|||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
};
|
||||
},
|
||||
onLaunch() {
|
||||
},
|
||||
onShow: function () {
|
||||
console.log('App Show');
|
||||
},
|
||||
onHide: function () {
|
||||
console.log('App Hide');
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@windicss;
|
||||
</style>
|
||||
<style lang="scss">
|
||||
@import 'uview-ui/index.scss';
|
||||
@import '@/style/index.scss';
|
||||
</style>
|
||||
|
|
@ -1,565 +0,0 @@
|
|||
let log = console.log; // 如果在项目的APP.vue文件中的onlaunch中设置 console.log = ()=> {} 则在此也不会有打印信息
|
||||
// log = ()=>{}; // 打开注释则该插件不会打印任何信息
|
||||
let _app = {
|
||||
lineFeedTags: ['<br />', '<br/>', '\r\n', '\n\t', '\r', '\n'], //换行符识别列表
|
||||
tagetLineFeedTag: `❤`, //将lineFeedTags列表中的字符串替换为该字符, 代表换行符, 只要找一个特殊符号就行,必须是单字符单字符单字符!
|
||||
//交互控制
|
||||
log(t) {
|
||||
log(t);
|
||||
}, // 打印控制,
|
||||
showLoading(msg, ifmask) {
|
||||
// uni.showLoading({
|
||||
// title: msg,
|
||||
// mask: ifmask || false
|
||||
// })
|
||||
},
|
||||
hideLoading() {
|
||||
// uni.hideLoading();
|
||||
},
|
||||
showToast(msg, icon) {
|
||||
// uni.showToast({
|
||||
// title: msg,
|
||||
// icon: icon || 'none'
|
||||
// })
|
||||
},
|
||||
getPosterUrl(objs) { // 后端获取背景图的url路径方法
|
||||
let {
|
||||
backgroundImage,
|
||||
type,
|
||||
formData
|
||||
} = objs;
|
||||
return new Promise((rs, rj) => {
|
||||
let image;
|
||||
if (backgroundImage) {
|
||||
image = backgroundImage;
|
||||
}else{
|
||||
switch (type) { //根据type获取背景图, 一般要改成request获取
|
||||
case 1:
|
||||
image = '';
|
||||
break;
|
||||
default:
|
||||
image = '/static/1.png';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (image) {
|
||||
rs(image); // resolve图片的路径
|
||||
}else{
|
||||
rj('背景图片路径不存在');
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//下面一般不用动他
|
||||
shareTypeListSheetArray: {
|
||||
array: [0, 1, 2, 3, 4, 5]
|
||||
}, // 分享类型 0-图文链接 1-纯文字 2-纯图片 3-音乐 4-视频 5-小程序
|
||||
isArray(arg) {
|
||||
return Object.prototype.toString.call(arg) === '[object Array]';
|
||||
},
|
||||
isObject(arg) {
|
||||
return Object.prototype.toString.call(arg) === '[object Object]';
|
||||
},
|
||||
isPromise(obj) {
|
||||
return !!obj && (typeof obj === 'object' || typeof obj === 'function') && typeof obj.then === 'function';
|
||||
},
|
||||
isNull(arg) {
|
||||
return arg === null;
|
||||
},
|
||||
isUndefined(arg) {
|
||||
return arg === undefined;
|
||||
},
|
||||
isUndef(arg) {
|
||||
return arg === undefined;
|
||||
},
|
||||
isNotNull_string(arg) {
|
||||
return arg !== null && arg !== undefined && arg !== '';
|
||||
},
|
||||
isFn(fn) {
|
||||
return fn && typeof fn === 'function';
|
||||
},
|
||||
getStorage(key, scb, fcb) {
|
||||
uni.getStorage({
|
||||
key,
|
||||
success: function(res) {
|
||||
if (res.data && res.data != "") {
|
||||
if (scb) scb(res.data);
|
||||
} else {
|
||||
if (fcb) fcb();
|
||||
}
|
||||
},
|
||||
fail: function() {
|
||||
if (fcb) fcb();
|
||||
}
|
||||
})
|
||||
},
|
||||
setStorage(key, data) {
|
||||
log('设置缓存')
|
||||
log('key:' + key)
|
||||
log('data:' + JSON.stringify(data));
|
||||
uni.setStorage({
|
||||
key,
|
||||
data
|
||||
})
|
||||
},
|
||||
setStorageSync(key, data) {
|
||||
uni.setStorageSync(key, data);
|
||||
},
|
||||
getStorageSync(key) {
|
||||
return uni.getStorageSync(key);
|
||||
},
|
||||
clearStorageSync() {
|
||||
uni.clearStorageSync();
|
||||
},
|
||||
removeStorageSync(key) {
|
||||
uni.removeStorageSync(key);
|
||||
},
|
||||
getImageInfo(url, cb, fcb) {
|
||||
url = checkMPUrl(url);
|
||||
uni.getImageInfo({
|
||||
src: url,
|
||||
success(res) {
|
||||
if (cb && typeof(cb) == 'function') cb(res);
|
||||
},
|
||||
fail(err) {
|
||||
if (fcb && typeof(fcb) == 'function') fcb(err);
|
||||
}
|
||||
})
|
||||
},
|
||||
downloadFile(url, cb) {
|
||||
url = checkMPUrl(url);
|
||||
uni.downloadFile({
|
||||
url,
|
||||
success(res) {
|
||||
if (cb && typeof(cb) == 'function') cb(res);
|
||||
}
|
||||
})
|
||||
},
|
||||
downloadFile_PromiseFc(url) {
|
||||
return new Promise((rs, rj) => {
|
||||
if (url.substring(0, 4) !== 'http') {
|
||||
rs(url);
|
||||
}else {
|
||||
url = checkMPUrl(url);
|
||||
log('url:' + url);
|
||||
|
||||
uni.downloadFile({
|
||||
url,
|
||||
success(res) {
|
||||
if (res && res.tempFilePath)
|
||||
rs(res.tempFilePath);
|
||||
else
|
||||
rj('not find tempFilePath');
|
||||
},
|
||||
fail(err) {
|
||||
log(err)
|
||||
rj(err);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
},
|
||||
saveFile(url) {
|
||||
uni.saveFile({
|
||||
tempFilePath: url,
|
||||
success(res) {
|
||||
log('保存成功:' + JSON.stringify(res))
|
||||
}
|
||||
})
|
||||
},
|
||||
downLoadAndSaveFile_PromiseFc(url) {
|
||||
return new Promise((rs, rj) => {
|
||||
log('准备下载并保存图片:' + url);
|
||||
if (url.substring(0, 4) === 'http') {
|
||||
url = checkMPUrl(url);
|
||||
uni.downloadFile({
|
||||
url,
|
||||
success(d_res) {
|
||||
log('下载背景图成功:' + JSON.stringify(d_res));
|
||||
if (d_res && d_res.tempFilePath) {
|
||||
|
||||
// #ifdef H5
|
||||
rs(d_res.tempFilePath);
|
||||
// #endif
|
||||
|
||||
// #ifndef H5
|
||||
uni.saveFile({
|
||||
tempFilePath: d_res.tempFilePath,
|
||||
success(s_res) {
|
||||
log('保存背景图成功:' + JSON.stringify(s_res));
|
||||
if (s_res && s_res.savedFilePath)
|
||||
rs(s_res.savedFilePath);
|
||||
else
|
||||
rs(d_res.tempFilePath);
|
||||
},
|
||||
fail(err) {
|
||||
rs(d_res.tempFilePath);
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
} else {
|
||||
rj('not find tempFilePath');
|
||||
}
|
||||
},
|
||||
fail(err) {
|
||||
rj(err);
|
||||
}
|
||||
})
|
||||
}else{
|
||||
rs(url);
|
||||
}
|
||||
})
|
||||
},
|
||||
checkFile_PromiseFc(url) {
|
||||
return new Promise((rs, rj) => {
|
||||
uni.getSavedFileList({
|
||||
success(res) {
|
||||
let d = res.fileList;
|
||||
let index = d.findIndex(item => {
|
||||
return item.filePath === url;
|
||||
})
|
||||
rs(index);
|
||||
},
|
||||
fail(err) {
|
||||
rj(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
removeSavedFile(path) {
|
||||
uni.getSavedFileList({
|
||||
success(res) {
|
||||
let d = res.fileList;
|
||||
let index = d.findIndex(item => {
|
||||
return item.filePath === path;
|
||||
});
|
||||
if (index >= 0)
|
||||
uni.removeSavedFile({
|
||||
filePath: path
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
fileNameInPath(path) {
|
||||
let s = path.split("/");
|
||||
return s[s.length - 1];
|
||||
},
|
||||
getImageInfo_PromiseFc(imgPath) {
|
||||
return new Promise((rs, rj) => {
|
||||
log('准备获取图片信息:' + imgPath);
|
||||
imgPath = checkMPUrl(imgPath);
|
||||
uni.getImageInfo({
|
||||
src: imgPath,
|
||||
success: res => {
|
||||
log('获取图片信息成功:' + JSON.stringify(res));
|
||||
rs(res);
|
||||
},
|
||||
fail: err => {
|
||||
log('获取图片信息失败:' + JSON.stringify(err));
|
||||
rj(err)
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
previewImage(urls) {
|
||||
if (typeof(urls) == 'string')
|
||||
urls = [urls];
|
||||
uni.previewImage({
|
||||
urls,
|
||||
})
|
||||
},
|
||||
actionSheet(obj, cb) {
|
||||
let sheetArray = [];
|
||||
for (let i = 0; i < obj.array.length; i++) {
|
||||
switch (obj.array[i]) {
|
||||
case 'sinaweibo':
|
||||
sheetArray[i] = '新浪微博';
|
||||
break;
|
||||
case 'qq':
|
||||
sheetArray[i] = 'QQ';
|
||||
break;
|
||||
case 'weixin':
|
||||
sheetArray[i] = '微信';
|
||||
break;
|
||||
case 'WXSceneSession':
|
||||
sheetArray[i] = '微信好友';
|
||||
break;
|
||||
case 'WXSenceTimeline':
|
||||
sheetArray[i] = '微信朋友圈';
|
||||
break;
|
||||
case 'WXSceneFavorite':
|
||||
sheetArray[i] = '微信收藏';
|
||||
break;
|
||||
case 0:
|
||||
sheetArray[i] = '图文链接';
|
||||
break;
|
||||
case 1:
|
||||
sheetArray[i] = '纯文字';
|
||||
break;
|
||||
case 2:
|
||||
sheetArray[i] = '纯图片';
|
||||
break;
|
||||
case 3:
|
||||
sheetArray[i] = '音乐';
|
||||
break;
|
||||
case 4:
|
||||
sheetArray[i] = '视频';
|
||||
break;
|
||||
case 5:
|
||||
sheetArray[i] = '小程序';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
this.showActionSheet(sheetArray, cb);
|
||||
},
|
||||
showActionSheet(sheetArray, cb) {
|
||||
uni.showActionSheet({
|
||||
itemList: sheetArray,
|
||||
success: (e) => {
|
||||
if (cb && typeof(cb) == 'function') cb(e.tapIndex);
|
||||
}
|
||||
})
|
||||
},
|
||||
getProvider(type, cb, sheet) {
|
||||
let _this = this;
|
||||
uni.getProvider({
|
||||
service: type,
|
||||
success: function(res) {
|
||||
if (sheet) {
|
||||
let obj = {};
|
||||
obj.array = res.provider;
|
||||
_this.actionSheet(obj, function(index) {
|
||||
if (cb && typeof(cb) == "function") cb(res.provider[index]);
|
||||
});
|
||||
} else {
|
||||
if (type == 'payment') {
|
||||
let providers = res.provider;
|
||||
let payTypeArray = [];
|
||||
for (let i = 0; i < providers.length; i++) {
|
||||
if (providers[i] == 'wxpay') {
|
||||
payTypeArray[i] = {
|
||||
name: '微信支付',
|
||||
value: providers[i],
|
||||
img: '/static/image/wei.png'
|
||||
};
|
||||
} else if (providers[i] == 'alipay') {
|
||||
payTypeArray[i] = {
|
||||
name: "支付宝支付",
|
||||
value: providers[i],
|
||||
img: '/static/image/ali.png'
|
||||
};
|
||||
}
|
||||
}
|
||||
if (cb && typeof(cb) == "function") cb(payTypeArray);
|
||||
} else {
|
||||
if (cb && typeof(cb) == "function") cb(res);
|
||||
}
|
||||
}
|
||||
},
|
||||
})
|
||||
},
|
||||
// #ifdef APP-PLUS
|
||||
getShare(providerName, WXScene, shareType, title, summary, href, imageUrl, miniProgramObj, mediaUrl, scb, fcb) { //miniProgram: {path: '', type: 0, webUrl: ''}
|
||||
let _this = this;
|
||||
if (typeof(shareType) == 'number' && !isNaN(shareType) && shareType >= 0) {
|
||||
_this.readyShare(providerName, WXScene, shareType, title, summary, href, imageUrl, miniProgramObj, mediaUrl, scb,
|
||||
fcb);
|
||||
} else {
|
||||
_this.actionSheet(_this.shareTypeListSheetArray, function(index) {
|
||||
_this.readyShare(providerName, WXScene, _this.shareTypeListSheetArray.array[index], title, summary, href,
|
||||
imageUrl, miniProgramObj, mediaUrl, scb, fcb);
|
||||
});
|
||||
}
|
||||
},
|
||||
readyShare(providerName, WXScene, shareType, title, summary, href, imageUrl, miniProgramObj, mediaUrl, scb, fcb) {
|
||||
let _this = this;
|
||||
let shareObjData = {};
|
||||
switch (shareType) {
|
||||
case 0:
|
||||
shareObjData = {
|
||||
href: href,
|
||||
summary: summary,
|
||||
imageUrl: imageUrl
|
||||
};
|
||||
break;
|
||||
case 1:
|
||||
shareObjData = {
|
||||
summary: summary,
|
||||
href: href
|
||||
};
|
||||
break;
|
||||
case 2:
|
||||
shareObjData = {
|
||||
imageUrl: imageUrl
|
||||
};
|
||||
break;
|
||||
case 3:
|
||||
if (mediaUrl) {
|
||||
_this.showToast('暂不支持此分享类型');
|
||||
return;
|
||||
};
|
||||
shareObjData = {
|
||||
mediaUrl: mediaUrl
|
||||
};
|
||||
break;
|
||||
case 4:
|
||||
if (mediaUrl) {
|
||||
_this.showToast('暂不支持此分享类型');
|
||||
return;
|
||||
};
|
||||
shareObjData = {
|
||||
mediaUrl: mediaUrl
|
||||
};
|
||||
break;
|
||||
case 5:
|
||||
shareObjData = {
|
||||
miniProgram: { ...miniProgramObj,
|
||||
id: miniProgramId,
|
||||
type: miniProgramShareType
|
||||
},
|
||||
imageUrl: imageUrl
|
||||
};
|
||||
providerName = 'weixin';
|
||||
break;
|
||||
default:
|
||||
_this.showToast('分享参数-shareType错误');
|
||||
return;
|
||||
break;
|
||||
}
|
||||
shareObjData.title = title;
|
||||
_this.share(providerName, WXScene, shareType, shareObjData, function(res) {
|
||||
if (scb && typeof(scb) == 'function') scb(res);
|
||||
}, function(err) {
|
||||
if (fcb && typeof(fcb) == 'function') fcb(err);
|
||||
});
|
||||
},
|
||||
share(providerName, WXScene, shareType, data, scb, fcb) {
|
||||
let _this = this;
|
||||
let shareObj = {
|
||||
provider: '',
|
||||
success: Function,
|
||||
fail: Function
|
||||
};
|
||||
if (providerName && providerName != '') {
|
||||
shareObj.provider = providerName;
|
||||
if (providerName == 'weixin') {
|
||||
_this.readyShareWXScene(WXScene, function(Scene) {
|
||||
shareObj.scene = Scene;
|
||||
_this.doingShare(shareObj, shareType, data, scb, fcb);
|
||||
});
|
||||
} else {
|
||||
_this.doingShare(shareObj, shareType, data, scb, fcb);
|
||||
}
|
||||
} else {
|
||||
_this.getProvider('share', function(name) {
|
||||
shareObj.provider = name;
|
||||
if (name == 'weixin') {
|
||||
_this.readyShareWXScene(WXScene, function(Scene) {
|
||||
shareObj.scene = Scene;
|
||||
_this.doingShare(shareObj, shareType, data, scb, fcb);
|
||||
});
|
||||
} else {
|
||||
_this.doingShare(shareObj, shareType, data, scb, fcb);
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
},
|
||||
readyShareWXScene(WXScene, cb) {
|
||||
let _this = this;
|
||||
let WXScenetypelist = {
|
||||
array: ['WXSceneSession', 'WXSenceTimeline', 'WXSceneFavorite']
|
||||
};
|
||||
if (WXScene && WXScene != "") {
|
||||
if (cb && typeof(cb) == 'function') cb(WXScene);
|
||||
} else {
|
||||
_this.actionSheet(WXScenetypelist, function(index) {
|
||||
if (cb && typeof(cb) == 'function') cb(WXScenetypelist.array[index]);
|
||||
});
|
||||
}
|
||||
},
|
||||
doingShare(shareObj, shareType, data, scb, fcb) {
|
||||
shareObj.type = shareType;
|
||||
if (data && data.title) shareObj.title = data.title;
|
||||
switch (shareType) {
|
||||
case 0: //图文链接
|
||||
shareObj.href = data.href;
|
||||
shareObj.summary = data.summary;
|
||||
shareObj.imageUrl = data.imageUrl;
|
||||
break;
|
||||
case 1: //纯文字
|
||||
shareObj.summary = data.summary;
|
||||
shareObj.href = data.href;
|
||||
break;
|
||||
case 2: //纯图片
|
||||
shareObj.imageUrl = data.imageUrl;
|
||||
break;
|
||||
case 3: //音乐
|
||||
if (!data.mediaUrl) {
|
||||
_this.showToast('暂不支持此分享类型');
|
||||
return;
|
||||
};
|
||||
shareObj.mediaUrl = data.mediaUrl;
|
||||
break;
|
||||
case 4: //视频
|
||||
if (!data.mediaUrl) {
|
||||
_this.showToast('暂不支持此分享类型');
|
||||
return;
|
||||
};
|
||||
shareObj.mediaUrl = data.mediaUrl;
|
||||
break;
|
||||
case 5: //小程序
|
||||
if (miniProgramId == '') {
|
||||
uni.showToast('未设置小程序ID, 请使用其他方式分享');
|
||||
return;
|
||||
}
|
||||
let mp = {
|
||||
id: miniProgramId
|
||||
};
|
||||
mp.path = data.miniProgram.path;
|
||||
mp.type = data.miniProgram.type;
|
||||
if (data.miniProgram.webUrl) mp.webUrl = data.miniProgram.webUrl;
|
||||
shareObj.miniProgram = mp;
|
||||
shareObj.imageUrl = data.imageUrl;
|
||||
break;
|
||||
default:
|
||||
appJS.showToast('分享参数-shareType错误');
|
||||
break;
|
||||
}
|
||||
shareObj.success = function(res) {
|
||||
if (scb && typeof(scb) == 'function') scb(res);
|
||||
}
|
||||
shareObj.fail = function(err) {
|
||||
if (fcb && typeof(fcb) == 'function') fcb(err);
|
||||
}
|
||||
log(JSON.stringify(shareObj));
|
||||
uni.share(shareObj);
|
||||
},
|
||||
// #endif
|
||||
}
|
||||
|
||||
function checkMPUrl(url) {
|
||||
// #ifdef MP
|
||||
// if(process.env.NODE_ENV !== 'development'){
|
||||
// if(
|
||||
// url.substring(0, 4) === 'http' &&
|
||||
// url.substring(0, 5) !== 'https' &&
|
||||
// url.substring(0, 12) !== 'http://store' &&
|
||||
// url.substring(0, 10) !== 'http://tmp' &&
|
||||
// url.substring(0, 10) !== 'http://usr'
|
||||
// ) {
|
||||
// url = 'https' + url.substring(4, url.length);
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
return url;
|
||||
}
|
||||
|
||||
module.exports = _app;
|
||||
|
|
@ -1,147 +0,0 @@
|
|||
function getLocalFilePath(path) {
|
||||
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf('_downloads') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('file://') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/storage/emulated/0/') === 0) {
|
||||
return path
|
||||
}
|
||||
if (path.indexOf('/') === 0) {
|
||||
var localFilePath = plus.io.convertAbsoluteFileSystem(path)
|
||||
if (localFilePath !== path) {
|
||||
return localFilePath
|
||||
} else {
|
||||
path = path.substr(1)
|
||||
}
|
||||
}
|
||||
return '_www/' + path
|
||||
}
|
||||
|
||||
export function pathToBase64(path) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (typeof window === 'object' && 'document' in window) {
|
||||
if (typeof FileReader === 'function') {
|
||||
var xhr = new XMLHttpRequest()
|
||||
xhr.open('GET', path, true)
|
||||
xhr.responseType = 'blob'
|
||||
xhr.onload = function() {
|
||||
if (this.status === 200) {
|
||||
let fileReader = new FileReader()
|
||||
fileReader.onload = function(e) {
|
||||
resolve(e.target.result)
|
||||
}
|
||||
fileReader.onerror = reject
|
||||
fileReader.readAsDataURL(this.response)
|
||||
}
|
||||
}
|
||||
xhr.onerror = reject
|
||||
xhr.send()
|
||||
return
|
||||
}
|
||||
var canvas = document.createElement('canvas')
|
||||
var c2x = canvas.getContext('2d')
|
||||
var img = new Image
|
||||
img.onload = function() {
|
||||
canvas.width = img.width
|
||||
canvas.height = img.height
|
||||
c2x.drawImage(img, 0, 0)
|
||||
resolve(canvas.toDataURL())
|
||||
canvas.height = canvas.width = 0
|
||||
}
|
||||
img.onerror = reject
|
||||
img.src = path
|
||||
return
|
||||
}
|
||||
if (typeof plus === 'object') {
|
||||
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), function(entry) {
|
||||
entry.file(function(file) {
|
||||
var fileReader = new plus.io.FileReader()
|
||||
fileReader.onload = function(data) {
|
||||
resolve(data.target.result)
|
||||
}
|
||||
fileReader.onerror = function(error) {
|
||||
reject(error)
|
||||
}
|
||||
fileReader.readAsDataURL(file)
|
||||
}, function(error) {
|
||||
reject(error)
|
||||
})
|
||||
}, function(error) {
|
||||
reject(error)
|
||||
})
|
||||
return
|
||||
}
|
||||
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
|
||||
wx.getFileSystemManager().readFile({
|
||||
filePath: path,
|
||||
encoding: 'base64',
|
||||
success: function(res) {
|
||||
resolve('data:image/png;base64,' + res.data)
|
||||
},
|
||||
fail: function(error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
reject(new Error('not support'))
|
||||
})
|
||||
}
|
||||
|
||||
export function base64ToPath(base64) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
if (typeof window === 'object' && 'document' in window) {
|
||||
base64 = base64.split(',')
|
||||
var type = base64[0].match(/:(.*?);/)[1]
|
||||
var str = atob(base64[1])
|
||||
var n = str.length
|
||||
var array = new Uint8Array(n)
|
||||
while (n--) {
|
||||
array[n] = str.charCodeAt(n)
|
||||
}
|
||||
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], { type: type })))
|
||||
}
|
||||
var extName = base64.match(/data\:\S+\/(\S+);/)
|
||||
if (extName) {
|
||||
extName = extName[1]
|
||||
} else {
|
||||
reject(new Error('base64 error'))
|
||||
}
|
||||
var fileName = Date.now() + '.' + extName
|
||||
if (typeof plus === 'object') {
|
||||
var bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
|
||||
bitmap.loadBase64Data(base64, function() {
|
||||
var filePath = '_doc/uniapp_temp/' + fileName
|
||||
bitmap.save(filePath, {}, function() {
|
||||
bitmap.clear()
|
||||
resolve(filePath)
|
||||
}, function(error) {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
}, function(error) {
|
||||
bitmap.clear()
|
||||
reject(error)
|
||||
})
|
||||
return
|
||||
}
|
||||
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
|
||||
var filePath = wx.env.USER_DATA_PATH + '/' + fileName
|
||||
wx.getFileSystemManager().writeFile({
|
||||
filePath: filePath,
|
||||
data: base64.replace(/^data:\S+\/\S+;base64,/, ''),
|
||||
encoding: 'base64',
|
||||
success: function() {
|
||||
resolve(filePath)
|
||||
},
|
||||
fail: function(error) {
|
||||
reject(error)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
reject(new Error('not support'))
|
||||
})
|
||||
}
|
||||
|
|
@ -1,288 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<u-popup :zoom="zoom" mode="center" :popup="false" :z-index="uZIndex" v-model="value" :length="width"
|
||||
:mask-close-able="maskCloseAble" :border-radius="borderRadius" @close="popupClose" :negative-top="negativeTop">
|
||||
<view class="u-model">
|
||||
<view v-if="showTitle" class="u-model__title u-line-1" :style="[titleStyle]">{{ title }}</view>
|
||||
<view class="u-model__content">
|
||||
<view :style="[contentStyle]" v-if="$slots.default || $slots.$default">
|
||||
<slot />
|
||||
</view>
|
||||
<view v-else class="u-model__content__message" :style="[contentStyle]">{{ content }}</view>
|
||||
</view>
|
||||
<view class="u-model__footer u-border-top" v-if="showCancelButton || showConfirmButton">
|
||||
<view v-if="showCancelButton" :hover-stay-time="100" hover-class="u-model__btn--hover" class="u-model__footer__button"
|
||||
:style="[cancelBtnStyle]" @tap="cancel">
|
||||
{{cancelText}}
|
||||
</view>
|
||||
<view v-if="showConfirmButton || $slots['confirm-button']" :hover-stay-time="100" :hover-class="asyncClose ? 'none' : 'u-model__btn--hover'"
|
||||
class="u-model__footer__button hairline-left" :style="[confirmBtnStyle]" @tap="confirm">
|
||||
<slot v-if="$slots['confirm-button']" name="confirm-button"></slot>
|
||||
<block v-else>
|
||||
<u-loading mode="circle" :color="confirmColor" v-if="loading && showLoading"></u-loading>
|
||||
<block v-else>
|
||||
{{confirmText}}
|
||||
</block>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
/**
|
||||
* modal 模态框
|
||||
* @description 弹出模态框,常用于消息提示、消息确认、在当前页面内完成特定的交互操作
|
||||
* @tutorial https://www.uviewui.com/components/modal.html
|
||||
* @property {Boolean} value 是否显示模态框
|
||||
* @property {String | Number} z-index 层级
|
||||
* @property {String} title 模态框标题(默认"提示")
|
||||
* @property {String | Number} width 模态框宽度(默认600)
|
||||
* @property {String} content 模态框内容(默认"内容")
|
||||
* @property {Boolean} show-title 是否显示标题(默认true)
|
||||
* @property {Boolean} async-close 是否异步关闭,只对确定按钮有效(默认false)
|
||||
* @property {Boolean} show-confirm-button 是否显示确认按钮(默认true)
|
||||
* @property {Stringr | Number} negative-top modal往上偏移的值
|
||||
* @property {Boolean} show-cancel-button 是否显示取消按钮(默认false)
|
||||
* @property {Boolean} mask-close-able 是否允许点击遮罩关闭modal(默认false)
|
||||
* @property {String} confirm-text 确认按钮的文字内容(默认"确认")
|
||||
* @property {String} cancel-text 取消按钮的文字内容(默认"取消")
|
||||
* @property {String} cancel-color 取消按钮的颜色(默认"#606266")
|
||||
* @property {String} confirm-color 确认按钮的文字内容(默认"#2979ff")
|
||||
* @property {String | Number} border-radius 模态框圆角值,单位rpx(默认16)
|
||||
* @property {Object} title-style 自定义标题样式,对象形式
|
||||
* @property {Object} content-style 自定义内容样式,对象形式
|
||||
* @property {Object} cancel-style 自定义取消按钮样式,对象形式
|
||||
* @property {Object} confirm-style 自定义确认按钮样式,对象形式
|
||||
* @property {Boolean} zoom 是否开启缩放模式(默认true)
|
||||
* @event {Function} confirm 确认按钮被点击
|
||||
* @event {Function} cancel 取消按钮被点击
|
||||
* @example <u-modal :src="title" :content="content"></u-modal>
|
||||
*/
|
||||
export default {
|
||||
name: 'u-modal',
|
||||
props: {
|
||||
// loading
|
||||
showLoading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否显示Modal
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 层级z-index
|
||||
zIndex: {
|
||||
type: [Number, String],
|
||||
default: ''
|
||||
},
|
||||
// 标题
|
||||
title: {
|
||||
type: [String],
|
||||
default: '提示'
|
||||
},
|
||||
// 弹窗宽度,可以是数值(rpx),百分比,auto等
|
||||
width: {
|
||||
type: [Number, String],
|
||||
default: 600
|
||||
},
|
||||
// 弹窗内容
|
||||
content: {
|
||||
type: String,
|
||||
default: '内容'
|
||||
},
|
||||
// 是否显示标题
|
||||
showTitle: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示确认按钮
|
||||
showConfirmButton: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否显示取消按钮
|
||||
showCancelButton: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 确认文案
|
||||
confirmText: {
|
||||
type: String,
|
||||
default: '确认'
|
||||
},
|
||||
// 取消文案
|
||||
cancelText: {
|
||||
type: String,
|
||||
default: '取消'
|
||||
},
|
||||
// 确认按钮颜色
|
||||
confirmColor: {
|
||||
type: String,
|
||||
default: '#378264'
|
||||
},
|
||||
// 取消文字颜色
|
||||
cancelColor: {
|
||||
type: String,
|
||||
default: '#606266'
|
||||
},
|
||||
// 圆角值
|
||||
borderRadius: {
|
||||
type: [Number, String],
|
||||
default: 16
|
||||
},
|
||||
// 标题的样式
|
||||
titleStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 内容的样式
|
||||
contentStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 取消按钮的样式
|
||||
cancelStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 确定按钮的样式
|
||||
confirmStyle: {
|
||||
type: Object,
|
||||
default () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
// 是否开启缩放效果
|
||||
zoom: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
// 是否异步关闭,只对确定按钮有效
|
||||
asyncClose: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 是否允许点击遮罩关闭modal
|
||||
maskCloseAble: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
// 给一个负的margin-top,往上偏移,避免和键盘重合的情况
|
||||
negativeTop: {
|
||||
type: [String, Number],
|
||||
default: 0
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
loading: false, // 确认按钮是否正在加载中
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
cancelBtnStyle() {
|
||||
return Object.assign({
|
||||
color: this.cancelColor
|
||||
}, this.cancelStyle);
|
||||
},
|
||||
confirmBtnStyle() {
|
||||
return Object.assign({
|
||||
color: this.confirmColor
|
||||
}, this.confirmStyle);
|
||||
},
|
||||
uZIndex() {
|
||||
return this.zIndex ? this.zIndex : this.$u.zIndex.popup;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
// 如果是异步关闭时,外部修改v-model的值为false时,重置内部的loading状态
|
||||
// 避免下次打开的时候,状态混乱
|
||||
value(n) {
|
||||
if (n === true) this.loading = false;
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
confirm() {
|
||||
// 异步关闭
|
||||
if (this.asyncClose) {
|
||||
this.loading = true;
|
||||
} else {
|
||||
this.$emit('input', false);
|
||||
}
|
||||
this.$emit('confirm');
|
||||
},
|
||||
cancel() {
|
||||
this.$emit('cancel');
|
||||
this.$emit('input', false);
|
||||
// 目前popup弹窗关闭有一个延时操作,此处做一个延时
|
||||
// 避免确认按钮文字变成了"确定"字样,modal还没消失,造成视觉不好的效果
|
||||
setTimeout(() => {
|
||||
this.loading = false;
|
||||
}, 300);
|
||||
},
|
||||
// 点击遮罩关闭modal,设置v-model的值为false,否则无法第二次弹起modal
|
||||
popupClose() {
|
||||
this.$emit('input', false);
|
||||
},
|
||||
// 清除加载中的状态
|
||||
clearLoading() {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@import "uView-ui/libs/css/style.components.scss";
|
||||
|
||||
.u-model {
|
||||
height: auto;
|
||||
overflow: hidden;
|
||||
font-size: 32rpx;
|
||||
background-color: #fff;
|
||||
|
||||
&__btn--hover {
|
||||
background-color: rgb(230, 230, 230);
|
||||
}
|
||||
|
||||
&__title {
|
||||
padding-top: 48rpx;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: $u-main-color;
|
||||
}
|
||||
|
||||
&__content {
|
||||
&__message {
|
||||
padding: 48rpx;
|
||||
font-size: 30rpx;
|
||||
text-align: center;
|
||||
color: $u-content-color;
|
||||
}
|
||||
}
|
||||
|
||||
&__footer {
|
||||
@include vue-flex;
|
||||
|
||||
&__button {
|
||||
flex: 1;
|
||||
height: 100rpx;
|
||||
line-height: 100rpx;
|
||||
font-size: 32rpx;
|
||||
box-sizing: border-box;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
border-radius: 4rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
<template>
|
||||
<view :class="'loading ' + ( type == 'flex' ? 'flex' : '' )" :style="{backgroundColor, }">
|
||||
<u-loading mode="circle" :size="size" :color="color"></u-loading>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
|
||||
export default {
|
||||
data() {
|
||||
return {};
|
||||
},
|
||||
props: {
|
||||
type: {
|
||||
type: String,
|
||||
default: 'fixed'
|
||||
},
|
||||
|
||||
backgroundColor: {
|
||||
type: String,
|
||||
default: '#fff'
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 40
|
||||
}
|
||||
},
|
||||
methods: {}
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
.loading {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
z-index: 9999;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.loading.flex {
|
||||
position: static;
|
||||
flex: 1;
|
||||
width: 100%;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
<template>
|
||||
<view class="mark" :class="[show_key ? '' : 'hidden']">
|
||||
<view class="kong"></view>
|
||||
<!-- 信息框 -->
|
||||
<view class="msg">
|
||||
<!-- 关闭按钮 -->
|
||||
<view class="px-30rpx py-20rpx">
|
||||
<u-icon name="close" @tap="closeFuc"> </u-icon>
|
||||
</view>
|
||||
<view class="title"> {{title}} </view>
|
||||
<slot> </slot>
|
||||
<view class="pswBox">
|
||||
<view v-for="(item, index) in 6" :key="index" class="content_item">{{ password[index] ? '●' : '' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 数字键盘 -->
|
||||
<view class="numeric">
|
||||
<block v-for="item in num" :key="item">
|
||||
<view class="num" :class="item == 10 ? 'amend1' : item == 12 ? 'amend3 iconfont' : ''" @tap="press({ num: item })">
|
||||
<u-icon size="36" v-if="item == 12" name="backspace"></u-icon>
|
||||
<text v-else>{{ item == 10 ? '' : item == 11 ? '0' : item == 12 ? '' : item }}</text>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
title:{
|
||||
type:String,
|
||||
default:'请输入支付密码'
|
||||
}
|
||||
},
|
||||
computed:{
|
||||
show_key:{
|
||||
get(){
|
||||
return this.value
|
||||
},
|
||||
set(val){
|
||||
this.$emit('input',val)
|
||||
}
|
||||
}
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
num: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
|
||||
password: '',
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
press(obj) {
|
||||
let num = obj.num;
|
||||
if (obj.num == 10) {
|
||||
console.log('我是10我什么都不干');
|
||||
} else if (obj.num == 12) {
|
||||
this.password = this.password.slice(0, this.password.length - 1);
|
||||
} else if (obj.num == 11) {
|
||||
num = '0';
|
||||
this.password += num;
|
||||
} else {
|
||||
this.password += num;
|
||||
}
|
||||
if (this.password.length == 6) {
|
||||
this.$emit('getPassword', this.password);
|
||||
this.password = '';
|
||||
}
|
||||
},
|
||||
// 关闭支付页面
|
||||
closeFuc() {
|
||||
this.show_key = false
|
||||
this.$emit('closeFuc', false);
|
||||
},
|
||||
// 找回密码
|
||||
forgetFuc() {
|
||||
uni.navigateTo({
|
||||
url: '/pages/mine/myWallet/changezfPwd/changezfPwd',
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.mark {
|
||||
width: 750upx;
|
||||
background: rgba(0, 0, 0, 0.7);
|
||||
padding: 0 0 500upx 0;
|
||||
position: fixed;
|
||||
top: 0upx;
|
||||
left: 0upx;
|
||||
z-index: 10027;
|
||||
}
|
||||
.hidden {
|
||||
display: none;
|
||||
}
|
||||
.kong {
|
||||
width: 750upx;
|
||||
height: 250upx;
|
||||
}
|
||||
.msg {
|
||||
width: 550upx;
|
||||
height: 450upx;
|
||||
background: rgba(255, 255, 255, 1);
|
||||
border-radius: 20upx;
|
||||
margin: 0 auto;
|
||||
animation: msgBox 0.2s linear;
|
||||
}
|
||||
@keyframes msgBox {
|
||||
0% {
|
||||
transform: translateY(50%);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: translateY(25%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
@keyframes numBox {
|
||||
0% {
|
||||
transform: translateY(50%);
|
||||
opacity: 0;
|
||||
}
|
||||
50% {
|
||||
transform: translateY(25%);
|
||||
opacity: 0.5;
|
||||
}
|
||||
100% {
|
||||
transform: translateY(0%);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
.msg > .img {
|
||||
padding: 20upx 0 0 20upx;
|
||||
font-size: 40upx;
|
||||
}
|
||||
.msg > .title {
|
||||
width: 100%;
|
||||
height: 100upx;
|
||||
line-height: 100upx;
|
||||
font-weight: 500;
|
||||
font-size: 36upx;
|
||||
text-align: center;
|
||||
}
|
||||
.msg > .subTitle {
|
||||
width: 100%;
|
||||
height: 50upx;
|
||||
line-height: 50upx;
|
||||
font-weight: 400;
|
||||
font-size: 32upx;
|
||||
text-align: center;
|
||||
}
|
||||
.pswBox {
|
||||
width: 80%;
|
||||
height: 80upx;
|
||||
margin: 50upx auto 0;
|
||||
display: flex;
|
||||
}
|
||||
.content_item {
|
||||
flex: 2;
|
||||
text-align: center;
|
||||
line-height: 80upx;
|
||||
border: 1upx solid #d6d6d6;
|
||||
border-right: 0upx solid;
|
||||
}
|
||||
.content_item:nth-child(1) {
|
||||
border-radius: 10upx 0 0 10upx;
|
||||
}
|
||||
.content_item:nth-child(6) {
|
||||
border-right: 1upx solid #d6d6d6;
|
||||
border-radius: 0 10upx 10upx 0;
|
||||
}
|
||||
.numeric {
|
||||
width: 750upx;
|
||||
height: 470upx;
|
||||
border: 1upx solid;
|
||||
position: fixed;
|
||||
z-index: 98;
|
||||
bottom: 0upx;
|
||||
background-color: #fff;
|
||||
animation: msgBox 0.2s linear;
|
||||
}
|
||||
.num {
|
||||
width: 225upx;
|
||||
height: 90upx;
|
||||
font-size: 42upx;
|
||||
font-weight: 500;
|
||||
line-height: 90upx;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
margin: 12upx 12upx 0 12upx;
|
||||
display: inline-block;
|
||||
border: 1upx solid #e4e7ed;
|
||||
border-radius: 10upx;
|
||||
background-color: #ffffff;
|
||||
position: relative;
|
||||
z-index: 99;
|
||||
}
|
||||
.forget {
|
||||
font-size: 28upx;
|
||||
font-weight: 500;
|
||||
color: #3d84ea;
|
||||
text-align: center;
|
||||
line-height: 80upx;
|
||||
}
|
||||
.amend1 {
|
||||
border: 1upx solid #e4e7ed;
|
||||
background-color: #cccfd6;
|
||||
}
|
||||
.amend3 {
|
||||
border: 1upx solid #e4e7ed;
|
||||
background-color: #e4e7ed;
|
||||
line-height: 80rpx;
|
||||
}
|
||||
/* .amend11{
|
||||
position: absolute;
|
||||
top: 313upx;
|
||||
left: 0upx;
|
||||
background-color: #CCCFD6;
|
||||
border: 1upx solid #FF0000;
|
||||
}
|
||||
.amend1{
|
||||
height: 100upx !important;
|
||||
position: absolute;
|
||||
top: 306upx;
|
||||
left: 0upx;
|
||||
z-index: 99;
|
||||
background-color: #CCCFD6;
|
||||
border: 2upx solid #CCCFD6;
|
||||
}
|
||||
.amend2{
|
||||
position: absolute;
|
||||
top: 306upx;
|
||||
left: 250upx;
|
||||
z-index: 99;
|
||||
}
|
||||
.amend3{
|
||||
position: absolute;
|
||||
top: 306upx;
|
||||
left: 500upx;
|
||||
z-index: 99;
|
||||
font-size: 60upx;
|
||||
border: 0upx;
|
||||
background-color: #CCCFD6;
|
||||
} */
|
||||
</style>
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
<template>
|
||||
<view class="flex w-full items-center">
|
||||
<u-input type="number" :clearable="false" class="w-full flex-1 ml-30rpx flex-1" v-model="val" :maxlength="maxLength" :placeholder="placeholder"></u-input>
|
||||
<view>
|
||||
<view @tap="run" class="bg-hex-F4F4F4 rounded-full px-32rpx py-12rpx text-primary text-26rpx"> {{ text }} </view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: '验证码',
|
||||
},
|
||||
maxLength: {
|
||||
type: Number,
|
||||
},
|
||||
second: {
|
||||
type: Number,
|
||||
default: 60,
|
||||
},
|
||||
btnText: {
|
||||
type: String,
|
||||
default: '获取验证码',
|
||||
},
|
||||
disabled: {
|
||||
type: [Boolean, Number],
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
time: 0,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
text() {
|
||||
return this.time > 0 ? this.time + `秒重新获取` : this.btnText;
|
||||
},
|
||||
val: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(e) {
|
||||
this.$emit('input', e);
|
||||
},
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
getSmsCode() {
|
||||
this.$emit('change');
|
||||
},
|
||||
run() {
|
||||
if(this.time>0) return
|
||||
this.$emit('run');
|
||||
},
|
||||
start() {
|
||||
this.time = this.second;
|
||||
this.timer();
|
||||
},
|
||||
stop() {
|
||||
this.time = 0;
|
||||
this.timer();
|
||||
},
|
||||
timer() {
|
||||
if (this.time > 0) {
|
||||
this.time--;
|
||||
setTimeout(this.timer, 1000);
|
||||
}
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
37
src/main.js
|
|
@ -1,37 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import App from './App'
|
||||
import store from '@/store'
|
||||
|
||||
import routeMixin from '@/mixin/router.mixin'
|
||||
Vue.mixin(routeMixin)
|
||||
import appMixin from '@/mixin/app.mixin'
|
||||
Vue.mixin(appMixin)
|
||||
|
||||
import uView from "uview-ui";
|
||||
Vue.use(uView);
|
||||
|
||||
import * as filters from "@/utils/filters.js";
|
||||
// 注册全局实用程序过滤器.
|
||||
Object.keys(filters).forEach(key => {
|
||||
Vue.filter(key, filters[key]);
|
||||
});
|
||||
|
||||
import { http } from '@/utils/request'
|
||||
Vue.config.productionTip = false
|
||||
App.mpType = 'app'
|
||||
Vue.prototype.$api = http
|
||||
|
||||
// #ifdef H5
|
||||
if(process.env.NODE_ENV === 'development'){
|
||||
const vconsole = require('vconsole')
|
||||
Vue.prototype.$vconsole = new vconsole() // 使用vconsole
|
||||
}
|
||||
// #endif
|
||||
|
||||
|
||||
const app = new Vue({
|
||||
store,
|
||||
...App
|
||||
})
|
||||
|
||||
app.$mount()
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
{
|
||||
"name" : "销售端",
|
||||
"appid" : "__UNI__EC894CF",
|
||||
"description" : "",
|
||||
"versionName" : "1.4.1",
|
||||
"versionCode" : 141,
|
||||
"transformPx" : false,
|
||||
"app-plus" : {
|
||||
/* 5+App特有相关 */
|
||||
"usingComponents" : true,
|
||||
"splashscreen" : {
|
||||
"alwaysShowBeforeRender" : true,
|
||||
"waiting" : true,
|
||||
"autoclose" : true,
|
||||
"delay" : 0
|
||||
},
|
||||
"privacy" : {
|
||||
"prompt" : "template",
|
||||
"template" : {
|
||||
//prompt取值为template时有效,用于配置模板提示框上显示的内容
|
||||
"title" : "服务协议和隐私政策",
|
||||
"message" : "请你务必审慎阅读、充分理解“服务协议”和“隐私协议”各条款,包括但不限于:为你提供商品购买等服务,我们需要收集你的设备信息等个人信息。你可以在“设置”中查看、变更、删除个人信息并管理你的授权。你可阅读<a href=\"https://api.zichunsheng.cn/h5/articles/2\">《用户协议》</a>及<a href=\"https://api.zichunsheng.cn/h5/articles/3\">《隐私政策》</a>,<br/> 了解详细信息。如你同意,请点击“同意”开始接受我们的服务",
|
||||
"buttonAccept" : "同意", //继续下一步
|
||||
"buttonRefuse" : "暂不使用" //退出下载
|
||||
}
|
||||
},
|
||||
"compatible" : {
|
||||
"ignoreVersion" : true //true表示忽略版本检查提示框
|
||||
},
|
||||
"safearea" : {
|
||||
//安全区域配置,仅iOS平台生效
|
||||
"bottom" : {
|
||||
// 底部安全区域配置
|
||||
"offset" : "none" // 底部安全区域偏移,"none"表示不空出安全区域,"auto"自动计算空出安全区域,默认值为"none"
|
||||
}
|
||||
},
|
||||
"modules" : {
|
||||
"Payment" : {},
|
||||
"iBeacon" : {},
|
||||
"SQLite" : {},
|
||||
"Push" : {},
|
||||
"Share" : {},
|
||||
"VideoPlayer" : {},
|
||||
"Maps" : {}
|
||||
},
|
||||
/* 模块配置 */
|
||||
"distribute" : {
|
||||
/* 应用发布信息 */
|
||||
"android" : {
|
||||
/* android打包配置 */
|
||||
"permissions" : [
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.BLUETOOTH\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECEIVE_USER_PRESENT\"/>",
|
||||
"<uses-permission android:name=\"android.permission.BLUETOOTH\"/>",
|
||||
"<uses-permission android:name=\"android.permission.BLUETOOTH_ADMIN\"/>"
|
||||
],
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a" ]
|
||||
},
|
||||
"ios" : {
|
||||
"capabilities" : {
|
||||
"entitlements" : {
|
||||
"com.apple.developer.associated-domains" : [ "applinks:static-639df241-3e40-451e-9e00-2cf3bf79ce41.bspapp.com" ]
|
||||
}
|
||||
},
|
||||
"privacyDescription" : {
|
||||
"NSPhotoLibraryUsageDescription" : "因相关功能涉及图片的读取与写入,请在设置-子春生中开启相册权限",
|
||||
"NSPhotoLibraryAddUsageDescription" : "因相关功能涉及图片的读取与写入,请在设置-子春生中开启相册权限",
|
||||
"NSCameraUsageDescription" : "因相关功能涉及照相,请在设置-子春生中开启相机权限",
|
||||
"NSLocationWhenInUseUsageDescription" : "用户能快速知道当前位置",
|
||||
"NSSpeechRecognitionUsageDescription" : "语音播报"
|
||||
},
|
||||
"idfa" : false
|
||||
},
|
||||
/* ios打包配置 */
|
||||
"sdkConfigs" : {
|
||||
"ad" : {},
|
||||
"maps" : {
|
||||
"amap" : {
|
||||
"appkey_ios" : "2fb25034076d2f0ab550ab3ab8616ce1",
|
||||
"appkey_android" : "ab4fc873f9e4a3b4e9bd9c586ddb3a4e"
|
||||
}
|
||||
},
|
||||
"payment" : {
|
||||
"weixin" : {
|
||||
"__platform__" : [ "ios", "android" ],
|
||||
"appid" : "wx5b38851d7f7aeede",
|
||||
"UniversalLinks" : "https://ios.zichunsheng.cn/ulink/"
|
||||
},
|
||||
"alipay" : {
|
||||
"__platform__" : [ "ios", "android" ]
|
||||
}
|
||||
},
|
||||
"push" : {
|
||||
"unipush" : {}
|
||||
},
|
||||
"share" : {
|
||||
"weixin" : {
|
||||
"appid" : "wx5b38851d7f7aeede",
|
||||
"UniversalLinks" : "https://ios.zichunsheng.cn/ulink/"
|
||||
}
|
||||
},
|
||||
"speech" : {
|
||||
"baidu" : {
|
||||
"appid" : "",
|
||||
"apikey" : "",
|
||||
"secretkey" : ""
|
||||
}
|
||||
}
|
||||
},
|
||||
"icons" : {
|
||||
"android" : {
|
||||
"hdpi" : "unpackage/res/icons/72x72.png",
|
||||
"xhdpi" : "unpackage/res/icons/96x96.png",
|
||||
"xxhdpi" : "unpackage/res/icons/144x144.png",
|
||||
"xxxhdpi" : "unpackage/res/icons/192x192.png"
|
||||
},
|
||||
"ios" : {
|
||||
"appstore" : "unpackage/res/icons/1024x1024.png",
|
||||
"ipad" : {
|
||||
"app" : "unpackage/res/icons/76x76.png",
|
||||
"app@2x" : "unpackage/res/icons/152x152.png",
|
||||
"notification" : "unpackage/res/icons/20x20.png",
|
||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||
"proapp@2x" : "unpackage/res/icons/167x167.png",
|
||||
"settings" : "unpackage/res/icons/29x29.png",
|
||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||
"spotlight" : "unpackage/res/icons/40x40.png",
|
||||
"spotlight@2x" : "unpackage/res/icons/80x80.png"
|
||||
},
|
||||
"iphone" : {
|
||||
"app@2x" : "unpackage/res/icons/120x120.png",
|
||||
"app@3x" : "unpackage/res/icons/180x180.png",
|
||||
"notification@2x" : "unpackage/res/icons/40x40.png",
|
||||
"notification@3x" : "unpackage/res/icons/60x60.png",
|
||||
"settings@2x" : "unpackage/res/icons/58x58.png",
|
||||
"settings@3x" : "unpackage/res/icons/87x87.png",
|
||||
"spotlight@2x" : "unpackage/res/icons/80x80.png",
|
||||
"spotlight@3x" : "unpackage/res/icons/120x120.png"
|
||||
}
|
||||
}
|
||||
},
|
||||
"splashscreen" : {
|
||||
"iosStyle" : "common",
|
||||
"androidStyle" : "common"
|
||||
}
|
||||
}
|
||||
},
|
||||
/* SDK配置 */
|
||||
"quickapp" : {},
|
||||
/* 快应用特有相关 */
|
||||
"mp-weixin" : {
|
||||
/* 微信小程序特有相关 */
|
||||
"appid" : "wxfc307afa8e513504",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-alipay" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-baidu" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-toutiao" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"mp-qq" : {
|
||||
"usingComponents" : true
|
||||
},
|
||||
"_spaceID" : "639df241-3e40-451e-9e00-2cf3bf79ce41"
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
|
||||
import { mapGetters } from 'vuex'
|
||||
export default {
|
||||
|
||||
onLoad(option) {
|
||||
// #ifdef H5
|
||||
|
||||
// #endif
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['isLogin', 'news_num']),
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
import store from '@/store'
|
||||
|
||||
|
||||
const whiteList = [
|
||||
'/pages/login/index'
|
||||
]
|
||||
|
||||
export default {
|
||||
created() {
|
||||
|
||||
// 登录判断跳转
|
||||
this.$u.routeAuth = (...args) => {
|
||||
let url = args[0]
|
||||
if (typeof url !== 'string' && typeof url.url === 'string') {
|
||||
url = url.url
|
||||
}
|
||||
|
||||
if (store.getters.isLogin || whiteList.includes(url)) {
|
||||
this.$u.route(...args)
|
||||
} else {
|
||||
this.$u.route('/pages/login/index')
|
||||
}
|
||||
}
|
||||
|
||||
this.$u.routeLogin = (...args) => {
|
||||
uni.$u.throttle(this.$u.route('/pages/login/index'), 1000)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
{
|
||||
"pages": [{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "门店管理"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/select_store/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择门店",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"path": "pages/select_product/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择商品",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
|
||||
},
|
||||
{
|
||||
"path": "pages/user_order/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "用户提货"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/code/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "二维码",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/order_details/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "订单详情",
|
||||
"enablePullDownRefresh": false,
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/user_select_pro/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "选择商品",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
}, {
|
||||
"path": "pages/login/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "登录",
|
||||
"enablePullDownRefresh": false
|
||||
}
|
||||
|
||||
}
|
||||
],
|
||||
"globalStyle": {
|
||||
"navigationBarTextStyle": "black",
|
||||
"navigationBarTitleText": "uni-app",
|
||||
"navigationBarBackgroundColor": "#ffffff"
|
||||
},
|
||||
"easycom": {
|
||||
"^u-(.*)": "uview-ui/components/u-$1/u-$1.vue"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<view class="flex items-center justify-center flex-col pt-80rpx">
|
||||
<image class="w-400rpx h-400rpx" src="http://zcs-test.oss-cn-chengdu.aliyuncs.com/product-spus/cover/2022-04-22/f6f96cba9ba86197e58ea3c2ce93b2a1.jpg" mode="scaleToFill"></image>
|
||||
<view class="mt-20rpx">请用户通过微信扫码进入小程序生成订单</view>
|
||||
<view class="grid grid-cols-2 gap-x-80rpx text-36rpx mt-120rpx">
|
||||
<view @click="jump">门店管理</view>
|
||||
<view>个人中心</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
methods:{
|
||||
jump(){
|
||||
uni.reLaunch({
|
||||
url:'/pages/index/index'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
page{
|
||||
background: #ffffff;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
|
||||
</style>
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
<template>
|
||||
<view class="px-80rpx pt-60rpx text-white text-30rpx font-extrabold">
|
||||
<view class="grid grid-cols-2 gap-x-80rpx gap-y-40rpx">
|
||||
<view @click="$u.route('/pages/select_store/index')" class="relative bg-hex-f39c12 bg-opacity-60" style="padding-top: 100%">
|
||||
<view class="absolute left-0 w-full top-0 h-full flex items-center justify-center flex-col">
|
||||
<view >帮用户下单</view>
|
||||
</view>
|
||||
</view>
|
||||
<view @click="$u.route('/pages/user_order/index')" class="relative bg-hex-dd4b39 bg-opacity-60" style="padding-top: 100%">
|
||||
<view class="absolute left-0 w-full top-0 h-full flex items-center justify-center flex-col">
|
||||
<view>用户提货</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<template>
|
||||
<view class="flex flex-col w-full min-h-full">
|
||||
<view class="flex-1 pt-300rpx">
|
||||
<view class="px-45rpx ">
|
||||
<button
|
||||
hover-class="none"
|
||||
class="cu-btn bg-primary text-center h-80rpx rounded-full leading-80rpx text-white text-lg mt-40rpx"
|
||||
open-type="getPhoneNumber"
|
||||
@getphonenumber="getPhoneNumber"
|
||||
>
|
||||
微信一键登录
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import { getWxCode } from '@/utils/login';
|
||||
import { mapMutations } from 'vuex';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
check: false,
|
||||
code:'',
|
||||
};
|
||||
},
|
||||
async onLoad() {
|
||||
},
|
||||
methods: {
|
||||
...mapMutations({
|
||||
LOGIN: 'user/LOGIN',
|
||||
}),
|
||||
|
||||
//微信快捷登录
|
||||
async getPhoneNumber(e) {
|
||||
const { encryptedData, iv } = e.detail;
|
||||
if(!iv) return
|
||||
const invite_code = uni.getStorageSync('INVITE_CODE');
|
||||
// const code = await getWxCode();
|
||||
const params = {}
|
||||
if(!!invite_code) params.inviter_code = invite_code
|
||||
const resData = await this.$api.post('/v1/socialite/code-bind-user/wechat-mini', {
|
||||
type: 'wechat-mini',
|
||||
code:this.code,
|
||||
iv,
|
||||
data: encryptedData,
|
||||
...params
|
||||
});
|
||||
this.LOGIN(resData.token);
|
||||
uni.navigateBack({
|
||||
delta: 1,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style>
|
||||
page {
|
||||
background: #fff;
|
||||
height: 100%;
|
||||
}
|
||||
.borderTop {
|
||||
border-top-left-radius: 15rpx;
|
||||
border-top-right-radius: 15rpx;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,113 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<loading-view v-if="isFirstLoading"></loading-view>
|
||||
<u-navbar>
|
||||
<view class="text-center w-full ">
|
||||
<block v-if="detail.status >= 0">
|
||||
<view class="text-lg font-medium">{{ detail.status | payStatusText }}</view>
|
||||
<view class="text-xs" v-if="detail.status == 0">
|
||||
<u-count-down :timestamp="detail.expires_at" font-size="20" separator="zh" separator-size="20"
|
||||
@end="onCountEnd"></u-count-down>
|
||||
</view>
|
||||
</block>
|
||||
<view v-else class="text-lg font-medium">订单详情</view>
|
||||
</view>
|
||||
</u-navbar>
|
||||
<view class="bg-white mt-20rpx py-base px-40rpx">
|
||||
<block v-for="(item, index) in products" :key="index">
|
||||
<view @tap="$u.routeAuth('/pages/product_details/index', { skuId: index})" class="mb-base">
|
||||
<view class="flex">
|
||||
<u-image border-radius="10" width="190rpx" height="190rpx"
|
||||
src="http://zcs-test.oss-cn-chengdu.aliyuncs.com/product-spus/cover/2022-04-22/f6f96cba9ba86197e58ea3c2ce93b2a1.jpg"
|
||||
:lazy-load="true"></u-image>
|
||||
<view class="flex-1 ml-16rpx w-0 flex justify-between flex-col">
|
||||
<view>
|
||||
<view>
|
||||
<view class="line-2">商品名称商品名称商品名称商品商品名称商品名称商品名称商品商品名称商品名称商品名称商品</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex items-end">
|
||||
<view class="flex-1">
|
||||
<view class="">
|
||||
<view class="flex-1 price-text">390</view>
|
||||
</view>
|
||||
<view class="flex items-center" >
|
||||
<view class="price-text">300</view>
|
||||
<image class="w-58rpx h-58rpx ml-10rpx" src="/static/images/svip.svg" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-lg"> x20</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="my-20rpx" v-if="index != products.length - 1">
|
||||
<u-line color="#E5E5E5" />
|
||||
</view>
|
||||
</block>
|
||||
<view class="mt-20rpx">
|
||||
<view class="h-50rpx leading-50rpx flex justify-between">
|
||||
<text class="text-black">商品金额</text>
|
||||
<text class="price-text">390</text>
|
||||
</view>
|
||||
<view class="h-50rpx leading-50rpx flex justify-between">
|
||||
<text class="text-black">运费</text>
|
||||
<text class="price-text">0</text>
|
||||
</view>
|
||||
</view>
|
||||
<u-line color="#E5E5E5" />
|
||||
<view class="flex justify-end mt-base">
|
||||
<text class="text-lg text-black">实付金额:</text>
|
||||
<text class="text-warning text-xl ">¥999</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="bg-white mt-20rpx py-base px-40rpx">
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="h-50rpx leading-50rpx text-md text-black">订单编号:12345678912345678</view>
|
||||
<view @tap="copy(detail.sn)" class="border border-solid border-hex-A6A6A6 rounded-full px-base py-6rpx">复制</view>
|
||||
</view>
|
||||
<view class="h-50rpx leading-50rpx text-md text-black">下单时间:2022-12-12</view>
|
||||
<view class="h-50rpx leading-50rpx text-md text-black" >支付时间:2022-12-12</view>
|
||||
</view>
|
||||
<!-- 底部导航栏 -->
|
||||
<block>
|
||||
<view class="h-120rpx"></view>
|
||||
<view class="fixed bg-white bottom-0 w-full px-40rpx">
|
||||
<view class="flex justify-end h-100rpx items-center">
|
||||
<view class="border border-hex-A6A6A6 border-solid rounded-full px-20rpx py-10rpx ml-base">取消订单</view>
|
||||
<view class="border border-hex-A6A6A6 border-solid rounded-full px-20rpx py-10rpx ml-base" >立即付款</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import uniCopy from '@/utils/uni-copy';
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
isFirstLoading: false,
|
||||
products: [{}, {}, {}],
|
||||
detail: {}
|
||||
};
|
||||
},
|
||||
|
||||
methods: {
|
||||
//复制
|
||||
copy(val) {
|
||||
uniCopy({
|
||||
content: val ?? '',
|
||||
success: (res) => {
|
||||
this.$u.toast(res);
|
||||
},
|
||||
error: (e) => {
|
||||
this.$u.toast(e);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
@ -1,291 +0,0 @@
|
|||
<template>
|
||||
<view class="w-full">
|
||||
<u-popup v-model="show" mode="bottom" border-radius="24">
|
||||
<view class="max-h-500rpx bg-txBorder">
|
||||
<view class="pt-20rpx text-26rpx text-center">分享</view>
|
||||
<view class="grid grid-cols-2 text-txGray text-26rpx mt-40rpx px-30rpx">
|
||||
<!-- <view @tap="shareApp(0)" class="flex items-center justify-center flex-col">
|
||||
<view class="w-110rpx h-110rpx flex items-center justify-center flex-col rounded-15rpx bg-hex-00dd00 ">
|
||||
<image class="w-66rpx h-66rpx" src="/static/images/share/wechat-friends.png" mode=""></image>
|
||||
</view>
|
||||
<view class="mt-15rpx">微信好友</view>
|
||||
</view>
|
||||
<view @tap="shareApp(1)" class="flex items-center justify-center flex-col">
|
||||
<view class="w-110rpx h-110rpx flex items-center justify-center flex-col rounded-15rpx bg-hex-ffffff">
|
||||
<image class="w-66rpx h-66rpx" src="/static/images/share/wechat-moments.png" mode=""></image>
|
||||
</view>
|
||||
<view class="mt-15rpx">朋友圈</view>
|
||||
</view> -->
|
||||
<view @tap="shareApplet" class="flex items-center justify-center flex-col">
|
||||
<view class="w-110rpx h-110rpx flex items-center justify-center flex-col rounded-full bg-hex-00dd00">
|
||||
<image class="w-66rpx h-66rpx" src="/static/images/share/wechat-friends.png" mode=""></image>
|
||||
</view>
|
||||
<view class="mt-15rpx">小程序</view>
|
||||
</view>
|
||||
<view @tap="onShowImg" class="flex items-center justify-center flex-col">
|
||||
<view class="w-110rpx h-110rpx rounded-full flex items-center justify-center flex-col bg-hex-fa3534">
|
||||
<image class="w-66rpx h-66rpx" src="/static/images/share/code.png" mode=""></image>
|
||||
</view>
|
||||
<view class="mt-15rpx">二维码海报</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="px-50rpx py-60rpx">
|
||||
<view @tap="show = false" class="rounded-full bg-white text-center h-66rpx leading-66rpx text-txBase text-32rpx">取消</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-popup>
|
||||
<!-- 合成的图片显示-->
|
||||
<u-mask :show="imgShow" @tap="imgShow = false" :zoom="false">
|
||||
<view class="flex justify-center items-center h-full">
|
||||
<view class="w1-7">
|
||||
<view class="w-full" @tap.stop="">
|
||||
<image class="w-full" :src="url" mode="widthFix"/>
|
||||
</view>
|
||||
<view @tap.stop="saveImage" class="rounded-15rpx mt-20rpx w-full bg-primary text-center h-66rpx leading-66rpx text-white text-32rpx">保存图片到相册</view>
|
||||
</view>
|
||||
</view>
|
||||
</u-mask>
|
||||
<canvas
|
||||
class="hideCanvas"
|
||||
canvas-id="default_PosterCanvasId"
|
||||
:style="{ width: (poster.width || 0) + 'px', height: (poster.height || 0) + 'px' }"
|
||||
></canvas>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { getSharePoster } from '@/components/QS-SharePoster/QS-SharePoster.js';
|
||||
export default {
|
||||
props: {
|
||||
value: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
goods: {
|
||||
type: Object,
|
||||
default: () => {},
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
show: {
|
||||
get() {
|
||||
return this.value;
|
||||
},
|
||||
set(val) {
|
||||
this.$emit('input', val);
|
||||
},
|
||||
},
|
||||
user() {
|
||||
return this.$store.getters.user ?? {};
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
imgShow: false,
|
||||
code: '',
|
||||
poster: {},
|
||||
url: '',
|
||||
};
|
||||
},
|
||||
mounted() {},
|
||||
methods: {
|
||||
//获取二维码
|
||||
async getCode() {
|
||||
let res = await this.$api.get(`/v1/product/products/${this.goods.id}/share`);
|
||||
this.code = res.image;
|
||||
},
|
||||
//保存图片
|
||||
saveImage() {
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: this.url,
|
||||
success: (res) => {
|
||||
this.$u.toast('保存图片成功');
|
||||
},
|
||||
complete: (cop) => {
|
||||
this.imgShow = false;
|
||||
},
|
||||
});
|
||||
},
|
||||
//
|
||||
async onShowImg() {
|
||||
this.show = false;
|
||||
this.$u.throttle(async () => {
|
||||
await this.getCode();
|
||||
await this.shareFc();
|
||||
}, 1000);
|
||||
},
|
||||
//合成图片
|
||||
async shareFc() {
|
||||
uni.showLoading({
|
||||
title: '图片合成中...',
|
||||
});
|
||||
let t = this;
|
||||
try {
|
||||
const d = await getSharePoster({
|
||||
_this: this, //若在组件中使用 必传
|
||||
type: 'testShareType',
|
||||
background: {
|
||||
height: 600, //画布高度
|
||||
width: 385, //画布宽度
|
||||
backgroundColor: '#ffffff', //画布背景颜色
|
||||
},
|
||||
setCanvasWH({ bgObj }) {
|
||||
//一般必传, 动态设置canvas宽高
|
||||
_this.poster = bgObj;
|
||||
},
|
||||
posterCanvasId: 'default_PosterCanvasId', //canvasId
|
||||
delayTimeScale: 2, //延时系数
|
||||
drawArray: ({ setBgObj, getBgObj, type, bgScale }) => {
|
||||
return new Promise((rs, rj) => {
|
||||
rs([
|
||||
{
|
||||
type: 'image',
|
||||
url: t.goods.cover,
|
||||
mode: 'aspectFill',
|
||||
infoCallBack(imageInfo) {
|
||||
return {
|
||||
dx: 0,
|
||||
dy: 0,
|
||||
dWidth: 385,
|
||||
dHeight: 385,
|
||||
};
|
||||
},
|
||||
}, //顶部图片
|
||||
{
|
||||
type: 'text',
|
||||
text: t.goods.name,
|
||||
size: '20',
|
||||
color: '#000000',
|
||||
fontWeight: 'bold',
|
||||
lineFeed: {
|
||||
maxWidth: 220,
|
||||
lineHeight: 30,
|
||||
},
|
||||
infoCallBack(imageInfo) {
|
||||
return {
|
||||
dx: 15,
|
||||
dy: 440,
|
||||
};
|
||||
},
|
||||
}, //商品名称
|
||||
{
|
||||
type: 'text',
|
||||
text: `¥${t.goods.market_price}`,
|
||||
size: '20',
|
||||
color: '#fa3534',
|
||||
fontWeight: 'bold',
|
||||
infoCallBack(imageInfo) {
|
||||
return {
|
||||
dx: 15,
|
||||
dy: 520,
|
||||
};
|
||||
},
|
||||
}, //价格
|
||||
{
|
||||
type: 'text',
|
||||
text: '扫描或长按识别二维码',
|
||||
size: '12',
|
||||
color: '#383838',
|
||||
infoCallBack(imageInfo) {
|
||||
return {
|
||||
dx: 260,
|
||||
dy: 540,
|
||||
};
|
||||
},
|
||||
}, //简介
|
||||
{
|
||||
type: 'image',
|
||||
url: t.code,
|
||||
//url: 'https://img0.baidu.com/it/u=2711835743,3110561022&fm=253&fmt=auto&app=138&f=JPEG?w=344&h=344',
|
||||
infoCallBack(imageInfo) {
|
||||
return {
|
||||
dx: 265,
|
||||
dy: 400,
|
||||
dWidth: 110,
|
||||
dHeight: 110,
|
||||
};
|
||||
},
|
||||
}, //二维码
|
||||
]);
|
||||
});
|
||||
},
|
||||
setCanvasWH: ({ bgObj, type, bgScale }) => {
|
||||
// 为动态设置画布宽高的方法,
|
||||
t.poster = bgObj;
|
||||
},
|
||||
});
|
||||
t.url = d.poster.tempFilePath;
|
||||
this.imgShow = true;
|
||||
uni.hideLoading();
|
||||
} catch (e) {
|
||||
uni.hideLoading();
|
||||
this.$u.toast('网络繁忙,请稍后重试');
|
||||
}
|
||||
},
|
||||
//app分享微信下载地址
|
||||
shareApp(val) {
|
||||
this.show = false;
|
||||
let code = this.user.code ? this.user.code : '';
|
||||
let scene = val == 0 ? 'WXSceneSession' : 'WXSceneTimeline';
|
||||
uni.share({
|
||||
provider: 'weixin',
|
||||
scene: scene,
|
||||
type: 0,
|
||||
href: `https://wap.zichunsheng.cn/register?code=${code}`,
|
||||
title: this.goods.name,
|
||||
summary: '我正在使用子春生APP,赶紧跟我一起来体验吧!',
|
||||
imageUrl: '/static/images/share/logs.png',
|
||||
success: (res) => {},
|
||||
});
|
||||
},
|
||||
// app分享小程序
|
||||
shareApplet(val) {
|
||||
this.show = false;
|
||||
let code = this.user.code ? this.user.code : '';
|
||||
uni.share({
|
||||
provider: 'weixin',
|
||||
scene: 'WXSceneSession',
|
||||
type: 5,
|
||||
title: this.goods.name,
|
||||
// summary: '我正在使用子春生APP,赶紧跟我一起来体验吧!',
|
||||
// imageUrl: this.goods.cover,
|
||||
imageUrl: `${this.goods.cover}?x-oss-process=image/resize,w_800,h_800`,
|
||||
miniProgram: {
|
||||
id: 'gh_261a68e68e45',
|
||||
path: `/pages/product_details/index?skuId=${this.goods.id}&invite_code=${code}`,
|
||||
type: 0,
|
||||
webUrl: `https://wap.zichunsheng.cn/register?code=${code}`,
|
||||
},
|
||||
success: (res) => {
|
||||
console.log(res);
|
||||
},
|
||||
fail: (err) => {
|
||||
console.log(err);
|
||||
},
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.warp {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
width: 70%;
|
||||
flex-direction: column;
|
||||
margin: 0 auto;
|
||||
}
|
||||
.w1-7{
|
||||
width: 70%;
|
||||
}
|
||||
.hideCanvas {
|
||||
position: fixed;
|
||||
top: -9999999px;
|
||||
left: -9999999px;
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,606 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<!-- 导航栏 -->
|
||||
<!-- <u-navbar back-icon-color="#000000" :border-bottom="false" :title="title">
|
||||
<template #right>
|
||||
<view class="mx-base relative" @tap="$u.route(`/pages/web_view/index?url=${service}`)">
|
||||
<u-icon name="chat" size="48"></u-icon>
|
||||
<block v-if="news_num > 0">
|
||||
<u-badge :is-dot="true" size="16" :offset="[0, 2]" class="badge"></u-badge>
|
||||
</block>
|
||||
</view>
|
||||
</template>
|
||||
</u-navbar> -->
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<!-- <view class="mt-750rpx"></view> -->
|
||||
<!-- #endif -->
|
||||
<view class="u-skeleton">
|
||||
<block v-if="isFirstLoading">
|
||||
<view class="w-750rpx h-750rpx u-skeleton-rect"></view>
|
||||
<view class="px-24rpx">
|
||||
<view class="w-600rpx h-44rpx u-skeleton-rect mt-20rpx"></view>
|
||||
<view class="w-333rpx h-36rpx u-skeleton-rect mt-20rpx"></view>
|
||||
<view class="flex justify-between items-center">
|
||||
<view class="w-142rpx h-44rpx u-skeleton-rect mt-20rpx"></view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
|
||||
<block>
|
||||
<template v-if="!isNull">
|
||||
<!-- banner图 -->
|
||||
<!-- <product-swiper :list="bannerList" :imgUrls="images" :video="detail.media"></product-swiper> -->
|
||||
|
||||
<cu-swiper v-if="!playShow" indicator-pos="bottomRight" mode="number" height="750" border-radius="0" :list="bannerList">
|
||||
<template slot-scope="scope">
|
||||
<view class="w-full h-full relative">
|
||||
<image
|
||||
v-if="scope.data.type == 'video'"
|
||||
class="w-90rpx h-90rpx translate-center z-40"
|
||||
src="/static/images/icon_play.png"
|
||||
@tap="playVideo"
|
||||
/>
|
||||
<image class="w-full h-full" :src="`${scope.data.image}?x-oss-process=image/resize,w_750/quality,q_90`" mode="aspectFill" />
|
||||
</view>
|
||||
</template>
|
||||
</cu-swiper>
|
||||
<view class="relative" v-else>
|
||||
<!-- <cover-view>
|
||||
<u-icon class="absolute right-30rpx top-30rpx z-40" color="#fff" name="close" @tap="pauseVideo"></u-icon>
|
||||
</cover-view> -->
|
||||
|
||||
<video
|
||||
object-fit="object-fit"
|
||||
class="w-750rpx h-750rpx"
|
||||
:enable-progress-gesture="false"
|
||||
id="myVideo"
|
||||
v-if="media.length"
|
||||
:src="`${media[0]}`"
|
||||
controls
|
||||
>
|
||||
<cover-image
|
||||
@click="pauseVideo"
|
||||
class="absolute right-30rpx top-30rpx w-60rpx h-60rpx"
|
||||
src="/static/images/app/app_update_close.png"
|
||||
></cover-image>
|
||||
</video>
|
||||
</view>
|
||||
|
||||
<!-- -->
|
||||
<view class="bg-white px-41rpx border-b border-txBorder pb-30rpx">
|
||||
<view class="text-txBase text-lg pt-base">{{ detail.name }}</view>
|
||||
<view class="flex items-center mt-10rpx">
|
||||
<view class="text-md text-txGray">{{ detail.subtitle }}</view>
|
||||
<view class="px-13rpx bg-primary bg-opacity-20 text-md rounded-10rpx text-primary" v-if="detail.sales_value"
|
||||
>成长值:{{ detail.sales_value }}</view
|
||||
>
|
||||
</view>
|
||||
<view class="flex items-center justify-between">
|
||||
<view class="text-txBase text-md flex items-center">
|
||||
<view>¥{{ detail.sell_price }}</view>
|
||||
<block v-if="showShrough == 'true'">
|
||||
<view class="fontFam line-through text-txGray ml-40rpx text-20rpx">¥{{ detail.market_price }}</view>
|
||||
</block>
|
||||
</view>
|
||||
<view class="flex items-center mt-base">
|
||||
<!-- <image class="w-36rpx h-36rpx" src="/static/images/user/collection.png" mode=""></image> -->
|
||||
<u-icon
|
||||
:color="products.is_collected ? '#FC8720' : ''"
|
||||
size="36"
|
||||
:name="products.is_collected ? 'star-fill' : 'star'"
|
||||
@tap="onCollection"
|
||||
></u-icon>
|
||||
<view class="text-txBase text-md ml-10rpx leading-0">收藏</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex items-center -mt-10rpx">
|
||||
<view class="text-lg -mt-5rpx text-txSvip">¥{{ detail.vip_price }}</view>
|
||||
<image class="w-58rpx h-58rpx ml-10rpx" src="/static/svg/svip.svg" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
<!-- -->
|
||||
<view class="flex items-center justify-between bg-white px-41rpx py-32rpx" @tap="showSpecFun(0)" v-if="specs.length">
|
||||
<view class="flex items-center text-txBase text-md">
|
||||
<view class="font-extrabold">选择</view>
|
||||
<view class="ml-base">{{ activeSkuText }}</view>
|
||||
</view>
|
||||
<view>
|
||||
<u-icon name="arrow-right" color="#808080" size="32"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 拼团 begin -->
|
||||
<!-- <view class="px-45rpx mt-20rpx bg-white">
|
||||
<view class="flex justify-between py-20rpx">
|
||||
<view>2人在拼单,可直接参与</view>
|
||||
<view>更多</view>
|
||||
</view>
|
||||
<u-line></u-line>
|
||||
<view class="flex items-center h-110rpx">
|
||||
<view class="rounded-full w-90rpx h-90rpx bg-rose-100"></view>
|
||||
<view class="ml-30rpx flex-1">名字</view>
|
||||
<view class="text-center w-200rpx">
|
||||
<view class="text-24rpx">
|
||||
<text>差</text>
|
||||
<text class="text-hex-e81300 text-30rpx">1</text>
|
||||
<text>人成团</text>
|
||||
</view>
|
||||
<view class="text-20rpx text-gray-400 mt-10rpx">还剩23:00:00</view>
|
||||
</view>
|
||||
<view>
|
||||
<view class="bg-hex-e81300 rounded-8rpx px-40rpx h-60rpx leading-60rpx text-white text-30rpx">去拼团</view>
|
||||
</view>
|
||||
</view>
|
||||
<u-line></u-line>
|
||||
<view class="flex items-center h-110rpx">
|
||||
<view class="rounded-full w-90rpx h-90rpx bg-rose-100"></view>
|
||||
<view class="ml-30rpx flex-1">名字</view>
|
||||
<view class="text-center w-200rpx">
|
||||
<view class="text-24rpx">
|
||||
<text>差</text>
|
||||
<text class="text-hex-e81300 text-30rpx">1</text>
|
||||
<text>人成团</text>
|
||||
</view>
|
||||
<view class="text-20rpx text-gray-400 mt-10rpx">还剩23:00:00</view>
|
||||
</view>
|
||||
<view>
|
||||
<view class="bg-hex-e81300 rounded-8rpx px-40rpx h-60rpx leading-60rpx text-white text-30rpx">去拼团</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- <view class="px-45rpx mt-20rpx bg-white">
|
||||
<view class="flex py-20rpx">
|
||||
<view>我的拼团</view>
|
||||
<view class="ml-60rpx">还差2人成团</view>
|
||||
</view>
|
||||
<view class="flex pb-20rpx items-center">
|
||||
<view class="flex rtl">
|
||||
<view class="rounded-full w-90rpx h-90rpx bg-hex-cacaca border border-4rpx border-white box-content flex items-center justify-center">
|
||||
<u-icon name="plus" color="#ffffff" size="40"></u-icon>
|
||||
</view>
|
||||
<view class="rounded-full w-90rpx h-90rpx bg-rose-100 -ml-30rpx border border-4rpx border-white box-content"></view>
|
||||
<view class="rounded-full w-90rpx h-90rpx bg-rose-100 -ml-30rpx border border-4rpx border-white box-content"></view>
|
||||
<view class="rounded-full w-90rpx h-90rpx bg-rose-100 border border-4rpx border-white box-content"></view>
|
||||
</view>
|
||||
<view class="flex-1"></view>
|
||||
<view class="">
|
||||
<view class="bg-hex-e81300 rounded-8rpx px-40rpx h-60rpx leading-60rpx text-white text-30rpx">邀请好友</view>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- 拼团 end -->
|
||||
<!-- 砍价 begin-->
|
||||
<view class="px-45rpx mt-20rpx bg-white" v-if="detail.is_bargaing">
|
||||
<view class="flex py-20rpx"> 邀请好友助力 </view>
|
||||
<view class="flex pb-20rpx items-center">
|
||||
<view class="flex rtl">
|
||||
<view class="rounded-full w-90rpx h-90rpx bg-hex-cacaca border border-4rpx border-white box-content flex items-center justify-center">
|
||||
<u-icon name="plus" color="#ffffff" size="40"></u-icon>
|
||||
</view>
|
||||
<view
|
||||
class="rounded-full w-90rpx h-90rpx bg-rose-100 border border-4rpx border-white box-content"
|
||||
v-for="(item, index) in bargainLogs"
|
||||
:key="index"
|
||||
:class="{ '-ml-30rpx': index != bargainLogs.length - 1 }"
|
||||
>
|
||||
<u-avatar :size="90" :src="item.avatar"></u-avatar>
|
||||
</view>
|
||||
</view>
|
||||
<view class="flex-1"></view>
|
||||
<view class="">
|
||||
<view
|
||||
class="bg-hex-e81300 rounded-8rpx px-40rpx h-60rpx leading-60rpx text-white text-30rpx"
|
||||
@click="$u.routeAuth('/pages/bargain/invite', { skuId: skuId })"
|
||||
>邀请好友</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 砍价 end -->
|
||||
<!-- 特点 -->
|
||||
<!-- v-if="features.length" -->
|
||||
<view v-if="features.length" class="px-30rpx bg-white">
|
||||
<view class="pt-32rpx flex">
|
||||
<view class="flex-none text-32rpx">服务:</view>
|
||||
<ul class="">
|
||||
<li class="float-left mr-8rpx" v-for="(item, index) in features" :key="index">
|
||||
<img class="w-28rpx float-left my-4rpx mr-6rpx" :src="item.icon" alt="" srcset="" /> {{ item.name }}
|
||||
</li>
|
||||
</ul>
|
||||
<view class="flex-1 flex flex-wrap">
|
||||
<view class="flex items-start ml-10rpx" v-for="(item, index) in features" :key="index"> </view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <swiper class="h-78rpx" :indicator-dots="false" :autoplay="true" vertical>
|
||||
<swiper-item v-for="(item, index) in features" :key="index">
|
||||
<view class="flex items-center h-full px-46rpx">
|
||||
<image class="w-32rpx h-32rpx" :src="item.icon" mode=""></image>
|
||||
<view class="text-md text-txGray ml-10rpx">{{ item.name }}</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper> -->
|
||||
</view>
|
||||
|
||||
<!-- 评价 -->
|
||||
<!-- <view class="bg-white">
|
||||
<view class="border-b border-txBorder flex items-center justify-between py-32rpx px-41rpx text-txBase text-md">
|
||||
<view class="font-extrabold">评价(109)</view>
|
||||
<view class="flex items-center" @tap="jump('/comment/comment')">
|
||||
<view class="text-txGray mr-10rpx">查看全部</view>
|
||||
<u-icon name="arrow-right" color="#808080" size="32"></u-icon>
|
||||
</view>
|
||||
</view>
|
||||
<view v-for="(item, index) in 1" :key="index">
|
||||
<comment :index="index" :length="3"></comment>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- -->
|
||||
<view class="flex items-center justify-center py-32rpx">
|
||||
<view class="w-25rpx h-1rpx bg-txBase"></view>
|
||||
<view class="text-xl font-extrabold text-txBase mx-10rpx">商品详情</view>
|
||||
<view class="w-25rpx h-1rpx bg-txBase"></view>
|
||||
</view>
|
||||
<!-- 富文本 -->
|
||||
<view class="bg-white">
|
||||
<view class="p-base" v-if="attrs.length">
|
||||
<u-table align="left" bg-color="transparent">
|
||||
<block v-for="(item, index) in attrs" :key="index">
|
||||
<u-tr class="u-tr">
|
||||
<u-td class="u-td">{{ item.name }}</u-td>
|
||||
</u-tr>
|
||||
<u-tr class="u-tr" v-for="(item1, index2) in item.attrs" :key="`${index2}_${index}_attrs`">
|
||||
<u-td width="160rpx" class="u-td">{{ item1.name }}</u-td>
|
||||
<u-td class="u-td">{{ item1.value }}</u-td>
|
||||
</u-tr>
|
||||
</block>
|
||||
</u-table>
|
||||
</view>
|
||||
<u-parse :html="detailHtml"></u-parse>
|
||||
</view>
|
||||
<!-- 购物须知 -->
|
||||
<view class="mt-base bg-white">
|
||||
<!-- <view class="w-710rpx m-auto bg-txBorder px-base pb-base rounded-10rpx"> -->
|
||||
<!-- <view class="text-lg text-txBase font-extrabold pt-10rpx">购物须知</view> -->
|
||||
<view class="mt-10rpx text-md text-txGray">
|
||||
<u-parse :html="detail.buynote"></u-parse>
|
||||
</view>
|
||||
<!-- </view> -->
|
||||
</view>
|
||||
|
||||
<!-- 分割 -->
|
||||
<view class="h-140rpx"></view>
|
||||
<!-- 底部导航栏 -->
|
||||
<view class="flex items-center fixed right-0 left-0 bottom-0 bottom w-full h-110rpx bg-white shadow">
|
||||
<view class="w-288rpx flex justify-around pl-20rpx">
|
||||
<view class="flex items-center justify-center h-full flex-col" @tap="$u.routeAuth('/pages/auxiliary_cart/index')">
|
||||
<view class="relative w-48rpx h-48rpx flex justify-center items-center">
|
||||
<image class="w-full h-full" src="/static/images/cart/short-cart.png" mode="scaleToFill" />
|
||||
<view
|
||||
v-if="cartList.length"
|
||||
class="absolute -right-24rpx -top-10rpx leading-36rpx text-sm text-bgSubtitle bg-white text-center w-36rpx h-36rpx border border-bgSubtitle rounded-full"
|
||||
>
|
||||
{{ cartList.length }}
|
||||
</view>
|
||||
</view>
|
||||
<view class="text-26rpx mt-10rpx">购物车</view>
|
||||
</view>
|
||||
<view class="flex items-center justify-center flex-col h-full" @tap="$u.route(`/pages/web_view/index?url=${service}`)">
|
||||
<view class="w-48rpx h-48rpx flex justify-center items-center">
|
||||
<u-icon name="kefu-ermai" color="#000" size="48"></u-icon>
|
||||
</view>
|
||||
<view class="text-26rpx mt-10rpx">客服</view>
|
||||
</view>
|
||||
<!-- #ifdef APP-PLUS -->
|
||||
<!-- 增加需求--分享 -->
|
||||
<view @tap="getShare" class="flex items-center justify-center flex-col h-full">
|
||||
<view class="w-48rpx h-48rpx flex justify-center items-center">
|
||||
<u-icon name="share" color="#000" size="48"></u-icon>
|
||||
</view>
|
||||
<view class="text-26rpx mt-10rpx">分享</view>
|
||||
</view>
|
||||
<!-- -->
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
<view class="flex items-center justify-center flex-1">
|
||||
<view class="bg-primary rounded-lg w-180rpx h-66rpx text-center leading-66rpx text-md text-white" @tap="showSpecFun(1)"
|
||||
>加入购物车</view
|
||||
>
|
||||
<view class="ml-base bg-bgSubtitle rounded-lg w-180rpx h-66rpx text-center leading-66rpx text-md text-white" @tap="showSpecFun(2)"
|
||||
>立即购买</view
|
||||
>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<template v-else>
|
||||
<view class="pt-140rpx text-center">
|
||||
<view class="">该商品已下架或不存在,去逛逛别的吧~</view>
|
||||
</view>
|
||||
</template>
|
||||
</block>
|
||||
</view>
|
||||
|
||||
<u-skeleton :loading="isFirstLoading" :animation="true" bgColor="#FFF"></u-skeleton>
|
||||
|
||||
<spec-popup
|
||||
:loading="loading"
|
||||
:specs="specs"
|
||||
:sku="detail"
|
||||
v-model="show"
|
||||
:quota-used="number"
|
||||
:show-cart="popupType == 1 || popupType == 0"
|
||||
:show-buy="popupType == 2 || popupType == 0"
|
||||
:show-confirm="popupType == 3"
|
||||
@stepper-change="onChangeNumber"
|
||||
@sku-selected="onChangeSku"
|
||||
@buy-clicked="onBuy"
|
||||
@add-cart="onAddCart"
|
||||
>
|
||||
</spec-popup>
|
||||
<!-- 分享弹窗 -->
|
||||
<sharePopup v-model="shareShow" :goods="detail"></sharePopup>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import comment from '@/components/comment/comment';
|
||||
import sharePopup from './components/share-popup.vue';
|
||||
import { mapGetters, mapActions } from 'vuex';
|
||||
export default {
|
||||
components: {
|
||||
comment,
|
||||
sharePopup,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
bargainDetail: {},
|
||||
showShrough: false, //是否显示店铺直通车才显示删除的市场价格
|
||||
// service: process.env.VUE_APP_SERVICE,
|
||||
isFirstLoading: true,
|
||||
isNull: false,
|
||||
loading: false,
|
||||
skuId: '',
|
||||
number: 1,
|
||||
products: {},
|
||||
show: false,
|
||||
popupType: '',
|
||||
goodsError: '',
|
||||
title: '',
|
||||
playShow: false,
|
||||
shareShow: false, //分享弹窗
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters(['cartList']),
|
||||
user() {
|
||||
return this.$store.getters.user ?? {};
|
||||
},
|
||||
service() {
|
||||
const clien = process.env.VUE_APP_SERVICE;
|
||||
const user = this.$store.getters.user ?? {};
|
||||
const phone = user?.phone;
|
||||
const name = user?.nickname ?? phone ?? '访客';
|
||||
return encodeURIComponent(`${clien}?name=${name}&phone=${phone}&id=${phone}`);
|
||||
},
|
||||
bargainLogs() {
|
||||
return this.bargainDetail?.logs?.reverse() ?? [];
|
||||
},
|
||||
detail() {
|
||||
return this.products?.sku ?? {};
|
||||
},
|
||||
//商品特点
|
||||
features() {
|
||||
return this.products?.features ?? [];
|
||||
},
|
||||
//banner图
|
||||
images() {
|
||||
return this.detail?.images ?? [];
|
||||
},
|
||||
media() {
|
||||
const media = this.detail?.media;
|
||||
return media ? [media] : [];
|
||||
},
|
||||
bannerList() {
|
||||
const imgs = this.images.map((e) => {
|
||||
return {
|
||||
type: 'image',
|
||||
image: e,
|
||||
};
|
||||
});
|
||||
const videos = this.media.map((e) => {
|
||||
return {
|
||||
type: 'video',
|
||||
image: this.detail.cover,
|
||||
video: e,
|
||||
};
|
||||
});
|
||||
return videos.concat(imgs);
|
||||
},
|
||||
|
||||
specs() {
|
||||
return this.products?.spu_specs ?? [];
|
||||
},
|
||||
attrs() {
|
||||
return this.detail?.attrs ?? [];
|
||||
},
|
||||
detailHtml() {
|
||||
return this.detail?.description ?? '';
|
||||
},
|
||||
//已选中sku
|
||||
activeSku() {
|
||||
const sku = this.specs.reduce((arr, pr) => {
|
||||
if (pr.items.some((e) => e.selected)) {
|
||||
const item = pr.items.find((e) => e.selected);
|
||||
arr.push(item);
|
||||
}
|
||||
return arr;
|
||||
}, []);
|
||||
return sku;
|
||||
},
|
||||
//选中sku文本
|
||||
activeSkuText() {
|
||||
if (this.activeSku.length) return `${this.activeSku.map((e) => e.name).join(',')};数量:${this.number}`;
|
||||
return '请选择规格';
|
||||
},
|
||||
},
|
||||
onLoad({ skuId, showShrough }) {
|
||||
if (showShrough) {
|
||||
this.showShrough = showShrough;
|
||||
}
|
||||
this.skuId = skuId;
|
||||
this.getProducts();
|
||||
this.$store.dispatch('goods/getCartList');
|
||||
},
|
||||
methods: {
|
||||
//分享
|
||||
getShare() {
|
||||
if (!this.isLogin) return this.$u.routeLogin();
|
||||
this.shareShow = true;
|
||||
},
|
||||
playVideo() {
|
||||
this.playShow = true;
|
||||
setTimeout(() => {
|
||||
const v = uni.createVideoContext('myVideo', this);
|
||||
v.play();
|
||||
}, 100);
|
||||
},
|
||||
pauseVideo() {
|
||||
const v = uni.createVideoContext('myVideo', this);
|
||||
v.pause();
|
||||
this.playShow = false;
|
||||
},
|
||||
...mapActions({
|
||||
getCartList: 'goods/getCartList',
|
||||
}),
|
||||
//动态设置导航标题
|
||||
getTitle(title) {
|
||||
this.title = title;
|
||||
uni.setNavigationBarTitle({
|
||||
title: title,
|
||||
});
|
||||
},
|
||||
|
||||
//购买
|
||||
onBuy() {
|
||||
const params = {
|
||||
goods: [
|
||||
{
|
||||
sku_id: this.skuId,
|
||||
num: this.number,
|
||||
},
|
||||
],
|
||||
};
|
||||
this.show = false;
|
||||
this.$u.routeAuth('/pages/confirm_order/confirm_order?data=' + encodeURIComponent(JSON.stringify(params)));
|
||||
},
|
||||
//添加购物车
|
||||
async onAddCart(e) {
|
||||
const { skuId, number } = this;
|
||||
try {
|
||||
await this.$store.dispatch(
|
||||
'goods/addCart',
|
||||
{
|
||||
skuId,
|
||||
number,
|
||||
},
|
||||
{
|
||||
custom: {
|
||||
loading: true,
|
||||
},
|
||||
},
|
||||
);
|
||||
this.$u.toast('添加购物车成功');
|
||||
this.show = false;
|
||||
} catch (error) {
|
||||
// console.log(error);
|
||||
}
|
||||
},
|
||||
//获取商品详情
|
||||
async getProducts() {
|
||||
try {
|
||||
this.loading = true;
|
||||
const resData = await this.$api.get(`/v1/product/products/${this.skuId}`);
|
||||
this.products = resData;
|
||||
|
||||
if (resData?.sku?.is_bargaing) {
|
||||
this.bargainOrderLog();
|
||||
}
|
||||
//动态修改导航栏
|
||||
this.getTitle(resData.sku.name);
|
||||
} catch (error) {
|
||||
if (error != 404) {
|
||||
this.isNull = true;
|
||||
}
|
||||
// console.log(error);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
this.isFirstLoading = false;
|
||||
}
|
||||
},
|
||||
//查看自己邀请的人
|
||||
async bargainOrderLog() {
|
||||
const data = await this.$api.get(`/v1/bargain-order/sku/${this.skuId}`);
|
||||
this.bargainDetail = data;
|
||||
},
|
||||
showSpecFun(type) {
|
||||
if (!this.isLogin) return this.$u.routeLogin();
|
||||
this.popupType = type;
|
||||
this.show = true;
|
||||
},
|
||||
//数量改变
|
||||
onChangeNumber(e) {
|
||||
this.number = e;
|
||||
},
|
||||
//sku改变
|
||||
onChangeSku(e) {
|
||||
this.skuId = e.sku_id;
|
||||
this.getProducts();
|
||||
},
|
||||
//收藏
|
||||
onCollection() {
|
||||
this.$u.throttle(async () => {
|
||||
if (!this.isLogin) return this.$u.routeLogin();
|
||||
if (this.products.is_collected) {
|
||||
await this.$api.post(`/v1/product/products/${this.skuId}/uncollect`);
|
||||
this.products.is_collected = false;
|
||||
this.$u.toast('取消收藏');
|
||||
} else {
|
||||
await this.$api.post(`/v1/product/products/${this.skuId}/collect`);
|
||||
this.products.is_collected = true;
|
||||
this.$u.toast('收藏成功');
|
||||
}
|
||||
}, 1000);
|
||||
},
|
||||
jump(url) {
|
||||
uni.navigateTo({
|
||||
url: '/pages' + url,
|
||||
});
|
||||
},
|
||||
//关闭弹窗
|
||||
close(e) {
|
||||
this.show = !e;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shadow {
|
||||
box-shadow: 0px -4rpx 10rpx rgba(0, 0, 0, 0.11);
|
||||
}
|
||||
|
||||
.shadowRight {
|
||||
box-shadow: 2rpx 0px 5rpx rgba(0, 0, 0, 0.12);
|
||||
}
|
||||
|
||||
.badge {
|
||||
@apply bg-badge;
|
||||
}
|
||||
.fontFam {
|
||||
/* #ifdef APP-PLUS */
|
||||
font-family: '宋体';
|
||||
/* #endif */
|
||||
}
|
||||
.rtl {
|
||||
direction: rtl;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
<template>
|
||||
<view class="py-base flex items-start">
|
||||
<u-image class="flex-none" width="190" height="190" border-radius="10" src="http://zcs-test.oss-cn-chengdu.aliyuncs.com/product-spus/cover/2022-04-22/f6f96cba9ba86197e58ea3c2ce93b2a1.jpg" :lazy-load="true">
|
||||
</u-image>
|
||||
<view class="flex-1 ml-15rpx">
|
||||
<view class="line-1 w-full text-txBase">商品名称商品名称商品名称商品名称商品名称商品名称商品名称商品名称商品名称</view>
|
||||
<view class="price-text flex-1 text-txBase text-md mt-30rpx">15元</view>
|
||||
<view class="flex items-center justify-between mt-30rpx flex-1">
|
||||
<view class="text-txBase text-md price-text">10元 vip</view>
|
||||
<view v-if="isAdd" class="bg-hex-ef4444 text-center px-20rpx py-8rpx text-white rounded-10rpx">添加</view>
|
||||
<view v-else>
|
||||
<u-number-box :min="1" :max="3" :value="1">
|
||||
</u-number-box>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props:{
|
||||
isAdd:{
|
||||
type:Boolean,
|
||||
default:false
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
<template>
|
||||
<view class="">
|
||||
<view class="flex items-center px-40rpx text-32rpx bg-white py-30rpx">
|
||||
<view class="mr-15rpx">商品信息</view>
|
||||
<u-search class="flex-1" placeholder="货号或名称" v-model="keyword" :show-action="false"></u-search>
|
||||
</view>
|
||||
<!-- 搜索的商品列表 -->
|
||||
<view class="px-30rpx bg-white " v-for="(item,index) in list1" :key="index">
|
||||
<GoodsItem :isAdd="true" />
|
||||
</view>
|
||||
<!-- 提货数量 -->
|
||||
<view class="px-30rpx bg-white mt-20rpx ">
|
||||
<view class="py-20rpx flex w-full items-start" v-for="(item,index) in list" :key="index">
|
||||
<view class="w-38rpx mt-8rpx h-38rpx rounded-full border border-hex-ef4444 border-solid text-center leading-38rpx text-hex-ef4444">{{index+1}}</view>
|
||||
<view class="flex-1 ml-15rpx">
|
||||
<view class="flex items-center">
|
||||
<view class="flex flex-1 items-center justify-between">
|
||||
<view>默认已提货量》</view>
|
||||
<view>
|
||||
<u-number-box :min="1" :max="3" :value="1">
|
||||
</u-number-box>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<GoodsItem class="px-0" />
|
||||
<u-line v-if="list.length-1!=index" :hair-line="false" color="#c8c9cc"></u-line>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="h-180rpx"></view>
|
||||
<!-- 底部按钮 -->
|
||||
<view class="flex items-center justify-end bg-white bottom-card z-99 fixed inset-x-0">
|
||||
<view class="flex items-center">
|
||||
<view>
|
||||
合计
|
||||
<text class="ml-10rpx price-text text-hex-ef4444">15元</text>
|
||||
</view>
|
||||
<view class="ml-10rpx">
|
||||
vip
|
||||
<text class="ml-10rpx price-text text-hex-ef4444">15元</text>
|
||||
</view>
|
||||
</view>
|
||||
<view @click="$u.route('/pages/code/index')" class="bg-hex-ef4444 h-100rpx w-210rpx text-white text-center leading-100rpx ml-30rpx">生成二维码</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import GoodsItem from './components/goods-item.vue'
|
||||
export default {
|
||||
components: {
|
||||
GoodsItem
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
keyword: '',
|
||||
list1: [{}],
|
||||
list: [{}, {}]
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.bottom-card {
|
||||
box-shadow: 0px -4rpx 8rpx rgba(0, 0, 0, 0.25);
|
||||
bottom: var(--window-bottom);
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
<template>
|
||||
<view class="px-80rpx pt-60rpx text-30rpx font-extrabold">
|
||||
<view class="grid grid-cols-2 gap-x-80rpx gap-y-40rpx">
|
||||
<view @click="$u.route('/pages/select_product/index')" class="flex items-center justify-center flex-col" v-for="(item,index) in 4" :key="index">
|
||||
<view class="w-240rpx h-240rpx bg-hex-367fa9 bg-opacity-60"></view>
|
||||
<view class="mt-15rpx">龙湖店</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
};
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
</style>
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<view class="bg-white px-40rpx" @tap="$u.route('/pages/user_select_pro/index',{id:1})">
|
||||
<view class="h-78rpx flex justify-between items-center">
|
||||
<text class="text-lg">订单编号:12345678954565</text>
|
||||
<text class="text-md text-hex-2A82E4">发货中</text>
|
||||
</view>
|
||||
<u-line color="#E5E5E5" />
|
||||
<block v-for="(ite,ind) in item.products" :key="ind">
|
||||
<view class="flex py-base">
|
||||
<u-image border-radius="10" width="160rpx" height="160rpx" src="http://zcs-test.oss-cn-chengdu.aliyuncs.com/product-spus/cover/2022-04-22/f6f96cba9ba86197e58ea3c2ce93b2a1.jpg" :lazy-load="true"></u-image>
|
||||
<view class="flex-1 ml-16rpx w-0">
|
||||
<view class="h-70rpx">
|
||||
<view class="line-2">商品名称商品名称商品名称商品商品名称商品名称商品名称商品商品名称商品名称商品名称商品</view>
|
||||
</view>
|
||||
<view class="text-right text-lg mt-20rpx"> x 20 </view>
|
||||
</view>
|
||||
</view>
|
||||
<u-line color="#E5E5E5" />
|
||||
</block>
|
||||
<view class="flex items-center justify-between h-85rpx">
|
||||
<text class="text-md">2022-2-15</text>
|
||||
<text class="text-lg font-medium">共件5 应付总额:¥8888</text>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
export default {
|
||||
props:{
|
||||
item:{
|
||||
type:Object,
|
||||
default:()=>{}
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
<template>
|
||||
<view>
|
||||
<!-- 搜索框 -->
|
||||
<view class="flex items-center px-40rpx text-32rpx bg-white py-30rpx">
|
||||
<view class="mr-15rpx">手机号:</view>
|
||||
<u-search class="flex-1" placeholder="用户手机号" v-model="searchText" :show-action="false"></u-search>
|
||||
</view>
|
||||
<view class="pt-base">
|
||||
<block v-for="(item,index) in dataList" :key="index">
|
||||
<order-item @Parent=Parent :item="item" class="mb-24rpx" />
|
||||
</block>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<script>
|
||||
import OrderItem from './conponents/order-item.vue';
|
||||
export default {
|
||||
components: {
|
||||
OrderItem,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
searchText:'',
|
||||
dataList:[{products:[{}]},{products:[{},{}]},{products:[{},{},{}]}]
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.badge {
|
||||
@apply bg-badge;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
<template>
|
||||
<view class="pt-20rpx">
|
||||
<block v-for="(item,index) in list" :key="index">
|
||||
<view class="bg-white px-40rpx notFrist">
|
||||
<view class="flex py-base">
|
||||
<u-image border-radius="10" width="170rpx" height="170rpx"
|
||||
src="http://zcs-test.oss-cn-chengdu.aliyuncs.com/product-spus/cover/2022-04-22/f6f96cba9ba86197e58ea3c2ce93b2a1.jpg"
|
||||
:lazy-load="true"></u-image>
|
||||
<view class="flex-1 ml-16rpx w-0">
|
||||
<view class="h-70rpx">
|
||||
<view class="line-2">商品名称商品名称商品名称商品商品名称商品名称商品名称商品商品名称商品名称商品名称商品</view>
|
||||
</view>
|
||||
<view class="flex items-center justify-between mt-20rpx">
|
||||
<text>待提货</text>
|
||||
<text>x 20</text>
|
||||
</view>
|
||||
<view class="flex items-center justify-between ">
|
||||
<text>已提货</text>
|
||||
<text>x 20</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<u-line color="#E5E5E5" />
|
||||
<view class="flex items-center justify-between h-85rpx">
|
||||
<view>本次提货</view>
|
||||
<view>
|
||||
<u-number-box :min="1" :max="3" :value="1">
|
||||
</u-number-box>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</block>
|
||||
<view class="h-170rpx"></view>
|
||||
<!-- 底部按钮 -->
|
||||
<view class="fixed bottom-0 left-0 w-full bg-white shadow-t ">
|
||||
<view class="h-100rpx flex items-center px-24rpx justify-between text-hex-999999">
|
||||
<view class="h-72rpx flex-1 leading-72rpx text-center rounded-full bg-hex-ED1B13 text-26rpx text-center text-white">提货</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
list: [{}, {}]
|
||||
};
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.shadow-t {
|
||||
box-shadow: 0px -2px 5px rgb(0 0 0 / 11%);
|
||||
}
|
||||
.notFrist:not(:first-child){
|
||||
margin-top: 20rpx;
|
||||
}
|
||||
</style>
|
||||
|
Before Width: | Height: | Size: 13 KiB |
|
|
@ -1 +0,0 @@
|
|||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1637738592328" class="icon" viewBox="0 0 1204 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="11081" xmlns:xlink="http://www.w3.org/1999/xlink" width="235.15625" height="200"><defs><style type="text/css"></style></defs><path d="M1241.702 391.831l2.1 1.663q-1.035-0.875-2.1-1.663zM-38.708 394.223l3.792-3.004c-4.553 3.167-8.267 7.216-10.964 11.922z" p-id="11082" fill="#d81e06"></path><path d="M94.676 1025.202c-0.335 2.246-0.686 4.478-0.991 6.738 0.632-2.397 1.004-5.153 1.025-7.992zM61.057 1057.29zM27.48 1021.133q0.365-2.523 0.758-5.046c-0.512 2.136-0.819 4.598-0.846 7.124z" p-id="11083" fill="#d81e06"></path><path d="M1178.516 1013.3c0.422 2.64 0.802 5.294 1.181 7.948-0.106-3.188-0.621-6.214-1.497-9.083zM1111.032 1030.643c-0.291-2.086-0.597-4.157-0.919-6.228 0.164 2.851 0.645 5.522 1.408 8.070z" p-id="11084" fill="#d81e06"></path><path d="M994.8 313.771h-46.018l-24.591 139.174h41.743q41.29 0 66.012-18.581t30.848-53.265q11.874-67.326-67.997-67.326z" p-id="11085" fill="#d81e06"></path><path d="M1149.333 204.992h-1092.943c-0.012 0-0.027 0-0.045 0-31.855 0-57.681 25.807-57.715 57.653v374.76c0.031 31.85 25.86 57.656 57.715 57.656 0.014 0 0.029 0 0.047 0h1092.94c0.012 0 0.027 0 0.045 0 31.855 0 57.681-25.807 57.715-57.653v-374.76c-0.031-31.85-25.86-57.656-57.715-57.656-0.014 0-0.029 0-0.047 0zM369.493 329.989q-27.712-21.877-76.005-21.877-32.088 0-54.608 13.404t-26.909 37.296q-3.734 21.179 7.907 34.451t54.345 36.347q47.111 24.549 63.3 49.094t10.779 55.176q-8.11 45.945-45.841 70.491t-97.067 24.474c-17.979-0.282-35.277-2.606-51.855-6.749q-22.166-4.73-33.28-13.291l9.072-51.34q13.55 13.973 38.811 22.972c14.958 5.68 32.253 8.984 50.316 9.019q72.64-0.004 81.872-52.25c0.54-2.515 0.852-5.406 0.852-8.369 0-6.535-1.509-12.719-4.2-18.219-5.953-11.315-15.433-20.387-26.978-25.774q-3.119-3.718-37.716-22.433-47.957-26.123-60.69-48.54t-7.643-51.239q7.686-43.465 47.155-68.916t92.748-25.436q52.114 0 74.254 12.616zM485.608 622.995l-64.35-348.854h50.086l45.9 272.748c1.11 6.694 1.743 14.408 1.743 22.27 0 3.177-0.105 6.33-0.309 9.456l1.145-0.425c6.745-22.026 17.444-41.077 31.384-57.255l126.109-246.778h48.278l-189.974 348.84h-50.086zM813.226 312.195l-48.131 272.748h33.546l-6.723 38.068h-112.307l6.723-38.068h33.546l48.131-272.748h-33.533l6.723-38.068h112.307l-6.723 38.068h-33.546zM1110.475 379.288q-9.072 51.355-51.253 83.21t-96.701 30.075h-45.331l-23.030 130.393h-45.141l61.594-348.824h99.283q56.635 0 83.137 27.246t17.4 77.93z" p-id="11086" fill="#d81e06"></path></svg>
|
||||
|
Before Width: | Height: | Size: 2.6 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.6 KiB |
|
Before Width: | Height: | Size: 1.7 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 1.1 KiB |
|
Before Width: | Height: | Size: 989 B |
|
Before Width: | Height: | Size: 774 B |
|
|
@ -1,17 +0,0 @@
|
|||
const getters = {
|
||||
isLogin: state => !!state.user.token,
|
||||
token: state => state.user.token,
|
||||
user:state => state.user.user,
|
||||
allaccounts:state => state.user.allaccounts,
|
||||
news_num:state=>state.user.news_num,
|
||||
order_count:state=>state.user.order_count,
|
||||
history:state=>state.user.history,
|
||||
platform: state => state.app.platform,
|
||||
system: state => state.app.system,
|
||||
article:state=>state.app.article,
|
||||
cartList:state => state.goods.cartList,
|
||||
selectedCart:state => state.goods.selectedCart,
|
||||
hiddenConfig:state => state.app.hiddenConfig,
|
||||
config:state => state.app.config,
|
||||
}
|
||||
export default getters
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
import Vue from 'vue'
|
||||
import Vuex from 'vuex'
|
||||
import getters from './getters'
|
||||
|
||||
import user from './modules/user'
|
||||
|
||||
|
||||
Vue.use(Vuex)
|
||||
|
||||
const store = new Vuex.Store({
|
||||
modules: {
|
||||
user,
|
||||
},
|
||||
getters
|
||||
})
|
||||
|
||||
export default store
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
import {
|
||||
http
|
||||
} from '@/utils/request'
|
||||
|
||||
const TOKEN = 'app_token'
|
||||
const getDefaultState = () => {
|
||||
return {
|
||||
token: uni.getStorageSync(TOKEN),
|
||||
user: null,//用户信息
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const state = getDefaultState()
|
||||
|
||||
const mutations = {
|
||||
RESET_STATE: (state) => {
|
||||
Object.assign(state, getDefaultState())
|
||||
},
|
||||
// 登录
|
||||
SET_TOKEN(state, value) {
|
||||
state.token = value
|
||||
uni.setStorageSync(TOKEN, value);
|
||||
},
|
||||
//获取用户信息
|
||||
SET_USERINFO(state, value) {
|
||||
state.user = value
|
||||
},
|
||||
}
|
||||
|
||||
const actions = {
|
||||
//登录
|
||||
async login({
|
||||
commit,
|
||||
state,
|
||||
dispatch
|
||||
}, { form, switchAllaccounts }) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('/v1/login', form, {
|
||||
custom: {
|
||||
loading: true
|
||||
}
|
||||
}).then(({
|
||||
token
|
||||
}) => {
|
||||
resolve(token)
|
||||
commit('SET_TOKEN', token)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
});
|
||||
|
||||
})
|
||||
},
|
||||
//退出登录
|
||||
logout({
|
||||
commit
|
||||
}) {
|
||||
return new Promise((resolve, reject) => {
|
||||
http.post('/v1/logout', {}, {
|
||||
custom: {
|
||||
loading: true
|
||||
}
|
||||
}).then(() => {
|
||||
// console.log(token)
|
||||
uni.removeStorageSync(TOKEN);
|
||||
commit('SET_TOKEN')
|
||||
commit('SET_USERINFO', {})
|
||||
resolve()
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
});
|
||||
})
|
||||
},
|
||||
//获取用户信息
|
||||
getUserInfo({ commit, state, dispatch }, switchAllaccounts) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!state.token) return
|
||||
http.get('/v1/me', {}, {
|
||||
custom: {
|
||||
silence: true
|
||||
}
|
||||
}).then(({ phone, user_info, is_vip, wallet, balance }) => {
|
||||
let user = user_info
|
||||
user.phone = phone
|
||||
user.is_vip = is_vip
|
||||
user.wallet = wallet
|
||||
user.balance = balance
|
||||
commit('SET_USERINFO', user)
|
||||
resolve(user)
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
},
|
||||
resetToken({ commit }) {
|
||||
return new Promise(resolve => {
|
||||
uni.removeStorageSync(TOKEN);
|
||||
commit('RESET_STATE')
|
||||
resolve()
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
page {
|
||||
background: #f5f5f5;
|
||||
}
|
||||
//文本溢出
|
||||
.line-1 {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical
|
||||
}
|
||||
.line-2 {
|
||||
overflow: hidden;
|
||||
-webkit-line-clamp: 2;
|
||||
text-overflow: ellipsis;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
.translate-y-0 {
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.translate-center {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
.translate-bottom-center {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
}
|
||||
|
||||
[type='search'] {
|
||||
-webkit-appearance: none !important;
|
||||
}
|
||||
.price-text {
|
||||
&::before {
|
||||
content: '¥';
|
||||
font-size: 60%;
|
||||
}
|
||||
}
|
||||
.cu-btn {
|
||||
position: relative;
|
||||
border:none;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
margin-left: initial;
|
||||
transform: translate(0upx, 0upx);
|
||||
margin-right: initial;
|
||||
appearance: none;
|
||||
}
|
||||
81
src/uni.scss
|
|
@ -1,81 +0,0 @@
|
|||
/**
|
||||
* 这里是uni-app内置的常用样式变量
|
||||
*
|
||||
* uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
|
||||
* 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
|
||||
*
|
||||
*/
|
||||
|
||||
/**
|
||||
* 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
|
||||
*
|
||||
* 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
|
||||
*/
|
||||
|
||||
/* 颜色变量 */
|
||||
@import 'uview-ui/theme.scss';
|
||||
|
||||
/* UView颜色变量 */
|
||||
$u-type-primary: theme("colors.primary");
|
||||
$u-type-warning : theme("colors.warning");
|
||||
|
||||
/* 行为相关颜色 */
|
||||
$uni-color-primary: theme("colors.primary");
|
||||
$uni-color-success: theme("colors.success");
|
||||
$uni-color-warning: theme("colors.warning");
|
||||
$uni-color-error: theme("colors.error");
|
||||
|
||||
/* 文字基本颜色 */
|
||||
$uni-text-color:#333;//基本色
|
||||
$uni-text-color-inverse:#fff;//反色
|
||||
$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
|
||||
$uni-text-color-placeholder: #808080;
|
||||
$uni-text-color-disable:#c0c0c0;
|
||||
|
||||
/* 背景颜色 */
|
||||
$uni-bg-color:#ffffff;
|
||||
$uni-bg-color-grey:#f8f8f8;
|
||||
$uni-bg-color-hover:#f1f1f1;//点击状态颜色
|
||||
$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
|
||||
|
||||
/* 边框颜色 */
|
||||
$uni-border-color:#c8c7cc;
|
||||
|
||||
/* 尺寸变量 */
|
||||
|
||||
/* 文字尺寸 */
|
||||
$uni-font-size-sm:24rpx;
|
||||
$uni-font-size-base:28rpx;
|
||||
$uni-font-size-lg:32rpx;
|
||||
|
||||
/* 图片尺寸 */
|
||||
$uni-img-size-sm:40rpx;
|
||||
$uni-img-size-base:52rpx;
|
||||
$uni-img-size-lg:80rpx;
|
||||
|
||||
/* Border Radius */
|
||||
$uni-border-radius-sm: 4rpx;
|
||||
$uni-border-radius-base: 6rpx;
|
||||
$uni-border-radius-lg: 12rpx;
|
||||
$uni-border-radius-circle: 50%;
|
||||
|
||||
/* 水平间距 */
|
||||
$uni-spacing-row-sm: 10px;
|
||||
$uni-spacing-row-base: 20rpx;
|
||||
$uni-spacing-row-lg: 30rpx;
|
||||
|
||||
/* 垂直间距 */
|
||||
$uni-spacing-col-sm: 8rpx;
|
||||
$uni-spacing-col-base: 16rpx;
|
||||
$uni-spacing-col-lg: 24rpx;
|
||||
|
||||
/* 透明度 */
|
||||
$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
|
||||
|
||||
/* 文章场景相关 */
|
||||
$uni-color-title: #2C405A; // 文章标题颜色
|
||||
$uni-font-size-title:40rpx;
|
||||
$uni-color-subtitle: #555555; // 二级标题颜色
|
||||
$uni-font-size-subtitle:36rpx;
|
||||
$uni-color-paragraph: #3F536E; // 文章段落颜色
|
||||
$uni-font-size-paragraph:30rpx;
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
## 1.3.7(2021-04-13)
|
||||
1. 新增`mescroll-swiper-sticky.vue`的示例, 轮播吸顶菜单导航
|
||||
2. 新增`mescroll-empty.vue`的示例, 单独使用空布局组件
|
||||
3. 简化tabs在具体项目中的使用,并简化对应的示例
|
||||
4. mescroll-uni 支持动态禁止滚动的属性 disableScroll (注: mescroll-body不支持)
|
||||
-by 小瑾同学
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
.mescroll-body {
|
||||
position: relative; /* 下拉刷新区域相对自身定位 */
|
||||
height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/
|
||||
overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */
|
||||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
|
||||
}
|
||||
|
||||
/* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */
|
||||
.mescroll-body.mescorll-sticky{
|
||||
overflow: unset !important
|
||||
}
|
||||
|
||||
/* 适配 iPhoneX */
|
||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
|
||||
.mescroll-safearea {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,400 +0,0 @@
|
|||
<template>
|
||||
<view
|
||||
class="mescroll-body mescroll-render-touch"
|
||||
:class="{'mescorll-sticky': sticky}"
|
||||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}"
|
||||
@touchstart="wxsBiz.touchstartEvent"
|
||||
@touchmove="wxsBiz.touchmoveEvent"
|
||||
@touchend="wxsBiz.touchendEvent"
|
||||
@touchcancel="wxsBiz.touchendEvent"
|
||||
:change:prop="wxsBiz.propObserver"
|
||||
:prop="wxsProp"
|
||||
>
|
||||
<!-- 状态栏 -->
|
||||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
|
||||
|
||||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
|
||||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
|
||||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
|
||||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
|
||||
<view class="downwarp-tip">{{downText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<slot></slot>
|
||||
|
||||
<!-- 空布局 -->
|
||||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
|
||||
|
||||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
|
||||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
|
||||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
||||
<view v-show="upLoadType===1">
|
||||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
|
||||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
|
||||
</view>
|
||||
<!-- 无数据 -->
|
||||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 适配iPhoneX -->
|
||||
<view v-if="safearea" class="mescroll-safearea"></view>
|
||||
|
||||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
|
||||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @tap="toTopClick"></mescroll-top>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
|
||||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<script src="../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- app, h5使用renderjs -->
|
||||
<!-- #ifdef APP-PLUS || H5 -->
|
||||
<script module="renderBiz" lang="renderjs">
|
||||
import renderBiz from "../mescroll-uni/wxs/renderjs.js";
|
||||
export default {
|
||||
mixins: [renderBiz]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
|
||||
<script>
|
||||
// 引入mescroll-uni.js,处理核心逻辑
|
||||
import MeScroll from "../mescroll-uni/mescroll-uni.js";
|
||||
// 引入全局配置
|
||||
import GlobalOption from "../mescroll-uni/mescroll-uni-option.js";
|
||||
// 引入国际化工具类
|
||||
import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
|
||||
// 引入回到顶部组件
|
||||
import MescrollTop from "../mescroll-uni/components/mescroll-top.vue";
|
||||
// 引入兼容wxs(含renderjs)写法的mixins
|
||||
import WxsMixin from "../mescroll-uni/wxs/mixins.js";
|
||||
|
||||
/**
|
||||
* mescroll-body 基于page滚动的下拉刷新和上拉加载组件, 支持嵌套原生组件, 性能好
|
||||
* @property {Object} down 下拉刷新的参数配置
|
||||
* @property {Object} up 上拉加载的参数配置
|
||||
* @property {Object} i18n 国际化的参数配置
|
||||
* @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
* @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
|
||||
* @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
* @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
|
||||
* @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
|
||||
* @property {String, Number} height 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
|
||||
* @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
|
||||
* @property {Boolean} sticky 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法隐藏
|
||||
* @event {Function} init 初始化完成的回调
|
||||
* @event {Function} down 下拉刷新的回调
|
||||
* @event {Function} up 上拉加载的回调
|
||||
* @event {Function} emptyclick 点击empty配置的btnText按钮回调
|
||||
* @event {Function} topclick 点击回到顶部的按钮回调
|
||||
* @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
|
||||
* @example <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-body>
|
||||
*/
|
||||
export default {
|
||||
name: 'mescroll-body',
|
||||
mixins: [WxsMixin],
|
||||
components: {
|
||||
MescrollTop
|
||||
},
|
||||
props: {
|
||||
down: Object,
|
||||
up: Object,
|
||||
i18n: Object,
|
||||
top: [String, Number],
|
||||
topbar: [Boolean, String],
|
||||
bottom: [String, Number],
|
||||
safearea: Boolean,
|
||||
height: [String, Number],
|
||||
bottombar:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
sticky: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mescroll: {optDown:{},optUp:{}}, // mescroll实例
|
||||
downHight: 0, //下拉刷新: 容器高度
|
||||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
|
||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
|
||||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
|
||||
isShowEmpty: false, // 是否显示空布局
|
||||
isShowToTop: false, // 是否显示回到顶部按钮
|
||||
windowHeight: 0, // 可使用窗口的高度
|
||||
windowBottom: 0, // 可使用窗口的底部位置
|
||||
statusBarHeight: 0 // 状态栏高度
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
|
||||
minHeight(){
|
||||
return this.toPx(this.height || '100%') + 'px'
|
||||
},
|
||||
// 下拉布局往下偏移的距离 (px)
|
||||
numTop() {
|
||||
return this.toPx(this.top)
|
||||
},
|
||||
padTop() {
|
||||
return this.numTop + 'px';
|
||||
},
|
||||
// 上拉布局往上偏移 (px)
|
||||
numBottom() {
|
||||
return this.toPx(this.bottom);
|
||||
},
|
||||
padBottom() {
|
||||
return this.numBottom + 'px';
|
||||
},
|
||||
// 是否为重置下拉的状态
|
||||
isDownReset() {
|
||||
return this.downLoadType === 3 || this.downLoadType === 4;
|
||||
},
|
||||
// 过渡
|
||||
transition() {
|
||||
return this.isDownReset ? 'transform 300ms' : '';
|
||||
},
|
||||
translateY() {
|
||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading(){
|
||||
return this.downLoadType === 3
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate(){
|
||||
return 'rotate(' + 360 * this.downRate + 'deg)'
|
||||
},
|
||||
// 文本提示
|
||||
downText(){
|
||||
if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
|
||||
switch (this.downLoadType){
|
||||
case 1: return this.mescroll.optDown.textInOffset;
|
||||
case 2: return this.mescroll.optDown.textOutOffset;
|
||||
case 3: return this.mescroll.optDown.textLoading;
|
||||
case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
|
||||
default: return this.mescroll.optDown.textInOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//number,rpx,upx,px,% --> px的数值
|
||||
toPx(num) {
|
||||
if (typeof num === 'string') {
|
||||
if (num.indexOf('px') !== -1) {
|
||||
if (num.indexOf('rpx') !== -1) {
|
||||
// "10rpx"
|
||||
num = num.replace('rpx', '');
|
||||
} else if (num.indexOf('upx') !== -1) {
|
||||
// "10upx"
|
||||
num = num.replace('upx', '');
|
||||
} else {
|
||||
// "10px"
|
||||
return Number(num.replace('px', ''));
|
||||
}
|
||||
} else if (num.indexOf('%') !== -1) {
|
||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
|
||||
let rate = Number(num.replace('%', '')) / 100;
|
||||
return this.windowHeight * rate;
|
||||
}
|
||||
}
|
||||
return num ? uni.upx2px(Number(num)) : 0;
|
||||
},
|
||||
// 点击空布局的按钮回调
|
||||
emptyClick() {
|
||||
this.$emit('emptyclick', this.mescroll);
|
||||
},
|
||||
// 点击回到顶部的按钮回调
|
||||
toTopClick() {
|
||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
|
||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
|
||||
}
|
||||
},
|
||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
|
||||
created() {
|
||||
let vm = this;
|
||||
|
||||
let diyOption = {
|
||||
// 下拉刷新的配置
|
||||
down: {
|
||||
inOffset() {
|
||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
outOffset() {
|
||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
onMoving(mescroll, rate, downHight) {
|
||||
// 下拉过程中的回调,滑动过程一直在执行;
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
|
||||
},
|
||||
showLoading(mescroll, downHight) {
|
||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
beforeEndDownScroll(mescroll){
|
||||
vm.downLoadType = 4;
|
||||
return mescroll.optDown.beforeEndDelay // 延时结束的时长
|
||||
},
|
||||
endDownScroll() {
|
||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
|
||||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
|
||||
if(vm.downLoadType === 4) vm.downLoadType = 0
|
||||
},300)
|
||||
},
|
||||
// 派发下拉刷新的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('down', mescroll);
|
||||
}
|
||||
},
|
||||
// 上拉加载的配置
|
||||
up: {
|
||||
// 显示加载中的回调
|
||||
showLoading() {
|
||||
vm.upLoadType = 1;
|
||||
},
|
||||
// 显示无更多数据的回调
|
||||
showNoMore() {
|
||||
vm.upLoadType = 2;
|
||||
},
|
||||
// 隐藏上拉加载的回调
|
||||
hideUpScroll(mescroll) {
|
||||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
|
||||
},
|
||||
// 空布局
|
||||
empty: {
|
||||
onShow(isShow) {
|
||||
// 显示隐藏的回调
|
||||
vm.isShowEmpty = isShow;
|
||||
}
|
||||
},
|
||||
// 回到顶部
|
||||
toTop: {
|
||||
onShow(isShow) {
|
||||
// 显示隐藏的回调
|
||||
vm.isShowToTop = isShow;
|
||||
}
|
||||
},
|
||||
// 派发上拉加载的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('up', mescroll);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let i18nType = mescrollI18n.getType() // 当前语言类型
|
||||
let i18nOption = {type: i18nType} // 国际化配置
|
||||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
|
||||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
|
||||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
|
||||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
|
||||
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
|
||||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
|
||||
|
||||
// 初始化MeScroll对象
|
||||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
|
||||
// 挂载语言包
|
||||
vm.mescroll.i18n = i18nOption;
|
||||
// init回调mescroll对象
|
||||
vm.$emit('init', vm.mescroll);
|
||||
|
||||
// 设置高度
|
||||
const sys = uni.getSystemInfoSync();
|
||||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
|
||||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
|
||||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
|
||||
// 使down的bottomOffset生效
|
||||
vm.mescroll.setBodyHeight(sys.windowHeight);
|
||||
|
||||
// 因为使用的是page的scroll,这里需自定义scrollTo
|
||||
vm.mescroll.resetScrollTo((y, t) => {
|
||||
if(typeof y === 'string'){
|
||||
// 滚动到指定view (y为css选择器)
|
||||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
|
||||
let selector;
|
||||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
|
||||
selector = '#'+y // 不带#和. 则默认为id选择器
|
||||
}else{
|
||||
selector = y
|
||||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
|
||||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
|
||||
selector = y.split('>>>')[1].trim()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
|
||||
if (rect) {
|
||||
let top = rect.top
|
||||
top += vm.mescroll.getScrollTop()
|
||||
uni.pageScrollTo({
|
||||
scrollTop: top,
|
||||
duration: t
|
||||
})
|
||||
} else{
|
||||
console.error(selector + ' does not exist');
|
||||
}
|
||||
}).exec()
|
||||
},30)
|
||||
} else{
|
||||
// 滚动到指定位置 (y必须为数字)
|
||||
uni.pageScrollTo({
|
||||
scrollTop: y,
|
||||
duration: t
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
|
||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
|
||||
vm.mescroll.optUp.toTop.safearea = vm.safearea;
|
||||
}
|
||||
|
||||
// 全局配置监听
|
||||
uni.$on("setMescrollGlobalOption", options=>{
|
||||
if(!options) return;
|
||||
let i18nType = options.i18n ? options.i18n.type : null
|
||||
if(i18nType && vm.mescroll.i18n.type != i18nType){
|
||||
vm.mescroll.i18n.type = i18nType
|
||||
mescrollI18n.setType(i18nType)
|
||||
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
|
||||
}
|
||||
if(options.down){
|
||||
let down = MeScroll.extend({}, options.down)
|
||||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
|
||||
}
|
||||
if(options.up){
|
||||
let up = MeScroll.extend({}, options.up)
|
||||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
|
||||
}
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
// 注销全局配置监听
|
||||
uni.$off("setMescrollGlobalOption")
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "../mescroll-body/mescroll-body.css";
|
||||
@import "../mescroll-uni/components/mescroll-down.css";
|
||||
@import "../mescroll-uni/components/mescroll-up.css";
|
||||
</style>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
/*下拉刷新--标语*/
|
||||
.mescroll-downwarp .downwarp-slogan{
|
||||
display: block;
|
||||
width: 420rpx;
|
||||
height: 168rpx;
|
||||
margin: auto;
|
||||
}
|
||||
/*下拉刷新--向下进度动画*/
|
||||
.mescroll-downwarp .downwarp-progress{
|
||||
display: inline-block;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
border: none;
|
||||
margin: auto;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-image: url(https://www.mescroll.com/img/beibei/mescroll-progress.png);
|
||||
transition: all 300ms;
|
||||
}
|
||||
/*下拉刷新--进度条*/
|
||||
.mescroll-downwarp .downwarp-loading{
|
||||
display: inline-block;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #FF8095;
|
||||
border-bottom-color: transparent;
|
||||
}
|
||||
/*下拉刷新--吉祥物*/
|
||||
.mescroll-downwarp .downwarp-mascot{
|
||||
position: absolute;
|
||||
right: 16rpx;
|
||||
bottom: 0;
|
||||
width: 100rpx;
|
||||
height: 100rpx;
|
||||
background-size: contain;
|
||||
background-repeat: no-repeat;
|
||||
animation: animMascot .6s steps(1,end) infinite;
|
||||
}
|
||||
@keyframes animMascot {
|
||||
0% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
|
||||
25% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb2.png)}
|
||||
50% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb3.png)}
|
||||
75% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb4.png)}
|
||||
100% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<!-- 下拉刷新区域 -->
|
||||
<template>
|
||||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
|
||||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
|
||||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
|
||||
<view class="downwarp-mascot"></view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
option: Object , // down的配置项
|
||||
type: Number // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
|
||||
},
|
||||
computed: {
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
||||
mOption(){
|
||||
return this.option || {}
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading(){
|
||||
return this.type === 3
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate(){
|
||||
return this.type === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "../../../mescroll-uni/components/mescroll-down.css";
|
||||
@import "./mescroll-down.css";
|
||||
</style>
|
||||
|
|
@ -1,360 +0,0 @@
|
|||
<template>
|
||||
<view
|
||||
class="mescroll-body mescroll-render-touch"
|
||||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}"
|
||||
:class="{'mescorll-sticky': sticky}"
|
||||
@touchstart="wxsBiz.touchstartEvent"
|
||||
@touchmove="wxsBiz.touchmoveEvent"
|
||||
@touchend="wxsBiz.touchendEvent"
|
||||
@touchcancel="wxsBiz.touchendEvent"
|
||||
:change:prop="wxsBiz.propObserver"
|
||||
:prop="wxsProp"
|
||||
>
|
||||
|
||||
<!-- 状态栏 -->
|
||||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
|
||||
|
||||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
|
||||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
|
||||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
|
||||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
|
||||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
|
||||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
|
||||
<view class="downwarp-mascot"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<slot></slot>
|
||||
|
||||
<!-- 空布局 -->
|
||||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
|
||||
|
||||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
|
||||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
|
||||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
||||
<view v-show="upLoadType===1">
|
||||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
|
||||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
|
||||
</view>
|
||||
<!-- 无数据 -->
|
||||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 适配iPhoneX -->
|
||||
<view v-if="safearea" class="mescroll-safearea"></view>
|
||||
|
||||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
|
||||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @tap="toTopClick"></mescroll-top>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
|
||||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- app, h5使用renderjs -->
|
||||
<!-- #ifdef APP-PLUS || H5 -->
|
||||
<script module="renderBiz" lang="renderjs">
|
||||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
|
||||
export default {
|
||||
mixins: [renderBiz]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
|
||||
<script>
|
||||
import MeScroll from '../../mescroll-uni/mescroll-uni.js';
|
||||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
|
||||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
|
||||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
|
||||
import GlobalOption from './mescroll-uni-option.js';
|
||||
|
||||
export default {
|
||||
mixins: [WxsMixin],
|
||||
components: {
|
||||
MescrollTop
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mescroll: null, // mescroll实例
|
||||
downHight: 0, //下拉刷新: 容器高度
|
||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
|
||||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
|
||||
isShowEmpty: false, // 是否显示空布局
|
||||
isShowToTop: false, // 是否显示回到顶部按钮
|
||||
windowHeight: 0, // 可使用窗口的高度
|
||||
windowBottom: 0, // 可使用窗口的底部位置
|
||||
statusBarHeight: 0 // 状态栏高度
|
||||
};
|
||||
},
|
||||
props: {
|
||||
down: Object, // 下拉刷新的参数配置
|
||||
up: Object, // 上拉加载的参数配置
|
||||
i18n: Object, // 国际化的参数配置
|
||||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
|
||||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
|
||||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
|
||||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏
|
||||
},
|
||||
computed: {
|
||||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
|
||||
minHeight(){
|
||||
return this.toPx(this.height || '100%') + 'px'
|
||||
},
|
||||
// 下拉布局往下偏移的距离 (px)
|
||||
numTop() {
|
||||
return this.toPx(this.top)
|
||||
},
|
||||
padTop() {
|
||||
return this.numTop + 'px';
|
||||
},
|
||||
// 上拉布局往上偏移 (px)
|
||||
numBottom() {
|
||||
return this.toPx(this.bottom);
|
||||
},
|
||||
padBottom() {
|
||||
return this.numBottom + 'px';
|
||||
},
|
||||
// 是否为重置下拉的状态
|
||||
isDownReset() {
|
||||
return this.downLoadType === 3 || this.downLoadType === 4;
|
||||
},
|
||||
// 过渡
|
||||
transition() {
|
||||
return this.isDownReset ? 'transform 300ms' : '';
|
||||
},
|
||||
translateY() {
|
||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading(){
|
||||
return this.downLoadType === 3
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate(){
|
||||
return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//number,rpx,upx,px,% --> px的数值
|
||||
toPx(num) {
|
||||
if (typeof num === 'string') {
|
||||
if (num.indexOf('px') !== -1) {
|
||||
if (num.indexOf('rpx') !== -1) {
|
||||
// "10rpx"
|
||||
num = num.replace('rpx', '');
|
||||
} else if (num.indexOf('upx') !== -1) {
|
||||
// "10upx"
|
||||
num = num.replace('upx', '');
|
||||
} else {
|
||||
// "10px"
|
||||
return Number(num.replace('px', ''));
|
||||
}
|
||||
} else if (num.indexOf('%') !== -1) {
|
||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
|
||||
let rate = Number(num.replace('%', '')) / 100;
|
||||
return this.windowHeight * rate;
|
||||
}
|
||||
}
|
||||
return num ? uni.upx2px(Number(num)) : 0;
|
||||
},
|
||||
// 点击空布局的按钮回调
|
||||
emptyClick() {
|
||||
this.$emit('emptyclick', this.mescroll);
|
||||
},
|
||||
// 点击回到顶部的按钮回调
|
||||
toTopClick() {
|
||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
|
||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
|
||||
}
|
||||
},
|
||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
|
||||
created() {
|
||||
let vm = this;
|
||||
|
||||
let diyOption = {
|
||||
// 下拉刷新的配置
|
||||
down: {
|
||||
inOffset() {
|
||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
outOffset() {
|
||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
onMoving(mescroll, rate, downHight) {
|
||||
// 下拉过程中的回调,滑动过程一直在执行;
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
showLoading(mescroll, downHight) {
|
||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
endDownScroll() {
|
||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
|
||||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
|
||||
if(vm.downLoadType === 4) vm.downLoadType = 0
|
||||
},300)
|
||||
},
|
||||
// 派发下拉刷新的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('down', mescroll);
|
||||
}
|
||||
},
|
||||
// 上拉加载的配置
|
||||
up: {
|
||||
// 显示加载中的回调
|
||||
showLoading() {
|
||||
vm.upLoadType = 1;
|
||||
},
|
||||
// 显示无更多数据的回调
|
||||
showNoMore() {
|
||||
vm.upLoadType = 2;
|
||||
},
|
||||
// 隐藏上拉加载的回调
|
||||
hideUpScroll(mescroll) {
|
||||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
|
||||
},
|
||||
// 空布局
|
||||
empty: {
|
||||
onShow(isShow) {
|
||||
// 显示隐藏的回调
|
||||
vm.isShowEmpty = isShow;
|
||||
}
|
||||
},
|
||||
// 回到顶部
|
||||
toTop: {
|
||||
onShow(isShow) {
|
||||
// 显示隐藏的回调
|
||||
vm.isShowToTop = isShow;
|
||||
}
|
||||
},
|
||||
// 派发上拉加载的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('up', mescroll);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let i18nType = mescrollI18n.getType() // 当前语言类型
|
||||
let i18nOption = {type: i18nType} // 国际化配置
|
||||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
|
||||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
|
||||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
|
||||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
|
||||
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
|
||||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
|
||||
|
||||
// 初始化MeScroll对象
|
||||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
|
||||
// 挂载语言包
|
||||
vm.mescroll.i18n = i18nOption;
|
||||
// init回调mescroll对象
|
||||
vm.$emit('init', vm.mescroll);
|
||||
|
||||
// 设置高度
|
||||
const sys = uni.getSystemInfoSync();
|
||||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
|
||||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
|
||||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
|
||||
// 使down的bottomOffset生效
|
||||
vm.mescroll.setBodyHeight(sys.windowHeight);
|
||||
|
||||
// 因为使用的是page的scroll,这里需自定义scrollTo
|
||||
vm.mescroll.resetScrollTo((y, t) => {
|
||||
if(typeof y === 'string'){
|
||||
// 滚动到指定view (y为css选择器)
|
||||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
|
||||
let selector;
|
||||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
|
||||
selector = '#'+y // 不带#和. 则默认为id选择器
|
||||
}else{
|
||||
selector = y
|
||||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
|
||||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
|
||||
selector = y.split('>>>')[1].trim()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
|
||||
if (rect) {
|
||||
let top = rect.top
|
||||
top += vm.mescroll.getScrollTop()
|
||||
uni.pageScrollTo({
|
||||
scrollTop: top,
|
||||
duration: t
|
||||
})
|
||||
} else{
|
||||
console.error(selector + ' does not exist');
|
||||
}
|
||||
}).exec()
|
||||
},30)
|
||||
} else{
|
||||
// 滚动到指定位置 (y必须为数字)
|
||||
uni.pageScrollTo({
|
||||
scrollTop: y,
|
||||
duration: t
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
|
||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
|
||||
vm.mescroll.optUp.toTop.safearea = vm.safearea;
|
||||
}
|
||||
|
||||
// 全局配置监听
|
||||
uni.$on("setMescrollGlobalOption", options=>{
|
||||
if(!options) return;
|
||||
let i18nType = options.i18n ? options.i18n.type : null
|
||||
if(i18nType && vm.mescroll.i18n.type != i18nType){
|
||||
vm.mescroll.i18n.type = i18nType
|
||||
mescrollI18n.setType(i18nType)
|
||||
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
|
||||
}
|
||||
if(options.down){
|
||||
let down = MeScroll.extend({}, options.down)
|
||||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
|
||||
}
|
||||
if(options.up){
|
||||
let up = MeScroll.extend({}, options.up)
|
||||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
|
||||
}
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
// 注销全局配置监听
|
||||
uni.$off("setMescrollGlobalOption")
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "../../mescroll-body/mescroll-body.css";
|
||||
@import "../../mescroll-uni/components/mescroll-down.css";
|
||||
@import "../../mescroll-uni/components/mescroll-up.css";
|
||||
@import "./components/mescroll-down.css";
|
||||
</style>
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
// mescroll-uni和mescroll-body 的全局配置
|
||||
const GlobalOption = {
|
||||
down: {
|
||||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
||||
offset: uni.upx2px(140), // 在列表顶部,下拉大于140upx,松手即可触发下拉刷新的回调
|
||||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
||||
},
|
||||
up: {
|
||||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
||||
offset: 150, // 距底部多远时,触发upCallback
|
||||
toTop: {
|
||||
// 回到顶部按钮,需配置src才显示
|
||||
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
|
||||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
|
||||
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
},
|
||||
empty: {
|
||||
use: true, // 是否显示空布局
|
||||
icon: "/static/images/user/empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
||||
}
|
||||
},
|
||||
// 国际化配置
|
||||
i18n: {
|
||||
// 中文
|
||||
zh: {
|
||||
up: {
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
||||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
||||
empty: {
|
||||
tip: '~ 暂无相关数据 ~' // 空提示
|
||||
}
|
||||
}
|
||||
},
|
||||
// 英文
|
||||
en: {
|
||||
up: {
|
||||
textLoading: 'loading ...',
|
||||
textNoMore: '-- END --',
|
||||
empty: {
|
||||
tip: '~ absolutely empty ~'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GlobalOption
|
||||
|
|
@ -1,437 +0,0 @@
|
|||
<template>
|
||||
<view class="mescroll-uni-warp">
|
||||
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
|
||||
<view class="mescroll-uni-content mescroll-render-touch"
|
||||
@touchstart="wxsBiz.touchstartEvent"
|
||||
@touchmove="wxsBiz.touchmoveEvent"
|
||||
@touchend="wxsBiz.touchendEvent"
|
||||
@touchcancel="wxsBiz.touchendEvent"
|
||||
:change:prop="wxsBiz.propObserver"
|
||||
:prop="wxsProp">
|
||||
|
||||
<!-- 状态栏 -->
|
||||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
|
||||
|
||||
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
|
||||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
|
||||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
|
||||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
|
||||
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
|
||||
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
|
||||
<view class="downwarp-mascot"></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<slot></slot>
|
||||
|
||||
<!-- 空布局 -->
|
||||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
|
||||
|
||||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
|
||||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
|
||||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
||||
<view v-show="upLoadType===1">
|
||||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
|
||||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
|
||||
</view>
|
||||
<!-- 无数据 -->
|
||||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 适配iPhoneX -->
|
||||
<view v-if="safearea" class="mescroll-safearea"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
|
||||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @tap="toTopClick"></mescroll-top>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
|
||||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- app, h5使用renderjs -->
|
||||
<!-- #ifdef APP-PLUS || H5 -->
|
||||
<script module="renderBiz" lang="renderjs">
|
||||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
|
||||
export default {
|
||||
mixins: [renderBiz]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
|
||||
<script>
|
||||
import MeScroll from '../../mescroll-uni/mescroll-uni.js';
|
||||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
|
||||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
|
||||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
|
||||
import GlobalOption from './mescroll-uni-option.js';
|
||||
|
||||
export default {
|
||||
mixins: [WxsMixin],
|
||||
components: {
|
||||
MescrollTop
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mescroll: null, // mescroll实例
|
||||
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
|
||||
downHight: 0, //下拉刷新: 容器高度
|
||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
|
||||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
|
||||
isShowEmpty: false, // 是否显示空布局
|
||||
isShowToTop: false, // 是否显示回到顶部按钮
|
||||
scrollTop: 0, // 滚动条的位置
|
||||
scrollAnim: false, // 是否开启滚动动画
|
||||
windowTop: 0, // 可使用窗口的顶部位置
|
||||
windowBottom: 0, // 可使用窗口的底部位置
|
||||
windowHeight: 0, // 可使用窗口的高度
|
||||
statusBarHeight: 0 // 状态栏高度
|
||||
}
|
||||
},
|
||||
props: {
|
||||
down: Object, // 下拉刷新的参数配置
|
||||
up: Object, // 上拉加载的参数配置
|
||||
i18n: Object, // 国际化的参数配置
|
||||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
|
||||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
|
||||
fixed: { // 是否通过fixed固定mescroll的高度, 默认true
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disableScroll: Boolean // 是否禁止滚动
|
||||
},
|
||||
computed: {
|
||||
// 是否使用fixed定位 (当height有值,则不使用)
|
||||
isFixed(){
|
||||
return !this.height && this.fixed
|
||||
},
|
||||
// mescroll的高度
|
||||
scrollHeight(){
|
||||
if (this.isFixed) {
|
||||
return "auto"
|
||||
} else if(this.height){
|
||||
return this.toPx(this.height) + 'px'
|
||||
}else{
|
||||
return "100%"
|
||||
}
|
||||
},
|
||||
// 下拉布局往下偏移的距离 (px)
|
||||
numTop() {
|
||||
return this.toPx(this.top)
|
||||
},
|
||||
fixedTop() {
|
||||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
|
||||
},
|
||||
padTop() {
|
||||
return !this.isFixed ? this.numTop + 'px' : 0
|
||||
},
|
||||
// 上拉布局往上偏移 (px)
|
||||
numBottom() {
|
||||
return this.toPx(this.bottom)
|
||||
},
|
||||
fixedBottom() {
|
||||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
|
||||
},
|
||||
padBottom() {
|
||||
return !this.isFixed ? this.numBottom + 'px' : 0
|
||||
},
|
||||
// 是否为重置下拉的状态
|
||||
isDownReset(){
|
||||
return this.downLoadType===3 || this.downLoadType===4
|
||||
},
|
||||
// 过渡
|
||||
transition() {
|
||||
return this.isDownReset ? 'transform 300ms' : ''
|
||||
},
|
||||
translateY() {
|
||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外
|
||||
},
|
||||
// 列表是否可滑动
|
||||
scrollable(){
|
||||
if(this.disableScroll) return false
|
||||
return this.downLoadType===0 || this.isDownReset
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading(){
|
||||
return this.downLoadType === 3
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate(){
|
||||
return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//number,rpx,upx,px,% --> px的数值
|
||||
toPx(num){
|
||||
if(typeof num === "string"){
|
||||
if (num.indexOf('px') !== -1) {
|
||||
if(num.indexOf('rpx') !== -1) { // "10rpx"
|
||||
num = num.replace('rpx', '');
|
||||
} else if(num.indexOf('upx') !== -1) { // "10upx"
|
||||
num = num.replace('upx', '');
|
||||
} else { // "10px"
|
||||
return Number(num.replace('px', ''))
|
||||
}
|
||||
}else if (num.indexOf('%') !== -1){
|
||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
|
||||
let rate = Number(num.replace("%","")) / 100
|
||||
return this.windowHeight * rate
|
||||
}
|
||||
}
|
||||
return num ? uni.upx2px(Number(num)) : 0
|
||||
},
|
||||
//注册列表滚动事件,用于下拉刷新和上拉加载
|
||||
scroll(e) {
|
||||
this.mescroll.scroll(e.detail, () => {
|
||||
this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
|
||||
})
|
||||
},
|
||||
// 点击空布局的按钮回调
|
||||
emptyClick() {
|
||||
this.$emit('emptyclick', this.mescroll)
|
||||
},
|
||||
// 点击回到顶部的按钮回调
|
||||
toTopClick() {
|
||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
|
||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
|
||||
},
|
||||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
|
||||
setClientHeight() {
|
||||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
|
||||
this.isExec = true; // 避免多次获取
|
||||
this.$nextTick(() => { // 确保dom已渲染
|
||||
this.getClientInfo(data=>{
|
||||
this.isExec = false;
|
||||
if (data) {
|
||||
this.mescroll.setClientHeight(data.height);
|
||||
} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
|
||||
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
|
||||
setTimeout(() => {
|
||||
this.setClientHeight()
|
||||
}, this.clientNum * 100)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
// 获取滚动区域的信息
|
||||
getClientInfo(success){
|
||||
let query = uni.createSelectorQuery();
|
||||
// #ifndef MP-ALIPAY || MP-DINGTALK
|
||||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
|
||||
// #endif
|
||||
let view = query.select('#' + this.viewId);
|
||||
view.boundingClientRect(data => {
|
||||
success(data)
|
||||
}).exec();
|
||||
}
|
||||
},
|
||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
|
||||
created() {
|
||||
let vm = this;
|
||||
|
||||
let diyOption = {
|
||||
// 下拉刷新的配置
|
||||
down: {
|
||||
inOffset() {
|
||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
outOffset() {
|
||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
onMoving(mescroll, rate, downHight) {
|
||||
// 下拉过程中的回调,滑动过程一直在执行;
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
showLoading(mescroll, downHight) {
|
||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
endDownScroll() {
|
||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
vm.downResetTimer && clearTimeout(vm.downResetTimer)
|
||||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
|
||||
if(vm.downLoadType===4) vm.downLoadType = 0
|
||||
},300)
|
||||
},
|
||||
// 派发下拉刷新的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('down', mescroll)
|
||||
}
|
||||
},
|
||||
// 上拉加载的配置
|
||||
up: {
|
||||
// 显示加载中的回调
|
||||
showLoading() {
|
||||
vm.upLoadType = 1;
|
||||
},
|
||||
// 显示无更多数据的回调
|
||||
showNoMore() {
|
||||
vm.upLoadType = 2;
|
||||
},
|
||||
// 隐藏上拉加载的回调
|
||||
hideUpScroll(mescroll) {
|
||||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
|
||||
},
|
||||
// 空布局
|
||||
empty: {
|
||||
onShow(isShow) { // 显示隐藏的回调
|
||||
vm.isShowEmpty = isShow;
|
||||
}
|
||||
},
|
||||
// 回到顶部
|
||||
toTop: {
|
||||
onShow(isShow) { // 显示隐藏的回调
|
||||
vm.isShowToTop = isShow;
|
||||
}
|
||||
},
|
||||
// 派发上拉加载的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('up', mescroll);
|
||||
// 更新容器的高度 (多mescroll的情况)
|
||||
vm.setClientHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let i18nType = mescrollI18n.getType() // 当前语言类型
|
||||
let i18nOption = {type: i18nType} // 国际化配置
|
||||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
|
||||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
|
||||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
|
||||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
|
||||
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
|
||||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
|
||||
|
||||
// 初始化MeScroll对象
|
||||
vm.mescroll = new MeScroll(myOption);
|
||||
vm.mescroll.viewId = vm.viewId; // 附带id
|
||||
// 挂载语言包
|
||||
vm.mescroll.i18n = i18nOption;
|
||||
// init回调mescroll对象
|
||||
vm.$emit('init', vm.mescroll);
|
||||
|
||||
// 设置高度
|
||||
const sys = uni.getSystemInfoSync();
|
||||
if(sys.windowTop) vm.windowTop = sys.windowTop;
|
||||
if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
|
||||
if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
|
||||
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
|
||||
// 使down的bottomOffset生效
|
||||
vm.mescroll.setBodyHeight(sys.windowHeight);
|
||||
|
||||
// 因为使用的是scrollview,这里需自定义scrollTo
|
||||
vm.mescroll.resetScrollTo((y, t) => {
|
||||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
|
||||
if(typeof y === 'string'){
|
||||
// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
|
||||
vm.getClientInfo(function(rect){
|
||||
let mescrollTop = rect.top // mescroll到顶部的距离
|
||||
let selector;
|
||||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
|
||||
selector = '#'+y // 不带#和. 则默认为id选择器
|
||||
}else{
|
||||
selector = y
|
||||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
|
||||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
|
||||
selector = y.split('>>>')[1].trim()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
|
||||
if (rect) {
|
||||
let curY = vm.mescroll.getScrollTop()
|
||||
let top = rect.top - mescrollTop
|
||||
top += curY
|
||||
if(!vm.isFixed) top -= vm.numTop
|
||||
vm.scrollTop = curY;
|
||||
vm.$nextTick(function() {
|
||||
vm.scrollTop = top
|
||||
})
|
||||
} else{
|
||||
console.error(selector + ' does not exist');
|
||||
}
|
||||
}).exec()
|
||||
})
|
||||
return;
|
||||
}
|
||||
let curY = vm.mescroll.getScrollTop()
|
||||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
|
||||
vm.scrollTop = curY;
|
||||
vm.$nextTick(function() {
|
||||
vm.scrollTop = y
|
||||
})
|
||||
} else {
|
||||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
|
||||
vm.scrollTop = step
|
||||
}, t)
|
||||
}
|
||||
})
|
||||
|
||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
|
||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
|
||||
vm.mescroll.optUp.toTop.safearea = vm.safearea;
|
||||
}
|
||||
// 全局配置监听
|
||||
uni.$on("setMescrollGlobalOption", options=>{
|
||||
if(!options) return;
|
||||
let i18nType = options.i18n ? options.i18n.type : null
|
||||
if(i18nType && vm.mescroll.i18n.type != i18nType){
|
||||
vm.mescroll.i18n.type = i18nType
|
||||
mescrollI18n.setType(i18nType)
|
||||
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
|
||||
}
|
||||
if(options.down){
|
||||
let down = MeScroll.extend({}, options.down)
|
||||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
|
||||
}
|
||||
if(options.up){
|
||||
let up = MeScroll.extend({}, options.up)
|
||||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
|
||||
}
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
// 设置容器的高度
|
||||
this.setClientHeight()
|
||||
},
|
||||
destroyed() {
|
||||
// 注销全局配置监听
|
||||
uni.$off("setMescrollGlobalOption")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "../../mescroll-uni/mescroll-uni.css";
|
||||
@import "../../mescroll-uni/components/mescroll-down.css";
|
||||
@import "../../mescroll-uni/components/mescroll-up.css";
|
||||
@import "./components/mescroll-down.css";
|
||||
</style>
|
||||
|
|
@ -1,44 +0,0 @@
|
|||
/*下拉刷新--上下箭头*/
|
||||
.mescroll-downwarp .downwarp-arrow {
|
||||
display: inline-block;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
margin: 10px;
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-arrow.png);
|
||||
background-size: contain;
|
||||
vertical-align: middle;
|
||||
transition: all 300ms;
|
||||
}
|
||||
|
||||
/*下拉刷新--旋转进度条*/
|
||||
.mescroll-downwarp .downwarp-progress{
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
margin: auto;
|
||||
background-size: contain;
|
||||
animation: progressRotate 0.6s steps(6, start) infinite;
|
||||
}
|
||||
@keyframes progressRotate {
|
||||
0% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
|
||||
}
|
||||
16% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
|
||||
}
|
||||
32% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
|
||||
}
|
||||
48% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
|
||||
}
|
||||
64% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
|
||||
}
|
||||
80% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
|
||||
}
|
||||
100% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +0,0 @@
|
|||
<!-- 下拉刷新区域 -->
|
||||
<template>
|
||||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<view v-if="isDownLoading" class="downwarp-progress"></view>
|
||||
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
|
||||
<view class="downwarp-tip">{{ downText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
option: Object, // down的配置项
|
||||
type: Number // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
|
||||
},
|
||||
computed: {
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
||||
mOption() {
|
||||
return this.option || {};
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading() {
|
||||
return this.type === 3;
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate() {
|
||||
return this.type === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
|
||||
},
|
||||
// 文本提示
|
||||
downText() {
|
||||
switch (this.type) {
|
||||
case 1:
|
||||
return this.mOption.textInOffset;
|
||||
case 2:
|
||||
return this.mOption.textOutOffset;
|
||||
case 3:
|
||||
return this.mOption.textLoading;
|
||||
case 4:
|
||||
return this.mOption.textLoading;
|
||||
default:
|
||||
return this.mOption.textInOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '../../../mescroll-uni/components/mescroll-down.css';
|
||||
@import './mescroll-down.css';
|
||||
</style>
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*上拉加载--旋转进度条*/
|
||||
.mescroll-upwarp .upwarp-progress {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border: none;
|
||||
margin: auto;
|
||||
background-size: contain;
|
||||
animation: progressRotate 0.6s steps(6, start) infinite;
|
||||
}
|
||||
@keyframes progressRotate {
|
||||
0% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
|
||||
}
|
||||
16% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
|
||||
}
|
||||
32% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
|
||||
}
|
||||
48% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
|
||||
}
|
||||
64% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
|
||||
}
|
||||
80% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
|
||||
}
|
||||
100% {
|
||||
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,40 +0,0 @@
|
|||
<!-- 上拉加载区域 -->
|
||||
<template>
|
||||
<view class="mescroll-upwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
||||
<view v-show="isUpLoading">
|
||||
<view class="upwarp-progress mescroll-rotate"></view>
|
||||
<view class="upwarp-tip">{{ mOption.textLoading }}</view>
|
||||
</view>
|
||||
<!-- 无数据 -->
|
||||
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
option: Object, // up的配置项
|
||||
type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
|
||||
},
|
||||
computed: {
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
||||
mOption() {
|
||||
return this.option || {};
|
||||
},
|
||||
// 加载中
|
||||
isUpLoading() {
|
||||
return this.type === 1;
|
||||
},
|
||||
// 没有更多了
|
||||
isUpNoMore() {
|
||||
return this.type === 2;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import '../../../mescroll-uni/components/mescroll-up.css';
|
||||
@import './mescroll-up.css';
|
||||
</style>
|
||||
|
|
@ -1,380 +0,0 @@
|
|||
<template>
|
||||
<view
|
||||
class="mescroll-body mescroll-render-touch"
|
||||
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}"
|
||||
:class="{'mescorll-sticky': sticky}"
|
||||
@touchstart="wxsBiz.touchstartEvent"
|
||||
@touchmove="wxsBiz.touchmoveEvent"
|
||||
@touchend="wxsBiz.touchendEvent"
|
||||
@touchcancel="wxsBiz.touchendEvent"
|
||||
:change:prop="wxsBiz.propObserver"
|
||||
:prop="wxsProp"
|
||||
>
|
||||
|
||||
<!-- 状态栏 -->
|
||||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
|
||||
|
||||
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
|
||||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
|
||||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
|
||||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<view v-if="isDownLoading" class="downwarp-progress"></view>
|
||||
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
|
||||
<view class="downwarp-tip">{{ downText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<slot></slot>
|
||||
|
||||
<!-- 空布局 -->
|
||||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
|
||||
|
||||
<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
|
||||
<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
|
||||
<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
||||
<view v-show="upLoadType===1">
|
||||
<view class="upwarp-progress mescroll-rotate"></view>
|
||||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
|
||||
</view>
|
||||
<!-- 无数据 -->
|
||||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 适配iPhoneX -->
|
||||
<view v-if="safearea" class="mescroll-safearea"></view>
|
||||
|
||||
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
|
||||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @tap="toTopClick"></mescroll-top>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
|
||||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- app, h5使用renderjs -->
|
||||
<!-- #ifdef APP-PLUS || H5 -->
|
||||
<script module="renderBiz" lang="renderjs">
|
||||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
|
||||
export default {
|
||||
mixins: [renderBiz]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
|
||||
<script>
|
||||
import MeScroll from '../../mescroll-uni/mescroll-uni.js';
|
||||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
|
||||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
|
||||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
|
||||
import GlobalOption from './mescroll-uni-option.js';
|
||||
|
||||
export default {
|
||||
mixins: [WxsMixin],
|
||||
components: {
|
||||
MescrollTop
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mescroll: null, // mescroll实例
|
||||
downHight: 0, //下拉刷新: 容器高度
|
||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
|
||||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
|
||||
isShowEmpty: false, // 是否显示空布局
|
||||
isShowToTop: false, // 是否显示回到顶部按钮
|
||||
windowHeight: 0, // 可使用窗口的高度
|
||||
windowBottom: 0, // 可使用窗口的底部位置
|
||||
statusBarHeight: 0 // 状态栏高度
|
||||
};
|
||||
},
|
||||
props: {
|
||||
down: Object, // 下拉刷新的参数配置
|
||||
up: Object, // 上拉加载的参数配置
|
||||
i18n: Object, // 国际化的参数配置
|
||||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
|
||||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
|
||||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
|
||||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
sticky: Boolean // 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法会隐藏
|
||||
},
|
||||
computed: {
|
||||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
|
||||
minHeight(){
|
||||
return this.toPx(this.height || '100%') + 'px'
|
||||
},
|
||||
// 下拉布局往下偏移的距离 (px)
|
||||
numTop() {
|
||||
return this.toPx(this.top)
|
||||
},
|
||||
padTop() {
|
||||
return this.numTop + 'px';
|
||||
},
|
||||
// 上拉布局往上偏移 (px)
|
||||
numBottom() {
|
||||
return this.toPx(this.bottom);
|
||||
},
|
||||
padBottom() {
|
||||
return this.numBottom + 'px';
|
||||
},
|
||||
// 是否为重置下拉的状态
|
||||
isDownReset() {
|
||||
return this.downLoadType === 3 || this.downLoadType === 4;
|
||||
},
|
||||
// 过渡
|
||||
transition() {
|
||||
return this.isDownReset ? 'transform 300ms' : '';
|
||||
},
|
||||
translateY() {
|
||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading() {
|
||||
return this.downLoadType === 3;
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate() {
|
||||
return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
|
||||
},
|
||||
// 文本提示
|
||||
downText() {
|
||||
if(!this.mescroll) return "";
|
||||
switch (this.downLoadType) {
|
||||
case 1:
|
||||
return this.mescroll.optDown.textInOffset;
|
||||
case 2:
|
||||
return this.mescroll.optDown.textOutOffset;
|
||||
case 3:
|
||||
return this.mescroll.optDown.textLoading;
|
||||
case 4:
|
||||
return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
|
||||
default:
|
||||
return this.mescroll.optDown.textInOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//number,rpx,upx,px,% --> px的数值
|
||||
toPx(num) {
|
||||
if (typeof num === 'string') {
|
||||
if (num.indexOf('px') !== -1) {
|
||||
if (num.indexOf('rpx') !== -1) {
|
||||
// "10rpx"
|
||||
num = num.replace('rpx', '');
|
||||
} else if (num.indexOf('upx') !== -1) {
|
||||
// "10upx"
|
||||
num = num.replace('upx', '');
|
||||
} else {
|
||||
// "10px"
|
||||
return Number(num.replace('px', ''));
|
||||
}
|
||||
} else if (num.indexOf('%') !== -1) {
|
||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
|
||||
let rate = Number(num.replace('%', '')) / 100;
|
||||
return this.windowHeight * rate;
|
||||
}
|
||||
}
|
||||
return num ? uni.upx2px(Number(num)) : 0;
|
||||
},
|
||||
// 点击空布局的按钮回调
|
||||
emptyClick() {
|
||||
this.$emit('emptyclick', this.mescroll);
|
||||
},
|
||||
// 点击回到顶部的按钮回调
|
||||
toTopClick() {
|
||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
|
||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
|
||||
}
|
||||
},
|
||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
|
||||
created() {
|
||||
let vm = this;
|
||||
|
||||
let diyOption = {
|
||||
// 下拉刷新的配置
|
||||
down: {
|
||||
inOffset() {
|
||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
outOffset() {
|
||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
onMoving(mescroll, rate, downHight) {
|
||||
// 下拉过程中的回调,滑动过程一直在执行;
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
showLoading(mescroll, downHight) {
|
||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
beforeEndDownScroll(mescroll){
|
||||
vm.downLoadType = 4;
|
||||
return mescroll.optDown.beforeEndDelay // 延时结束的时长
|
||||
},
|
||||
endDownScroll() {
|
||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} // 移除重置倒计时
|
||||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset
|
||||
if(vm.downLoadType === 4) vm.downLoadType = 0
|
||||
},300)
|
||||
},
|
||||
// 派发下拉刷新的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('down', mescroll);
|
||||
}
|
||||
},
|
||||
// 上拉加载的配置
|
||||
up: {
|
||||
// 显示加载中的回调
|
||||
showLoading() {
|
||||
vm.upLoadType = 1;
|
||||
},
|
||||
// 显示无更多数据的回调
|
||||
showNoMore() {
|
||||
vm.upLoadType = 2;
|
||||
},
|
||||
// 隐藏上拉加载的回调
|
||||
hideUpScroll(mescroll) {
|
||||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
|
||||
},
|
||||
// 空布局
|
||||
empty: {
|
||||
onShow(isShow) {
|
||||
// 显示隐藏的回调
|
||||
vm.isShowEmpty = isShow;
|
||||
}
|
||||
},
|
||||
// 回到顶部
|
||||
toTop: {
|
||||
onShow(isShow) {
|
||||
// 显示隐藏的回调
|
||||
vm.isShowToTop = isShow;
|
||||
}
|
||||
},
|
||||
// 派发上拉加载的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('up', mescroll);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let i18nType = mescrollI18n.getType() // 当前语言类型
|
||||
let i18nOption = {type: i18nType} // 国际化配置
|
||||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
|
||||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
|
||||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
|
||||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
|
||||
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
|
||||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
|
||||
|
||||
// 初始化MeScroll对象
|
||||
vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
|
||||
// 挂载语言包
|
||||
vm.mescroll.i18n = i18nOption;
|
||||
// init回调mescroll对象
|
||||
vm.$emit('init', vm.mescroll);
|
||||
|
||||
// 设置高度
|
||||
const sys = uni.getSystemInfoSync();
|
||||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
|
||||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
|
||||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
|
||||
// 使down的bottomOffset生效
|
||||
vm.mescroll.setBodyHeight(sys.windowHeight);
|
||||
|
||||
// 因为使用的是page的scroll,这里需自定义scrollTo
|
||||
vm.mescroll.resetScrollTo((y, t) => {
|
||||
if(typeof y === 'string'){
|
||||
// 滚动到指定view (y为css选择器)
|
||||
setTimeout(()=>{ // 延时确保view已渲染; 不使用$nextTick
|
||||
let selector;
|
||||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
|
||||
selector = '#'+y // 不带#和. 则默认为id选择器
|
||||
}else{
|
||||
selector = y
|
||||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
|
||||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
|
||||
selector = y.split('>>>')[1].trim()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
|
||||
if (rect) {
|
||||
let top = rect.top
|
||||
top += vm.mescroll.getScrollTop()
|
||||
uni.pageScrollTo({
|
||||
scrollTop: top,
|
||||
duration: t
|
||||
})
|
||||
} else{
|
||||
console.error(selector + ' does not exist');
|
||||
}
|
||||
}).exec()
|
||||
},30)
|
||||
} else{
|
||||
// 滚动到指定位置 (y必须为数字)
|
||||
uni.pageScrollTo({
|
||||
scrollTop: y,
|
||||
duration: t
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
|
||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
|
||||
vm.mescroll.optUp.toTop.safearea = vm.safearea;
|
||||
}
|
||||
|
||||
// 全局配置监听
|
||||
uni.$on("setMescrollGlobalOption", options=>{
|
||||
if(!options) return;
|
||||
let i18nType = options.i18n ? options.i18n.type : null
|
||||
if(i18nType && vm.mescroll.i18n.type != i18nType){
|
||||
vm.mescroll.i18n.type = i18nType
|
||||
mescrollI18n.setType(i18nType)
|
||||
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
|
||||
}
|
||||
if(options.down){
|
||||
let down = MeScroll.extend({}, options.down)
|
||||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
|
||||
}
|
||||
if(options.up){
|
||||
let up = MeScroll.extend({}, options.up)
|
||||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
|
||||
}
|
||||
})
|
||||
},
|
||||
destroyed() {
|
||||
// 注销全局配置监听
|
||||
uni.$off("setMescrollGlobalOption")
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "../../mescroll-uni/mescroll-uni.css";
|
||||
@import "../../mescroll-uni/components/mescroll-down.css";
|
||||
@import "../../mescroll-uni/components/mescroll-up.css";
|
||||
@import "./components/mescroll-down.css";
|
||||
@import "./components/mescroll-up.css";
|
||||
</style>
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// 全局配置
|
||||
// mescroll-body 和 mescroll-uni 通用
|
||||
const GlobalOption = {
|
||||
down: {
|
||||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
||||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
||||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
||||
},
|
||||
up: {
|
||||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
||||
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
|
||||
toTop: {
|
||||
// 回到顶部按钮,需配置src才显示
|
||||
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
|
||||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
|
||||
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
},
|
||||
empty: {
|
||||
use: true, // 是否显示空布局
|
||||
icon: "/static/images/user/empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
||||
}
|
||||
},
|
||||
// 国际化配置
|
||||
i18n: {
|
||||
// 中文
|
||||
zh: {
|
||||
down: {
|
||||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
||||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
||||
textSuccess: '加载成功', // 加载成功的文本
|
||||
textErr: '加载失败', // 加载失败的文本
|
||||
},
|
||||
up: {
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
||||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
||||
empty: {
|
||||
tip: '~ 空空如也 ~' // 空提示
|
||||
}
|
||||
}
|
||||
},
|
||||
// 英文
|
||||
en: {
|
||||
down: {
|
||||
textInOffset: 'drop down refresh',
|
||||
textOutOffset: 'release updates',
|
||||
textLoading: 'loading ...',
|
||||
textSuccess: 'loaded successfully',
|
||||
textErr: 'loading failed'
|
||||
},
|
||||
up: {
|
||||
textLoading: 'loading ...',
|
||||
textNoMore: '-- END --',
|
||||
empty: {
|
||||
tip: '~ absolutely empty ~'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GlobalOption
|
||||
|
|
@ -1,462 +0,0 @@
|
|||
<template>
|
||||
<view class="mescroll-uni-warp">
|
||||
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
|
||||
<view class="mescroll-uni-content mescroll-render-touch"
|
||||
@touchstart="wxsBiz.touchstartEvent"
|
||||
@touchmove="wxsBiz.touchmoveEvent"
|
||||
@touchend="wxsBiz.touchendEvent"
|
||||
@touchcancel="wxsBiz.touchendEvent"
|
||||
:change:prop="wxsBiz.propObserver"
|
||||
:prop="wxsProp">
|
||||
|
||||
<!-- 状态栏 -->
|
||||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
|
||||
|
||||
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
|
||||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
|
||||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
|
||||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<view v-if="isDownLoading" class="downwarp-progress"></view>
|
||||
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
|
||||
<view class="downwarp-tip">{{ downText }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<slot></slot>
|
||||
|
||||
<!-- 空布局 -->
|
||||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
|
||||
|
||||
<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
|
||||
<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
|
||||
<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
||||
<view v-show="upLoadType===1">
|
||||
<view class="upwarp-progress mescroll-rotate"></view>
|
||||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
|
||||
</view>
|
||||
<!-- 无数据 -->
|
||||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 适配iPhoneX -->
|
||||
<view v-if="safearea" class="mescroll-safearea"></view>
|
||||
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
|
||||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @tap="toTopClick"></mescroll-top>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
|
||||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- app, h5使用renderjs -->
|
||||
<!-- #ifdef APP-PLUS || H5 -->
|
||||
<script module="renderBiz" lang="renderjs">
|
||||
import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
|
||||
export default {
|
||||
mixins: [renderBiz]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
|
||||
<script>
|
||||
import MeScroll from '../../mescroll-uni/mescroll-uni.js';
|
||||
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
|
||||
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
|
||||
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
|
||||
import GlobalOption from './mescroll-uni-option.js';
|
||||
|
||||
export default {
|
||||
mixins: [WxsMixin],
|
||||
components: {
|
||||
MescrollTop
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mescroll: null, // mescroll实例
|
||||
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
|
||||
downHight: 0, //下拉刷新: 容器高度
|
||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
|
||||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
|
||||
isShowEmpty: false, // 是否显示空布局
|
||||
isShowToTop: false, // 是否显示回到顶部按钮
|
||||
scrollTop: 0, // 滚动条的位置
|
||||
scrollAnim: false, // 是否开启滚动动画
|
||||
windowTop: 0, // 可使用窗口的顶部位置
|
||||
windowBottom: 0, // 可使用窗口的底部位置
|
||||
windowHeight: 0, // 可使用窗口的高度
|
||||
statusBarHeight: 0 // 状态栏高度
|
||||
}
|
||||
},
|
||||
props: {
|
||||
down: Object, // 下拉刷新的参数配置
|
||||
up: Object, // 上拉加载的参数配置
|
||||
i18n: Object, // 国际化的参数配置
|
||||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
|
||||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
|
||||
fixed: { // 是否通过fixed固定mescroll的高度, 默认true
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disableScroll: Boolean // 是否禁止滚动
|
||||
},
|
||||
computed: {
|
||||
// 是否使用fixed定位 (当height有值,则不使用)
|
||||
isFixed(){
|
||||
return !this.height && this.fixed
|
||||
},
|
||||
// mescroll的高度
|
||||
scrollHeight(){
|
||||
if (this.isFixed) {
|
||||
return "auto"
|
||||
} else if(this.height){
|
||||
return this.toPx(this.height) + 'px'
|
||||
}else{
|
||||
return "100%"
|
||||
}
|
||||
},
|
||||
// 下拉布局往下偏移的距离 (px)
|
||||
numTop() {
|
||||
return this.toPx(this.top)
|
||||
},
|
||||
fixedTop() {
|
||||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
|
||||
},
|
||||
padTop() {
|
||||
return !this.isFixed ? this.numTop + 'px' : 0
|
||||
},
|
||||
// 上拉布局往上偏移 (px)
|
||||
numBottom() {
|
||||
return this.toPx(this.bottom)
|
||||
},
|
||||
fixedBottom() {
|
||||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
|
||||
},
|
||||
padBottom() {
|
||||
return !this.isFixed ? this.numBottom + 'px' : 0
|
||||
},
|
||||
// 是否为重置下拉的状态
|
||||
isDownReset(){
|
||||
return this.downLoadType===3 || this.downLoadType===4
|
||||
},
|
||||
// 过渡
|
||||
transition() {
|
||||
return this.isDownReset ? 'transform 300ms' : ''
|
||||
},
|
||||
translateY() {
|
||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外
|
||||
},
|
||||
// 列表是否可滑动
|
||||
scrollable(){
|
||||
if(this.disableScroll) return false
|
||||
return this.downLoadType===0 || this.isDownReset
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading() {
|
||||
return this.downLoadType === 3;
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate() {
|
||||
return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
|
||||
},
|
||||
// 文本提示
|
||||
downText() {
|
||||
if(!this.mescroll) return "";
|
||||
switch (this.downLoadType) {
|
||||
case 1:
|
||||
return this.mescroll.optDown.textInOffset;
|
||||
case 2:
|
||||
return this.mescroll.optDown.textOutOffset;
|
||||
case 3:
|
||||
return this.mescroll.optDown.textLoading;
|
||||
case 4:
|
||||
return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
|
||||
default:
|
||||
return this.mescroll.optDown.textInOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//number,rpx,upx,px,% --> px的数值
|
||||
toPx(num){
|
||||
if(typeof num === "string"){
|
||||
if (num.indexOf('px') !== -1) {
|
||||
if(num.indexOf('rpx') !== -1) { // "10rpx"
|
||||
num = num.replace('rpx', '');
|
||||
} else if(num.indexOf('upx') !== -1) { // "10upx"
|
||||
num = num.replace('upx', '');
|
||||
} else { // "10px"
|
||||
return Number(num.replace('px', ''))
|
||||
}
|
||||
}else if (num.indexOf('%') !== -1){
|
||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
|
||||
let rate = Number(num.replace("%","")) / 100
|
||||
return this.windowHeight * rate
|
||||
}
|
||||
}
|
||||
return num ? uni.upx2px(Number(num)) : 0
|
||||
},
|
||||
//注册列表滚动事件,用于下拉刷新和上拉加载
|
||||
scroll(e) {
|
||||
this.mescroll.scroll(e.detail, () => {
|
||||
this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
|
||||
})
|
||||
},
|
||||
// 点击空布局的按钮回调
|
||||
emptyClick() {
|
||||
this.$emit('emptyclick', this.mescroll)
|
||||
},
|
||||
// 点击回到顶部的按钮回调
|
||||
toTopClick() {
|
||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
|
||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
|
||||
},
|
||||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
|
||||
setClientHeight() {
|
||||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
|
||||
this.isExec = true; // 避免多次获取
|
||||
this.$nextTick(() => { // 确保dom已渲染
|
||||
this.getClientInfo(data=>{
|
||||
this.isExec = false;
|
||||
if (data) {
|
||||
this.mescroll.setClientHeight(data.height);
|
||||
} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
|
||||
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
|
||||
setTimeout(() => {
|
||||
this.setClientHeight()
|
||||
}, this.clientNum * 100)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
// 获取滚动区域的信息
|
||||
getClientInfo(success){
|
||||
let query = uni.createSelectorQuery();
|
||||
// #ifndef MP-ALIPAY || MP-DINGTALK
|
||||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
|
||||
// #endif
|
||||
let view = query.select('#' + this.viewId);
|
||||
view.boundingClientRect(data => {
|
||||
success(data)
|
||||
}).exec();
|
||||
}
|
||||
},
|
||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
|
||||
created() {
|
||||
let vm = this;
|
||||
|
||||
let diyOption = {
|
||||
// 下拉刷新的配置
|
||||
down: {
|
||||
inOffset() {
|
||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
outOffset() {
|
||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
onMoving(mescroll, rate, downHight) {
|
||||
// 下拉过程中的回调,滑动过程一直在执行;
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
showLoading(mescroll, downHight) {
|
||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
beforeEndDownScroll(mescroll){
|
||||
vm.downLoadType = 4;
|
||||
return mescroll.optDown.beforeEndDelay // 延时结束的时长
|
||||
},
|
||||
endDownScroll() {
|
||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
vm.downResetTimer && clearTimeout(vm.downResetTimer)
|
||||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
|
||||
if(vm.downLoadType===4) vm.downLoadType = 0
|
||||
},300)
|
||||
},
|
||||
// 派发下拉刷新的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('down', mescroll)
|
||||
}
|
||||
},
|
||||
// 上拉加载的配置
|
||||
up: {
|
||||
// 显示加载中的回调
|
||||
showLoading() {
|
||||
vm.upLoadType = 1;
|
||||
},
|
||||
// 显示无更多数据的回调
|
||||
showNoMore() {
|
||||
vm.upLoadType = 2;
|
||||
},
|
||||
// 隐藏上拉加载的回调
|
||||
hideUpScroll(mescroll) {
|
||||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
|
||||
},
|
||||
// 空布局
|
||||
empty: {
|
||||
onShow(isShow) { // 显示隐藏的回调
|
||||
vm.isShowEmpty = isShow;
|
||||
}
|
||||
},
|
||||
// 回到顶部
|
||||
toTop: {
|
||||
onShow(isShow) { // 显示隐藏的回调
|
||||
vm.isShowToTop = isShow;
|
||||
}
|
||||
},
|
||||
// 派发上拉加载的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('up', mescroll);
|
||||
// 更新容器的高度 (多mescroll的情况)
|
||||
vm.setClientHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let i18nType = mescrollI18n.getType() // 当前语言类型
|
||||
let i18nOption = {type: i18nType} // 国际化配置
|
||||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
|
||||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
|
||||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
|
||||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
|
||||
let myOption = JSON.parse(JSON.stringify({
|
||||
'down': vm.down,
|
||||
'up': vm.up
|
||||
})) // 深拷贝,避免对props的影响
|
||||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
|
||||
|
||||
// 初始化MeScroll对象
|
||||
vm.mescroll = new MeScroll(myOption);
|
||||
vm.mescroll.viewId = vm.viewId; // 附带id
|
||||
// 挂载语言包
|
||||
vm.mescroll.i18n = i18nOption;
|
||||
// init回调mescroll对象
|
||||
vm.$emit('init', vm.mescroll);
|
||||
|
||||
// 设置高度
|
||||
const sys = uni.getSystemInfoSync();
|
||||
if(sys.windowTop) vm.windowTop = sys.windowTop;
|
||||
if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
|
||||
if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
|
||||
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
|
||||
// 使down的bottomOffset生效
|
||||
vm.mescroll.setBodyHeight(sys.windowHeight);
|
||||
|
||||
// 因为使用的是scrollview,这里需自定义scrollTo
|
||||
vm.mescroll.resetScrollTo((y, t) => {
|
||||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
|
||||
if(typeof y === 'string'){
|
||||
// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
|
||||
vm.getClientInfo(function(rect){
|
||||
let mescrollTop = rect.top // mescroll到顶部的距离
|
||||
let selector;
|
||||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
|
||||
selector = '#'+y // 不带#和. 则默认为id选择器
|
||||
}else{
|
||||
selector = y
|
||||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
|
||||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
|
||||
selector = y.split('>>>')[1].trim()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
|
||||
if (rect) {
|
||||
let curY = vm.mescroll.getScrollTop()
|
||||
let top = rect.top - mescrollTop
|
||||
top += curY
|
||||
if(!vm.isFixed) top -= vm.numTop
|
||||
vm.scrollTop = curY;
|
||||
vm.$nextTick(function() {
|
||||
vm.scrollTop = top
|
||||
})
|
||||
} else{
|
||||
console.error(selector + ' does not exist');
|
||||
}
|
||||
}).exec()
|
||||
})
|
||||
return;
|
||||
}
|
||||
let curY = vm.mescroll.getScrollTop()
|
||||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
|
||||
vm.scrollTop = curY;
|
||||
vm.$nextTick(function() {
|
||||
vm.scrollTop = y
|
||||
})
|
||||
} else {
|
||||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
|
||||
vm.scrollTop = step
|
||||
}, t)
|
||||
}
|
||||
})
|
||||
|
||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
|
||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
|
||||
vm.mescroll.optUp.toTop.safearea = vm.safearea;
|
||||
}
|
||||
|
||||
// 全局配置监听
|
||||
uni.$on("setMescrollGlobalOption", options=>{
|
||||
if(!options) return;
|
||||
let i18nType = options.i18n ? options.i18n.type : null
|
||||
if(i18nType && vm.mescroll.i18n.type != i18nType){
|
||||
vm.mescroll.i18n.type = i18nType
|
||||
mescrollI18n.setType(i18nType)
|
||||
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
|
||||
}
|
||||
if(options.down){
|
||||
let down = MeScroll.extend({}, options.down)
|
||||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
|
||||
}
|
||||
if(options.up){
|
||||
let up = MeScroll.extend({}, options.up)
|
||||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
|
||||
}
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
// 设置容器的高度
|
||||
this.setClientHeight()
|
||||
},
|
||||
destroyed() {
|
||||
// 注销全局配置监听
|
||||
uni.$off("setMescrollGlobalOption")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "../../mescroll-uni/mescroll-uni.css";
|
||||
@import "../../mescroll-uni/components/mescroll-down.css";
|
||||
@import "../../mescroll-uni/components/mescroll-up.css";
|
||||
@import "./components/mescroll-down.css";
|
||||
@import "./components/mescroll-up.css";
|
||||
</style>
|
||||
|
|
@ -1,116 +0,0 @@
|
|||
<!--空布局:
|
||||
遵循easycom规范, 可作为独立的组件, 不使用mescroll的页面也能使用:
|
||||
<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
|
||||
-->
|
||||
<template>
|
||||
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
|
||||
<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
|
||||
<view v-if="tip" class="empty-tip">{{ tip }}</view>
|
||||
<view v-if="btnText" class="empty-btn" @tap="emptyClick">{{ btnText }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
// 引入全局配置
|
||||
import GlobalOption from '../mescroll-uni/mescroll-uni-option.js';
|
||||
// 引入国际化工具类
|
||||
import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
|
||||
export default {
|
||||
props: {
|
||||
// empty的配置项: 默认为GlobalOption.up.empty
|
||||
option: {
|
||||
type: Object,
|
||||
default() {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
},
|
||||
// 使用computed获取配置,用于支持option的动态配置
|
||||
computed: {
|
||||
// 图标
|
||||
icon() {
|
||||
if (this.option.icon != null) { // 此处不使用短路求值, 用于支持传空串不显示图标
|
||||
return this.option.icon
|
||||
} else{
|
||||
let i18nType = mescrollI18n.getType() // 国际化配置
|
||||
if (this.option.i18n) {
|
||||
return this.option.i18n[i18nType].icon
|
||||
} else{
|
||||
return GlobalOption.i18n[i18nType].up.empty.icon || GlobalOption.up.empty.icon
|
||||
}
|
||||
}
|
||||
},
|
||||
// 文本提示
|
||||
tip() {
|
||||
if (this.option.tip != null) { // 支持传空串不显示文本提示
|
||||
return this.option.tip
|
||||
} else{
|
||||
let i18nType = mescrollI18n.getType() // 国际化配置
|
||||
if (this.option.i18n) {
|
||||
return this.option.i18n[i18nType].tip
|
||||
} else{
|
||||
return GlobalOption.i18n[i18nType].up.empty.tip || GlobalOption.up.empty.tip
|
||||
}
|
||||
}
|
||||
},
|
||||
// 按钮文本
|
||||
btnText() {
|
||||
if (this.option.i18n) {
|
||||
let i18nType = mescrollI18n.getType() // 国际化配置
|
||||
return this.option.i18n[i18nType].btnText
|
||||
} else{
|
||||
return this.option.btnText
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 点击按钮
|
||||
emptyClick() {
|
||||
this.$emit('emptyclick');
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 无任何数据的空布局 */
|
||||
.mescroll-empty {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 100rpx 50rpx;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mescroll-empty.empty-fixed {
|
||||
z-index: 99;
|
||||
position: absolute; /*transform会使fixed失效,最终会降级为absolute */
|
||||
top: 100rpx;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.mescroll-empty .empty-icon {
|
||||
width: 280rpx;
|
||||
height: 280rpx;
|
||||
}
|
||||
|
||||
.mescroll-empty .empty-tip {
|
||||
margin-top: 20rpx;
|
||||
font-size: 24rpx;
|
||||
color: gray;
|
||||
}
|
||||
|
||||
.mescroll-empty .empty-btn {
|
||||
display: inline-block;
|
||||
margin-top: 40rpx;
|
||||
min-width: 200rpx;
|
||||
padding: 18rpx;
|
||||
font-size: 28rpx;
|
||||
border: 1rpx solid #e04b28;
|
||||
border-radius: 60rpx;
|
||||
color: #e04b28;
|
||||
}
|
||||
|
||||
.mescroll-empty .empty-btn:active {
|
||||
opacity: 0.75;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/* 下拉刷新区域 */
|
||||
.mescroll-downwarp {
|
||||
position: absolute;
|
||||
top: -100%;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 下拉刷新--内容区,定位于区域底部 */
|
||||
.mescroll-downwarp .downwarp-content {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
min-height: 60rpx;
|
||||
padding: 20rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 下拉刷新--提示文本 */
|
||||
.mescroll-downwarp .downwarp-tip {
|
||||
display: inline-block;
|
||||
font-size: 28rpx;
|
||||
vertical-align: middle;
|
||||
margin-left: 16rpx;
|
||||
/* color: gray; 已在style设置color,此处删去*/
|
||||
}
|
||||
|
||||
/* 下拉刷新--旋转进度条 */
|
||||
.mescroll-downwarp .downwarp-progress {
|
||||
display: inline-block;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid gray;
|
||||
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 旋转动画 */
|
||||
.mescroll-downwarp .mescroll-rotate {
|
||||
animation: mescrollDownRotate 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes mescrollDownRotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
<!-- 下拉刷新区域 -->
|
||||
<template>
|
||||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mOption.textColor, 'transform':downRotate}"></view>
|
||||
<view class="downwarp-tip">{{downText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
option: Object , // down的配置项
|
||||
type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
|
||||
rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1)
|
||||
},
|
||||
computed: {
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
||||
mOption(){
|
||||
return this.option || {}
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading(){
|
||||
return this.type === 3
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate(){
|
||||
return 'rotate(' + 360 * this.rate + 'deg)'
|
||||
},
|
||||
// 文本提示
|
||||
downText(){
|
||||
switch (this.type){
|
||||
case 1: return this.mOption.textInOffset;
|
||||
case 2: return this.mOption.textOutOffset;
|
||||
case 3: return this.mOption.textLoading;
|
||||
case 4: return this.mOption.textLoading;
|
||||
default: return this.mOption.textInOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "./mescroll-down.css";
|
||||
</style>
|
||||
|
|
@ -1,83 +0,0 @@
|
|||
<!-- 回到顶部的按钮 -->
|
||||
<template>
|
||||
<image
|
||||
v-if="mOption.src"
|
||||
class="mescroll-totop"
|
||||
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-totop-safearea': mOption.safearea}]"
|
||||
:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}"
|
||||
:src="mOption.src"
|
||||
mode="widthFix"
|
||||
@tap="toTopClick"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
// up.toTop的配置项
|
||||
option: Object,
|
||||
// 是否显示
|
||||
value: false
|
||||
},
|
||||
computed: {
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
||||
mOption(){
|
||||
return this.option || {}
|
||||
},
|
||||
// 优先显示左边
|
||||
left(){
|
||||
return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
|
||||
},
|
||||
// 右边距离 (优先显示左边)
|
||||
right() {
|
||||
return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
addUnit(num){
|
||||
if(!num) return 0;
|
||||
if(typeof num === 'number') return num + 'rpx';
|
||||
return num
|
||||
},
|
||||
toTopClick() {
|
||||
this.$emit('input', false); // 使v-model生效
|
||||
this.$emit('click'); // 派发点击事件
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* 回到顶部的按钮 */
|
||||
.mescroll-totop {
|
||||
z-index: 9990;
|
||||
position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
|
||||
right: 20rpx;
|
||||
bottom: 120rpx;
|
||||
width: 72rpx;
|
||||
height: auto;
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.5s; /* 过渡 */
|
||||
margin-bottom: var(--window-bottom); /* css变量 */
|
||||
}
|
||||
|
||||
/* 适配 iPhoneX */
|
||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
|
||||
.mescroll-totop-safearea {
|
||||
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
|
||||
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
|
||||
/* 显示 -- 淡入 */
|
||||
.mescroll-totop-in {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* 隐藏 -- 淡出且不接收事件*/
|
||||
.mescroll-totop-out {
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
/* 上拉加载区域 */
|
||||
.mescroll-upwarp {
|
||||
box-sizing: border-box;
|
||||
min-height: 110rpx;
|
||||
padding: 30rpx 0;
|
||||
text-align: center;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
/*提示文本 */
|
||||
.mescroll-upwarp .upwarp-tip,
|
||||
.mescroll-upwarp .upwarp-nodata {
|
||||
display: inline-block;
|
||||
font-size: 28rpx;
|
||||
vertical-align: middle;
|
||||
/* color: gray; 已在style设置color,此处删去*/
|
||||
}
|
||||
|
||||
.mescroll-upwarp .upwarp-tip {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
|
||||
/*旋转进度条 */
|
||||
.mescroll-upwarp .upwarp-progress {
|
||||
display: inline-block;
|
||||
width: 32rpx;
|
||||
height: 32rpx;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid gray;
|
||||
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* 旋转动画 */
|
||||
.mescroll-upwarp .mescroll-rotate {
|
||||
animation: mescrollUpRotate 0.6s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes mescrollUpRotate {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
<!-- 上拉加载区域 -->
|
||||
<template>
|
||||
<view class="mescroll-upwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
||||
<view v-show="isUpLoading">
|
||||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mOption.textColor}"></view>
|
||||
<view class="upwarp-tip">{{ mOption.textLoading }}</view>
|
||||
</view>
|
||||
<!-- 无数据 -->
|
||||
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
option: Object, // up的配置项
|
||||
type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了)
|
||||
},
|
||||
computed: {
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错
|
||||
mOption() {
|
||||
return this.option || {};
|
||||
},
|
||||
// 加载中
|
||||
isUpLoading() {
|
||||
return this.type === 1;
|
||||
},
|
||||
// 没有更多了
|
||||
isUpNoMore() {
|
||||
return this.type === 2;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import './mescroll-up.css';
|
||||
</style>
|
||||
|
|
@ -1,15 +0,0 @@
|
|||
// 国际化工具类
|
||||
const mescrollI18n = {
|
||||
// 默认语言
|
||||
def: "zh",
|
||||
// 获取当前语言类型
|
||||
getType(){
|
||||
return uni.getStorageSync("mescroll-i18n") || this.def
|
||||
},
|
||||
// 设置当前语言类型
|
||||
setType(type){
|
||||
uni.setStorageSync("mescroll-i18n", type)
|
||||
}
|
||||
}
|
||||
|
||||
export default mescrollI18n
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
// mescroll-body 和 mescroll-uni 通用
|
||||
const MescrollMixin = {
|
||||
data() {
|
||||
return {
|
||||
mescroll: null //mescroll实例对象
|
||||
}
|
||||
},
|
||||
// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
||||
onPullDownRefresh(){
|
||||
this.mescroll && this.mescroll.onPullDownRefresh();
|
||||
},
|
||||
// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
||||
onPageScroll(e) {
|
||||
this.mescroll && this.mescroll.onPageScroll(e);
|
||||
},
|
||||
// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
|
||||
onReachBottom() {
|
||||
this.mescroll && this.mescroll.onReachBottom();
|
||||
},
|
||||
methods: {
|
||||
// mescroll组件初始化的回调,可获取到mescroll对象
|
||||
mescrollInit(mescroll) {
|
||||
this.mescroll = mescroll;
|
||||
this.mescrollInitByRef(); // 兼容字节跳动小程序
|
||||
},
|
||||
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
|
||||
mescrollInitByRef() {
|
||||
if(!this.mescroll || !this.mescroll.resetUpScroll){
|
||||
let mescrollRef = this.$refs.mescrollRef;
|
||||
if(mescrollRef) this.mescroll = mescrollRef.mescroll
|
||||
}
|
||||
},
|
||||
// 下拉刷新的回调 (mixin默认resetUpScroll)
|
||||
downCallback() {
|
||||
if(this.mescroll.optUp.use){
|
||||
this.mescroll.resetUpScroll()
|
||||
}else{
|
||||
setTimeout(()=>{
|
||||
this.mescroll.endSuccess();
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
// 上拉加载的回调
|
||||
upCallback() {
|
||||
// mixin默认延时500自动结束加载
|
||||
setTimeout(()=>{
|
||||
this.mescroll.endErr();
|
||||
}, 500)
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default MescrollMixin;
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
// 全局配置
|
||||
// mescroll-body 和 mescroll-uni 通用
|
||||
const GlobalOption = {
|
||||
down: {
|
||||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
||||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
||||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
||||
},
|
||||
up: {
|
||||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
||||
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
|
||||
toTop: {
|
||||
// 回到顶部按钮,需配置src才显示
|
||||
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
|
||||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
|
||||
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
|
||||
},
|
||||
empty: {
|
||||
use: true, // 是否显示空布局
|
||||
icon: "/static/images/user/empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
||||
}
|
||||
},
|
||||
// 国际化配置
|
||||
i18n: {
|
||||
// 中文
|
||||
zh: {
|
||||
down: {
|
||||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
||||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
||||
textSuccess: '加载成功', // 加载成功的文本
|
||||
textErr: '加载失败', // 加载失败的文本
|
||||
},
|
||||
up: {
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
||||
textNoMore: '-- 没有更多数据 --', // 没有更多数据的提示文本
|
||||
empty: {
|
||||
tip: '~ 空空如也 ~' // 空提示
|
||||
}
|
||||
}
|
||||
},
|
||||
// 英文
|
||||
en: {
|
||||
down: {
|
||||
textInOffset: 'drop down refresh',
|
||||
textOutOffset: 'release updates',
|
||||
textLoading: 'loading ...',
|
||||
textSuccess: 'loaded successfully',
|
||||
textErr: 'loading failed'
|
||||
},
|
||||
up: {
|
||||
textLoading: 'loading ...',
|
||||
textNoMore: '-- END --',
|
||||
empty: {
|
||||
tip: '~ absolutely empty ~'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GlobalOption
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
.mescroll-uni-warp{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mescroll-uni-content{
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.mescroll-uni {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 200rpx;
|
||||
overflow-y: auto;
|
||||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
|
||||
}
|
||||
|
||||
/* 定位的方式固定高度 */
|
||||
.mescroll-uni-fixed{
|
||||
z-index: 1;
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: auto; /* 使right生效 */
|
||||
height: auto; /* 使bottom生效 */
|
||||
}
|
||||
|
||||
/* 适配 iPhoneX */
|
||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
|
||||
.mescroll-safearea {
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,799 +0,0 @@
|
|||
/* mescroll
|
||||
* version 1.3.7
|
||||
* 2021-04-12 wenju
|
||||
* https://www.mescroll.com
|
||||
*/
|
||||
|
||||
export default function MeScroll(options, isScrollBody) {
|
||||
let me = this;
|
||||
me.version = '1.3.7'; // mescroll版本号
|
||||
me.options = options || {}; // 配置
|
||||
me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
|
||||
|
||||
me.isDownScrolling = false; // 是否在执行下拉刷新的回调
|
||||
me.isUpScrolling = false; // 是否在执行上拉加载的回调
|
||||
let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
|
||||
|
||||
// 初始化下拉刷新
|
||||
me.initDownScroll();
|
||||
// 初始化上拉加载,则初始化
|
||||
me.initUpScroll();
|
||||
|
||||
// 自动加载
|
||||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
||||
// 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
|
||||
if ((me.optDown.use || me.optDown.native) && me.optDown.auto && hasDownCallback) {
|
||||
if (me.optDown.autoShowLoading) {
|
||||
me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
|
||||
} else {
|
||||
me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
|
||||
}
|
||||
}
|
||||
// 自动触发上拉加载
|
||||
if(!me.isUpAutoLoad){ // 部分小程序(头条小程序)emit是异步, 会导致isUpAutoLoad判断有误, 先延时确保先执行down的callback,再执行up的callback
|
||||
setTimeout(function(){
|
||||
me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
|
||||
},100)
|
||||
}
|
||||
}, 30); // 需让me.optDown.inited和me.optUp.inited先执行
|
||||
}
|
||||
|
||||
/* 配置参数:下拉刷新 */
|
||||
MeScroll.prototype.extendDownScroll = function(optDown) {
|
||||
// 下拉刷新的配置
|
||||
MeScroll.extend(optDown, {
|
||||
use: true, // 是否启用下拉刷新; 默认true
|
||||
auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
|
||||
native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
||||
autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
|
||||
isLock: false, // 是否锁定下拉刷新,默认false;
|
||||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
||||
startTop: 100, // scroll-view快速滚动到顶部时,此时的scroll-top可能大于0, 此值用于控制最大的误差
|
||||
inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
|
||||
outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
|
||||
bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
|
||||
minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
|
||||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
||||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
||||
textSuccess: '加载成功', // 加载成功的文本
|
||||
textErr: '加载失败', // 加载失败的文本
|
||||
beforeEndDelay: 0, // 延时结束的时长 (显示加载成功/失败的时长, android小程序设置此项结束下拉会卡顿, 配置后请注意测试)
|
||||
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop)
|
||||
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
|
||||
inited: null, // 下拉刷新初始化完毕的回调
|
||||
inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
|
||||
outOffset: null, // 下拉的距离大于offset那一刻的回调
|
||||
onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
|
||||
beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
|
||||
showLoading: null, // 显示下拉刷新进度的回调
|
||||
afterLoading: null, // 显示下拉刷新进度的回调之后,马上要执行的代码 (如: 在wxs中使用)
|
||||
beforeEndDownScroll: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
|
||||
endDownScroll: null, // 结束下拉刷新的回调
|
||||
afterEndDownScroll: null, // 结束下拉刷新的回调,马上要执行的代码 (如: 在wxs中使用)
|
||||
callback: function(mescroll) {
|
||||
// 下拉刷新的回调;默认重置上拉加载列表为第一页
|
||||
mescroll.resetUpScroll();
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/* 配置参数:上拉加载 */
|
||||
MeScroll.prototype.extendUpScroll = function(optUp) {
|
||||
// 上拉加载的配置
|
||||
MeScroll.extend(optUp, {
|
||||
use: true, // 是否启用上拉加载; 默认true
|
||||
auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
|
||||
isLock: false, // 是否锁定上拉加载,默认false;
|
||||
isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
|
||||
callback: null, // 上拉加载的回调;function(page,mescroll){ }
|
||||
page: {
|
||||
num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
|
||||
size: 10, // 每页数据的数量
|
||||
time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
|
||||
},
|
||||
noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
|
||||
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
||||
textNoMore: '-- END --', // 没有更多数据的提示文本
|
||||
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom)
|
||||
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
|
||||
inited: null, // 初始化完毕的回调
|
||||
showLoading: null, // 显示加载中的回调
|
||||
showNoMore: null, // 显示无更多数据的回调
|
||||
hideUpScroll: null, // 隐藏上拉加载的回调
|
||||
errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
|
||||
toTop: {
|
||||
// 回到顶部按钮,需配置src才显示
|
||||
src: null, // 图片路径,默认null (绝对路径或网络图)
|
||||
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
|
||||
duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
|
||||
btnClick: null, // 点击按钮的回调
|
||||
onShow: null, // 是否显示的回调
|
||||
zIndex: 9990, // fixed定位z-index值
|
||||
left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
||||
right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
||||
bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
||||
safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
|
||||
width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
||||
radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
|
||||
},
|
||||
empty: {
|
||||
use: true, // 是否显示空布局
|
||||
icon: null, // 图标路径
|
||||
tip: '~ 暂无相关数据 ~', // 提示
|
||||
btnText: '', // 按钮
|
||||
btnClick: null, // 点击按钮的回调
|
||||
onShow: null, // 是否显示的回调
|
||||
fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
|
||||
top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
|
||||
zIndex: 99 // fixed定位z-index值
|
||||
},
|
||||
onScroll: false // 是否监听滚动事件
|
||||
})
|
||||
}
|
||||
|
||||
/* 配置参数 */
|
||||
MeScroll.extend = function(userOption, defaultOption) {
|
||||
if (!userOption) return defaultOption;
|
||||
for (let key in defaultOption) {
|
||||
if (userOption[key] == null) {
|
||||
let def = defaultOption[key];
|
||||
if (def != null && typeof def === 'object') {
|
||||
userOption[key] = MeScroll.extend({}, def); // 深度匹配
|
||||
} else {
|
||||
userOption[key] = def;
|
||||
}
|
||||
} else if (typeof userOption[key] === 'object') {
|
||||
MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
|
||||
}
|
||||
}
|
||||
return userOption;
|
||||
}
|
||||
|
||||
/* 简单判断是否配置了颜色 (非透明,非白色) */
|
||||
MeScroll.prototype.hasColor = function(color) {
|
||||
if(!color) return false;
|
||||
let c = color.toLowerCase();
|
||||
return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white"
|
||||
}
|
||||
|
||||
/* -------初始化下拉刷新------- */
|
||||
MeScroll.prototype.initDownScroll = function() {
|
||||
let me = this;
|
||||
// 配置参数
|
||||
me.optDown = me.options.down || {};
|
||||
if(!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
|
||||
me.extendDownScroll(me.optDown);
|
||||
|
||||
// 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
|
||||
if(me.isScrollBody && me.optDown.native){
|
||||
me.optDown.use = false
|
||||
}else{
|
||||
me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
|
||||
}
|
||||
|
||||
me.downHight = 0; // 下拉区域的高度
|
||||
|
||||
// 在页面中加入下拉布局
|
||||
if (me.optDown.use && me.optDown.inited) {
|
||||
// 初始化完毕的回调
|
||||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
||||
me.optDown.inited(me);
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/* 列表touchstart事件 */
|
||||
MeScroll.prototype.touchstartEvent = function(e) {
|
||||
if (!this.optDown.use) return;
|
||||
|
||||
this.startPoint = this.getPoint(e); // 记录起点
|
||||
this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
|
||||
this.startAngle = 0; // 初始角度
|
||||
this.lastPoint = this.startPoint; // 重置上次move的点
|
||||
this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
|
||||
this.inTouchend = false; // 标记不是touchend
|
||||
}
|
||||
|
||||
/* 列表touchmove事件 */
|
||||
MeScroll.prototype.touchmoveEvent = function(e) {
|
||||
if (!this.optDown.use) return;
|
||||
let me = this;
|
||||
|
||||
let scrollTop = me.getScrollTop(); // 当前滚动条的距离
|
||||
let curPoint = me.getPoint(e); // 当前点
|
||||
|
||||
let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
||||
|
||||
// 向下拉 && 在顶部
|
||||
// mescroll-body,直接判定在顶部即可
|
||||
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
|
||||
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
|
||||
if (moveY > 0 && (
|
||||
(me.isScrollBody && scrollTop <= 0)
|
||||
||
|
||||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
|
||||
)) {
|
||||
// 可下拉的条件
|
||||
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
|
||||
me.optUp.isBoth))) {
|
||||
|
||||
// 下拉的初始角度是否在配置的范围内
|
||||
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
|
||||
if (me.startAngle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
|
||||
|
||||
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
|
||||
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
|
||||
me.inTouchend = true; // 标记执行touchend
|
||||
me.touchendEvent(); // 提前触发touchend
|
||||
return;
|
||||
}
|
||||
|
||||
me.preventDefault(e); // 阻止默认事件
|
||||
|
||||
let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
|
||||
|
||||
// 下拉距离 < 指定距离
|
||||
if (me.downHight < me.optDown.offset) {
|
||||
if (me.movetype !== 1) {
|
||||
me.movetype = 1; // 加入标记,保证只执行一次
|
||||
me.isDownEndSuccess = null; // 重置是否加载成功的状态 (wxs执行的是wxs.wxs)
|
||||
me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
|
||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
||||
}
|
||||
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
|
||||
|
||||
// 指定距离 <= 下拉距离
|
||||
} else {
|
||||
if (me.movetype !== 2) {
|
||||
me.movetype = 2; // 加入标记,保证只执行一次
|
||||
me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
|
||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
||||
}
|
||||
if (diff > 0) { // 向下拉
|
||||
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
|
||||
} else { // 向上收
|
||||
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
|
||||
}
|
||||
}
|
||||
|
||||
me.downHight = Math.round(me.downHight) // 取整
|
||||
let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
|
||||
me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
|
||||
}
|
||||
}
|
||||
|
||||
me.lastPoint = curPoint; // 记录本次移动的点
|
||||
}
|
||||
|
||||
/* 列表touchend事件 */
|
||||
MeScroll.prototype.touchendEvent = function(e) {
|
||||
if (!this.optDown.use) return;
|
||||
// 如果下拉区域高度已改变,则需重置回来
|
||||
if (this.isMoveDown) {
|
||||
if (this.downHight >= this.optDown.offset) {
|
||||
// 符合触发刷新的条件
|
||||
this.triggerDownScroll();
|
||||
} else {
|
||||
// 不符合的话 则重置
|
||||
this.downHight = 0;
|
||||
this.endDownScrollCall(this);
|
||||
}
|
||||
this.movetype = 0;
|
||||
this.isMoveDown = false;
|
||||
} else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
|
||||
let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
||||
// 上滑
|
||||
if (isScrollUp) {
|
||||
// 需检查滑动的角度
|
||||
let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
|
||||
if (angle > 80) {
|
||||
// 检查并触发上拉
|
||||
this.triggerUpScroll(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 根据点击滑动事件获取第一个手指的坐标 */
|
||||
MeScroll.prototype.getPoint = function(e) {
|
||||
if (!e) {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0
|
||||
}
|
||||
}
|
||||
if (e.touches && e.touches[0]) {
|
||||
return {
|
||||
x: e.touches[0].pageX,
|
||||
y: e.touches[0].pageY
|
||||
}
|
||||
} else if (e.changedTouches && e.changedTouches[0]) {
|
||||
return {
|
||||
x: e.changedTouches[0].pageX,
|
||||
y: e.changedTouches[0].pageY
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
x: e.clientX,
|
||||
y: e.clientY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 计算两点之间的角度: 区间 [0,90]*/
|
||||
MeScroll.prototype.getAngle = function(p1, p2) {
|
||||
let x = Math.abs(p1.x - p2.x);
|
||||
let y = Math.abs(p1.y - p2.y);
|
||||
let z = Math.sqrt(x * x + y * y);
|
||||
let angle = 0;
|
||||
if (z !== 0) {
|
||||
angle = Math.asin(y / z) / Math.PI * 180;
|
||||
}
|
||||
return angle
|
||||
}
|
||||
|
||||
/* 触发下拉刷新 */
|
||||
MeScroll.prototype.triggerDownScroll = function() {
|
||||
if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) {
|
||||
//return true则处于完全自定义状态
|
||||
} else {
|
||||
this.showDownScroll(); // 下拉刷新中...
|
||||
!this.optDown.native && this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
|
||||
}
|
||||
}
|
||||
|
||||
/* 显示下拉进度布局 */
|
||||
MeScroll.prototype.showDownScroll = function() {
|
||||
this.isDownScrolling = true; // 标记下拉中
|
||||
if (this.optDown.native) {
|
||||
uni.startPullDownRefresh(); // 系统自带的下拉刷新
|
||||
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
|
||||
} else{
|
||||
this.downHight = this.optDown.offset; // 更新下拉区域高度
|
||||
this.showDownLoadingCall(this.downHight); // 下拉刷新中...
|
||||
}
|
||||
}
|
||||
|
||||
MeScroll.prototype.showDownLoadingCall = function(downHight) {
|
||||
this.optDown.showLoading && this.optDown.showLoading(this, downHight); // 下拉刷新中...
|
||||
this.optDown.afterLoading && this.optDown.afterLoading(this, downHight); // 下拉刷新中...触发之后马上要执行的代码
|
||||
}
|
||||
|
||||
/* 显示系统自带的下拉刷新时需要处理的业务 */
|
||||
MeScroll.prototype.onPullDownRefresh = function() {
|
||||
this.isDownScrolling = true; // 标记下拉中
|
||||
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
|
||||
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
|
||||
}
|
||||
|
||||
/* 结束下拉刷新 */
|
||||
MeScroll.prototype.endDownScroll = function() {
|
||||
if (this.optDown.native) { // 结束原生下拉刷新
|
||||
this.isDownScrolling = false;
|
||||
this.endDownScrollCall(this);
|
||||
uni.stopPullDownRefresh();
|
||||
return
|
||||
}
|
||||
let me = this;
|
||||
// 结束下拉刷新的方法
|
||||
let endScroll = function() {
|
||||
me.downHight = 0;
|
||||
me.isDownScrolling = false;
|
||||
me.endDownScrollCall(me);
|
||||
if(!me.isScrollBody){
|
||||
me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
|
||||
me.scrollTo(0,0) // scroll-view需重置滚动条到顶部,避免startTop大于0时,对下拉刷新的影响
|
||||
}
|
||||
}
|
||||
// 结束下拉刷新时的回调
|
||||
let delay = 0;
|
||||
if (me.optDown.beforeEndDownScroll) {
|
||||
delay = me.optDown.beforeEndDownScroll(me); // 结束下拉刷新的延时,单位ms
|
||||
if(me.isDownEndSuccess == null) delay = 0; // 没有执行加载中,则不延时
|
||||
}
|
||||
if (typeof delay === 'number' && delay > 0) {
|
||||
setTimeout(endScroll, delay);
|
||||
} else {
|
||||
endScroll();
|
||||
}
|
||||
}
|
||||
|
||||
MeScroll.prototype.endDownScrollCall = function() {
|
||||
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
|
||||
this.optDown.afterEndDownScroll && this.optDown.afterEndDownScroll(this);
|
||||
}
|
||||
|
||||
/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */
|
||||
MeScroll.prototype.lockDownScroll = function(isLock) {
|
||||
if (isLock == null) isLock = true;
|
||||
this.optDown.isLock = isLock;
|
||||
}
|
||||
|
||||
/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */
|
||||
MeScroll.prototype.lockUpScroll = function(isLock) {
|
||||
if (isLock == null) isLock = true;
|
||||
this.optUp.isLock = isLock;
|
||||
}
|
||||
|
||||
/* -------初始化上拉加载------- */
|
||||
MeScroll.prototype.initUpScroll = function() {
|
||||
let me = this;
|
||||
// 配置参数
|
||||
me.optUp = me.options.up || {use: false}
|
||||
if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
|
||||
me.extendUpScroll(me.optUp);
|
||||
|
||||
if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
|
||||
me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
|
||||
me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
|
||||
|
||||
// 初始化完毕的回调
|
||||
if (me.optUp.inited) {
|
||||
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
|
||||
me.optUp.inited(me);
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
|
||||
/*滚动到底部的事件 (仅mescroll-body生效)*/
|
||||
MeScroll.prototype.onReachBottom = function() {
|
||||
if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
|
||||
if (!this.optUp.isLock && this.optUp.hasNext) {
|
||||
this.triggerUpScroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*列表滚动事件 (仅mescroll-body生效)*/
|
||||
MeScroll.prototype.onPageScroll = function(e) {
|
||||
if (!this.isScrollBody) return;
|
||||
|
||||
// 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
|
||||
this.setScrollTop(e.scrollTop);
|
||||
|
||||
// 顶部按钮的显示隐藏
|
||||
if (e.scrollTop >= this.optUp.toTop.offset) {
|
||||
this.showTopBtn();
|
||||
} else {
|
||||
this.hideTopBtn();
|
||||
}
|
||||
}
|
||||
|
||||
/*列表滚动事件*/
|
||||
MeScroll.prototype.scroll = function(e, onScroll) {
|
||||
// 更新滚动条的位置
|
||||
this.setScrollTop(e.scrollTop);
|
||||
// 更新滚动内容高度
|
||||
this.setScrollHeight(e.scrollHeight);
|
||||
|
||||
// 向上滑还是向下滑动
|
||||
if (this.preScrollY == null) this.preScrollY = 0;
|
||||
this.isScrollUp = e.scrollTop - this.preScrollY > 0;
|
||||
this.preScrollY = e.scrollTop;
|
||||
|
||||
// 上滑 && 检查并触发上拉
|
||||
this.isScrollUp && this.triggerUpScroll(true);
|
||||
|
||||
// 顶部按钮的显示隐藏
|
||||
if (e.scrollTop >= this.optUp.toTop.offset) {
|
||||
this.showTopBtn();
|
||||
} else {
|
||||
this.hideTopBtn();
|
||||
}
|
||||
|
||||
// 滑动监听
|
||||
this.optUp.onScroll && onScroll && onScroll()
|
||||
}
|
||||
|
||||
/* 触发上拉加载 */
|
||||
MeScroll.prototype.triggerUpScroll = function(isCheck) {
|
||||
if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) {
|
||||
// 是否校验在底部; 默认不校验
|
||||
if (isCheck === true) {
|
||||
let canUp = false;
|
||||
// 还有下一页 && 没有锁定 && 不在下拉中
|
||||
if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) {
|
||||
if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
|
||||
canUp = true; // 标记可上拉
|
||||
}
|
||||
}
|
||||
if (canUp === false) return;
|
||||
}
|
||||
this.showUpScroll(); // 上拉加载中...
|
||||
this.optUp.page.num++; // 预先加一页,如果失败则减回
|
||||
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
|
||||
this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
|
||||
this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
|
||||
this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
|
||||
this.optUp.callback(this); // 执行回调,联网加载数据
|
||||
}
|
||||
}
|
||||
|
||||
/* 显示上拉加载中 */
|
||||
MeScroll.prototype.showUpScroll = function() {
|
||||
this.isUpScrolling = true; // 标记上拉加载中
|
||||
this.optUp.showLoading && this.optUp.showLoading(this); // 回调
|
||||
}
|
||||
|
||||
/* 显示上拉无更多数据 */
|
||||
MeScroll.prototype.showNoMore = function() {
|
||||
this.optUp.hasNext = false; // 标记无更多数据
|
||||
this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
|
||||
}
|
||||
|
||||
/* 隐藏上拉区域**/
|
||||
MeScroll.prototype.hideUpScroll = function() {
|
||||
this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
|
||||
}
|
||||
|
||||
/* 结束上拉加载 */
|
||||
MeScroll.prototype.endUpScroll = function(isShowNoMore) {
|
||||
if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
|
||||
if (isShowNoMore) {
|
||||
this.showNoMore(); // isShowNoMore=true,显示无更多数据
|
||||
} else {
|
||||
this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
|
||||
}
|
||||
}
|
||||
this.isUpScrolling = false; // 标记结束上拉加载
|
||||
}
|
||||
|
||||
/* 重置上拉加载列表为第一页
|
||||
*isShowLoading 是否显示进度布局;
|
||||
* 1.默认null,不传参,则显示上拉加载的进度布局
|
||||
* 2.传参true, 则显示下拉刷新的进度布局
|
||||
* 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据)
|
||||
*/
|
||||
MeScroll.prototype.resetUpScroll = function(isShowLoading) {
|
||||
if (this.optUp && this.optUp.use) {
|
||||
let page = this.optUp.page;
|
||||
this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
|
||||
this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
|
||||
page.num = this.startNum; // 重置为第一页
|
||||
page.time = null; // 重置时间为空
|
||||
if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
|
||||
if (isShowLoading == null) {
|
||||
this.removeEmpty(); // 移除空布局
|
||||
this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
|
||||
} else {
|
||||
this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
|
||||
}
|
||||
}
|
||||
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
|
||||
this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
|
||||
this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
|
||||
this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
|
||||
this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
|
||||
}
|
||||
}
|
||||
|
||||
/* 设置page.num的值 */
|
||||
MeScroll.prototype.setPageNum = function(num) {
|
||||
this.optUp.page.num = num - 1;
|
||||
}
|
||||
|
||||
/* 设置page.size的值 */
|
||||
MeScroll.prototype.setPageSize = function(size) {
|
||||
this.optUp.page.size = size;
|
||||
}
|
||||
|
||||
/* 联网回调成功,结束下拉刷新和上拉加载
|
||||
* dataSize: 当前页的数据量(必传)
|
||||
* totalPage: 总页数(必传)
|
||||
* systime: 服务器时间 (可空)
|
||||
*/
|
||||
MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) {
|
||||
let hasNext;
|
||||
if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
|
||||
this.endSuccess(dataSize, hasNext, systime);
|
||||
}
|
||||
|
||||
/* 联网回调成功,结束下拉刷新和上拉加载
|
||||
* dataSize: 当前页的数据量(必传)
|
||||
* totalSize: 列表所有数据总数量(必传)
|
||||
* systime: 服务器时间 (可空)
|
||||
*/
|
||||
MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) {
|
||||
let hasNext;
|
||||
if (this.optUp.use && totalSize != null) {
|
||||
let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
|
||||
hasNext = loadSize < totalSize; // 是否还有下一页
|
||||
}
|
||||
this.endSuccess(dataSize, hasNext, systime);
|
||||
}
|
||||
|
||||
/* 联网回调成功,结束下拉刷新和上拉加载
|
||||
* dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页
|
||||
* hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据.
|
||||
* systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录
|
||||
*/
|
||||
MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) {
|
||||
let me = this;
|
||||
// 结束下拉刷新
|
||||
if (me.isDownScrolling) {
|
||||
me.isDownEndSuccess = true
|
||||
me.endDownScroll();
|
||||
}
|
||||
|
||||
// 结束上拉加载
|
||||
if (me.optUp.use) {
|
||||
let isShowNoMore; // 是否已无更多数据
|
||||
if (dataSize != null) {
|
||||
let pageNum = me.optUp.page.num; // 当前页码
|
||||
let pageSize = me.optUp.page.size; // 每页长度
|
||||
// 如果是第一页
|
||||
if (pageNum === 1) {
|
||||
if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
|
||||
}
|
||||
if (dataSize < pageSize || hasNext === false) {
|
||||
// 返回的数据不满一页时,则说明已无更多数据
|
||||
me.optUp.hasNext = false;
|
||||
if (dataSize === 0 && pageNum === 1) {
|
||||
// 如果第一页无任何数据且配置了空布局
|
||||
isShowNoMore = false;
|
||||
me.showEmpty();
|
||||
} else {
|
||||
// 总列表数少于配置的数量,则不显示无更多数据
|
||||
let allDataSize = (pageNum - 1) * pageSize + dataSize;
|
||||
if (allDataSize < me.optUp.noMoreSize) {
|
||||
isShowNoMore = false;
|
||||
} else {
|
||||
isShowNoMore = true;
|
||||
}
|
||||
me.removeEmpty(); // 移除空布局
|
||||
}
|
||||
} else {
|
||||
// 还有下一页
|
||||
isShowNoMore = false;
|
||||
me.optUp.hasNext = true;
|
||||
me.removeEmpty(); // 移除空布局
|
||||
}
|
||||
}
|
||||
|
||||
// 隐藏上拉
|
||||
me.endUpScroll(isShowNoMore);
|
||||
}
|
||||
}
|
||||
|
||||
/* 回调失败,结束下拉刷新和上拉加载 */
|
||||
MeScroll.prototype.endErr = function(errDistance) {
|
||||
// 结束下拉,回调失败重置回原来的页码和时间
|
||||
if (this.isDownScrolling) {
|
||||
this.isDownEndSuccess = false
|
||||
let page = this.optUp.page;
|
||||
if (page && this.prePageNum) {
|
||||
page.num = this.prePageNum;
|
||||
page.time = this.prePageTime;
|
||||
}
|
||||
this.endDownScroll();
|
||||
}
|
||||
// 结束上拉,回调失败重置回原来的页码
|
||||
if (this.isUpScrolling) {
|
||||
this.optUp.page.num--;
|
||||
this.endUpScroll(false);
|
||||
// 如果是mescroll-body,则需往回滚一定距离
|
||||
if(this.isScrollBody && errDistance !== 0){ // 不处理0
|
||||
if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
|
||||
this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* 显示空布局 */
|
||||
MeScroll.prototype.showEmpty = function() {
|
||||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true)
|
||||
}
|
||||
|
||||
/* 移除空布局 */
|
||||
MeScroll.prototype.removeEmpty = function() {
|
||||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false)
|
||||
}
|
||||
|
||||
/* 显示回到顶部的按钮 */
|
||||
MeScroll.prototype.showTopBtn = function() {
|
||||
if (!this.topBtnShow) {
|
||||
this.topBtnShow = true;
|
||||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(true);
|
||||
}
|
||||
}
|
||||
|
||||
/* 隐藏回到顶部的按钮 */
|
||||
MeScroll.prototype.hideTopBtn = function() {
|
||||
if (this.topBtnShow) {
|
||||
this.topBtnShow = false;
|
||||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(false);
|
||||
}
|
||||
}
|
||||
|
||||
/* 获取滚动条的位置 */
|
||||
MeScroll.prototype.getScrollTop = function() {
|
||||
return this.scrollTop || 0
|
||||
}
|
||||
|
||||
/* 记录滚动条的位置 */
|
||||
MeScroll.prototype.setScrollTop = function(y) {
|
||||
this.scrollTop = y;
|
||||
}
|
||||
|
||||
/* 滚动到指定位置 */
|
||||
MeScroll.prototype.scrollTo = function(y, t) {
|
||||
this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
|
||||
}
|
||||
|
||||
/* 自定义scrollTo */
|
||||
MeScroll.prototype.resetScrollTo = function(myScrollTo) {
|
||||
this.myScrollTo = myScrollTo
|
||||
}
|
||||
|
||||
/* 滚动条到底部的距离 */
|
||||
MeScroll.prototype.getScrollBottom = function() {
|
||||
return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop()
|
||||
}
|
||||
|
||||
/* 计步器
|
||||
star: 开始值
|
||||
end: 结束值
|
||||
callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器;
|
||||
t: 计步时长,传0则直接回调end值;不传则默认300ms
|
||||
rate: 周期;不传则默认30ms计步一次
|
||||
* */
|
||||
MeScroll.prototype.getStep = function(star, end, callback, t, rate) {
|
||||
let diff = end - star; // 差值
|
||||
if (t === 0 || diff === 0) {
|
||||
callback && callback(end);
|
||||
return;
|
||||
}
|
||||
t = t || 300; // 时长 300ms
|
||||
rate = rate || 30; // 周期 30ms
|
||||
let count = t / rate; // 次数
|
||||
let step = diff / count; // 步长
|
||||
let i = 0; // 计数
|
||||
let timer = setInterval(function() {
|
||||
if (i < count - 1) {
|
||||
star += step;
|
||||
callback && callback(star, timer);
|
||||
i++;
|
||||
} else {
|
||||
callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
|
||||
clearInterval(timer);
|
||||
}
|
||||
}, rate);
|
||||
}
|
||||
|
||||
/* 滚动容器的高度 */
|
||||
MeScroll.prototype.getClientHeight = function(isReal) {
|
||||
let h = this.clientHeight || 0
|
||||
if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
|
||||
h = this.getBodyHeight()
|
||||
}
|
||||
return h
|
||||
}
|
||||
MeScroll.prototype.setClientHeight = function(h) {
|
||||
this.clientHeight = h;
|
||||
}
|
||||
|
||||
/* 滚动内容的高度 */
|
||||
MeScroll.prototype.getScrollHeight = function() {
|
||||
return this.scrollHeight || 0;
|
||||
}
|
||||
MeScroll.prototype.setScrollHeight = function(h) {
|
||||
this.scrollHeight = h;
|
||||
}
|
||||
|
||||
/* body的高度 */
|
||||
MeScroll.prototype.getBodyHeight = function() {
|
||||
return this.bodyHeight || 0;
|
||||
}
|
||||
MeScroll.prototype.setBodyHeight = function(h) {
|
||||
this.bodyHeight = h;
|
||||
}
|
||||
|
||||
/* 阻止浏览器默认滚动事件 */
|
||||
MeScroll.prototype.preventDefault = function(e) {
|
||||
// 小程序不支持e.preventDefault, 已在wxs中禁止
|
||||
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止
|
||||
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
|
||||
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
|
||||
}
|
||||
|
|
@ -1,477 +0,0 @@
|
|||
<template>
|
||||
<view class="mescroll-uni-warp">
|
||||
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
|
||||
<view class="mescroll-uni-content mescroll-render-touch"
|
||||
@touchstart="wxsBiz.touchstartEvent"
|
||||
@touchmove="wxsBiz.touchmoveEvent"
|
||||
@touchend="wxsBiz.touchendEvent"
|
||||
@touchcancel="wxsBiz.touchendEvent"
|
||||
:change:prop="wxsBiz.propObserver"
|
||||
:prop="wxsProp">
|
||||
<!-- 状态栏 -->
|
||||
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
|
||||
|
||||
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
|
||||
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
|
||||
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
|
||||
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
|
||||
<view class="downwarp-content">
|
||||
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
|
||||
<view class="downwarp-tip">{{downText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 列表内容 -->
|
||||
<slot></slot>
|
||||
|
||||
<!-- 空布局 -->
|
||||
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
|
||||
|
||||
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
|
||||
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
|
||||
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
|
||||
<view v-show="upLoadType===1">
|
||||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
|
||||
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
|
||||
</view>
|
||||
<!-- 无数据 -->
|
||||
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
|
||||
<!-- #ifdef H5 -->
|
||||
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- 适配iPhoneX -->
|
||||
<view v-if="safearea" class="mescroll-safearea"></view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
|
||||
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @tap="toTopClick"></mescroll-top>
|
||||
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
|
||||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
|
||||
<!-- #endif -->
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
|
||||
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
|
||||
<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
|
||||
<!-- #endif -->
|
||||
|
||||
<!-- app, h5使用renderjs -->
|
||||
<!-- #ifdef APP-PLUS || H5 -->
|
||||
<script module="renderBiz" lang="renderjs">
|
||||
import renderBiz from './wxs/renderjs.js';
|
||||
export default {
|
||||
mixins:[renderBiz]
|
||||
}
|
||||
</script>
|
||||
<!-- #endif -->
|
||||
|
||||
<script>
|
||||
// 引入mescroll-uni.js,处理核心逻辑
|
||||
import MeScroll from './mescroll-uni.js';
|
||||
// 引入全局配置
|
||||
import GlobalOption from './mescroll-uni-option.js';
|
||||
// 引入国际化工具类
|
||||
import mescrollI18n from './mescroll-i18n.js';
|
||||
// 引入回到顶部组件
|
||||
import MescrollTop from './components/mescroll-top.vue';
|
||||
// 引入兼容wxs(含renderjs)写法的mixins
|
||||
import WxsMixin from './wxs/mixins.js';
|
||||
|
||||
/**
|
||||
* mescroll-uni 嵌在页面某个区域的下拉刷新和上拉加载组件, 如嵌在弹窗,浮层,swiper中...
|
||||
* @property {Object} down 下拉刷新的参数配置
|
||||
* @property {Object} up 上拉加载的参数配置
|
||||
* @property {Object} i18n 国际化的参数配置
|
||||
* @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
* @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
|
||||
* @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
* @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
|
||||
* @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
|
||||
* @property {String, Number} height 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
|
||||
* @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
|
||||
* @property {Boolean} disableScroll 是否禁止滚动, 默认false
|
||||
* @event {Function} init 初始化完成的回调
|
||||
* @event {Function} down 下拉刷新的回调
|
||||
* @event {Function} up 上拉加载的回调
|
||||
* @event {Function} emptyclick 点击empty配置的btnText按钮回调
|
||||
* @event {Function} topclick 点击回到顶部的按钮回调
|
||||
* @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
|
||||
* @example <mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-uni>
|
||||
*/
|
||||
export default {
|
||||
name: 'mescroll-uni',
|
||||
mixins: [WxsMixin],
|
||||
components: {
|
||||
MescrollTop
|
||||
},
|
||||
props: {
|
||||
down: Object,
|
||||
up: Object,
|
||||
i18n: Object,
|
||||
top: [String, Number],
|
||||
topbar: [Boolean, String],
|
||||
bottom: [String, Number],
|
||||
safearea: Boolean,
|
||||
fixed: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
height: [String, Number],
|
||||
bottombar:{
|
||||
type: Boolean,
|
||||
default: true
|
||||
},
|
||||
disableScroll: Boolean
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mescroll: {optDown:{},optUp:{}}, // mescroll实例
|
||||
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
|
||||
downHight: 0, //下拉刷新: 容器高度
|
||||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
|
||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
|
||||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
|
||||
isShowEmpty: false, // 是否显示空布局
|
||||
isShowToTop: false, // 是否显示回到顶部按钮
|
||||
scrollTop: 0, // 滚动条的位置
|
||||
scrollAnim: false, // 是否开启滚动动画
|
||||
windowTop: 0, // 可使用窗口的顶部位置
|
||||
windowBottom: 0, // 可使用窗口的底部位置
|
||||
windowHeight: 0, // 可使用窗口的高度
|
||||
statusBarHeight: 0 // 状态栏高度
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
// 是否使用fixed定位 (当height有值,则不使用)
|
||||
isFixed(){
|
||||
return !this.height && this.fixed
|
||||
},
|
||||
// mescroll的高度
|
||||
scrollHeight(){
|
||||
if (this.isFixed) {
|
||||
return "auto"
|
||||
} else if(this.height){
|
||||
return this.toPx(this.height) + 'px'
|
||||
}else{
|
||||
return "100%"
|
||||
}
|
||||
},
|
||||
// 下拉布局往下偏移的距离 (px)
|
||||
numTop() {
|
||||
return this.toPx(this.top)
|
||||
},
|
||||
fixedTop() {
|
||||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
|
||||
},
|
||||
padTop() {
|
||||
return !this.isFixed ? this.numTop + 'px' : 0
|
||||
},
|
||||
// 上拉布局往上偏移 (px)
|
||||
numBottom() {
|
||||
return this.toPx(this.bottom)
|
||||
},
|
||||
fixedBottom() {
|
||||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
|
||||
},
|
||||
padBottom() {
|
||||
return !this.isFixed ? this.numBottom + 'px' : 0
|
||||
},
|
||||
// 是否为重置下拉的状态
|
||||
isDownReset(){
|
||||
return this.downLoadType===3 || this.downLoadType===4
|
||||
},
|
||||
// 过渡
|
||||
transition() {
|
||||
return this.isDownReset ? 'transform 300ms' : '';
|
||||
},
|
||||
translateY() {
|
||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
|
||||
},
|
||||
// 列表是否可滑动
|
||||
scrollable(){
|
||||
if(this.disableScroll) return false
|
||||
return this.downLoadType===0 || this.isDownReset
|
||||
},
|
||||
// 是否在加载中
|
||||
isDownLoading(){
|
||||
return this.downLoadType === 3
|
||||
},
|
||||
// 旋转的角度
|
||||
downRotate(){
|
||||
return 'rotate(' + 360 * this.downRate + 'deg)'
|
||||
},
|
||||
// 文本提示
|
||||
downText(){
|
||||
if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
|
||||
switch (this.downLoadType){
|
||||
case 1: return this.mescroll.optDown.textInOffset;
|
||||
case 2: return this.mescroll.optDown.textOutOffset;
|
||||
case 3: return this.mescroll.optDown.textLoading;
|
||||
case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
|
||||
default: return this.mescroll.optDown.textInOffset;
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
//number,rpx,upx,px,% --> px的数值
|
||||
toPx(num){
|
||||
if(typeof num === "string"){
|
||||
if (num.indexOf('px') !== -1) {
|
||||
if(num.indexOf('rpx') !== -1) { // "10rpx"
|
||||
num = num.replace('rpx', '');
|
||||
} else if(num.indexOf('upx') !== -1) { // "10upx"
|
||||
num = num.replace('upx', '');
|
||||
} else { // "10px"
|
||||
return Number(num.replace('px', ''))
|
||||
}
|
||||
}else if (num.indexOf('%') !== -1){
|
||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
|
||||
let rate = Number(num.replace("%","")) / 100
|
||||
return this.windowHeight * rate
|
||||
}
|
||||
}
|
||||
return num ? uni.upx2px(Number(num)) : 0
|
||||
},
|
||||
//注册列表滚动事件,用于下拉刷新和上拉加载
|
||||
scroll(e) {
|
||||
this.mescroll.scroll(e.detail, () => {
|
||||
this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动
|
||||
})
|
||||
},
|
||||
// 点击空布局的按钮回调
|
||||
emptyClick() {
|
||||
this.$emit('emptyclick', this.mescroll)
|
||||
},
|
||||
// 点击回到顶部的按钮回调
|
||||
toTopClick() {
|
||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部
|
||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调
|
||||
},
|
||||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页)
|
||||
setClientHeight() {
|
||||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
|
||||
this.isExec = true; // 避免多次获取
|
||||
this.$nextTick(() => { // 确保dom已渲染
|
||||
this.getClientInfo(data=>{
|
||||
this.isExec = false;
|
||||
if (data) {
|
||||
this.mescroll.setClientHeight(data.height);
|
||||
} else if (this.clientNum != 3) { // 极少部分情况,可能dom还未渲染完毕,递归获取,最多重试3次
|
||||
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
|
||||
setTimeout(() => {
|
||||
this.setClientHeight()
|
||||
}, this.clientNum * 100)
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
},
|
||||
// 获取滚动区域的信息
|
||||
getClientInfo(success){
|
||||
let query = uni.createSelectorQuery();
|
||||
// #ifndef MP-ALIPAY || MP-DINGTALK
|
||||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值
|
||||
// #endif
|
||||
let view = query.select('#' + this.viewId);
|
||||
view.boundingClientRect(data => {
|
||||
success(data)
|
||||
}).exec();
|
||||
}
|
||||
},
|
||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
|
||||
created() {
|
||||
let vm = this;
|
||||
|
||||
let diyOption = {
|
||||
// 下拉刷新的配置
|
||||
down: {
|
||||
inOffset() {
|
||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
outOffset() {
|
||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
onMoving(mescroll, rate, downHight) {
|
||||
// 下拉过程中的回调,滑动过程一直在执行;
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
|
||||
},
|
||||
showLoading(mescroll, downHight) {
|
||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
},
|
||||
beforeEndDownScroll(mescroll){
|
||||
vm.downLoadType = 4;
|
||||
return mescroll.optDown.beforeEndDelay // 延时结束的时长
|
||||
},
|
||||
endDownScroll() {
|
||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
|
||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
|
||||
vm.downResetTimer && clearTimeout(vm.downResetTimer)
|
||||
vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
|
||||
if(vm.downLoadType===4) vm.downLoadType = 0
|
||||
},300)
|
||||
},
|
||||
// 派发下拉刷新的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('down', mescroll)
|
||||
}
|
||||
},
|
||||
// 上拉加载的配置
|
||||
up: {
|
||||
// 显示加载中的回调
|
||||
showLoading() {
|
||||
vm.upLoadType = 1;
|
||||
},
|
||||
// 显示无更多数据的回调
|
||||
showNoMore() {
|
||||
vm.upLoadType = 2;
|
||||
},
|
||||
// 隐藏上拉加载的回调
|
||||
hideUpScroll(mescroll) {
|
||||
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
|
||||
},
|
||||
// 空布局
|
||||
empty: {
|
||||
onShow(isShow) { // 显示隐藏的回调
|
||||
vm.isShowEmpty = isShow;
|
||||
}
|
||||
},
|
||||
// 回到顶部
|
||||
toTop: {
|
||||
onShow(isShow) { // 显示隐藏的回调
|
||||
vm.isShowToTop = isShow;
|
||||
}
|
||||
},
|
||||
// 派发上拉加载的回调
|
||||
callback: function(mescroll) {
|
||||
vm.$emit('up', mescroll);
|
||||
// 更新容器的高度 (多mescroll的情况)
|
||||
vm.setClientHeight()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let i18nType = mescrollI18n.getType() // 当前语言类型
|
||||
let i18nOption = {type: i18nType} // 国际化配置
|
||||
MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
|
||||
MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
|
||||
MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
|
||||
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
|
||||
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
|
||||
MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
|
||||
|
||||
// 初始化MeScroll对象
|
||||
vm.mescroll = new MeScroll(myOption);
|
||||
vm.mescroll.viewId = vm.viewId; // 附带id
|
||||
vm.mescroll.i18n = i18nOption; // 挂载语言包
|
||||
// init回调mescroll对象
|
||||
vm.$emit('init', vm.mescroll);
|
||||
|
||||
// 设置高度
|
||||
const sys = uni.getSystemInfoSync();
|
||||
if(sys.windowTop) vm.windowTop = sys.windowTop;
|
||||
if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
|
||||
if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
|
||||
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
|
||||
// 使down的bottomOffset生效
|
||||
vm.mescroll.setBodyHeight(sys.windowHeight);
|
||||
|
||||
// 因为使用的是scrollview,这里需自定义scrollTo
|
||||
vm.mescroll.resetScrollTo((y, t) => {
|
||||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
|
||||
if(typeof y === 'string'){
|
||||
// 小程序不支持slot里面的scroll-into-view, 统一使用计算的方式实现
|
||||
vm.getClientInfo(function(rect){
|
||||
let mescrollTop = rect.top // mescroll到顶部的距离
|
||||
let selector;
|
||||
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
|
||||
selector = '#'+y // 不带#和. 则默认为id选择器
|
||||
}else{
|
||||
selector = y
|
||||
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
|
||||
if(y.indexOf('>>>')!=-1){ // 不支持跨自定义组件的后代选择器 (转为普通的选择器即可跨组件查询)
|
||||
selector = y.split('>>>')[1].trim()
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
|
||||
if (rect) {
|
||||
let curY = vm.mescroll.getScrollTop()
|
||||
let top = rect.top - mescrollTop
|
||||
top += curY
|
||||
if(!vm.isFixed) top -= vm.numTop
|
||||
vm.scrollTop = curY;
|
||||
vm.$nextTick(function() {
|
||||
vm.scrollTop = top
|
||||
})
|
||||
} else{
|
||||
console.error(selector + ' does not exist');
|
||||
}
|
||||
}).exec()
|
||||
})
|
||||
return;
|
||||
}
|
||||
let curY = vm.mescroll.getScrollTop()
|
||||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡
|
||||
vm.scrollTop = curY;
|
||||
vm.$nextTick(function() {
|
||||
vm.scrollTop = y
|
||||
})
|
||||
} else {
|
||||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t
|
||||
vm.scrollTop = step
|
||||
}, t)
|
||||
}
|
||||
})
|
||||
|
||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值
|
||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
|
||||
vm.mescroll.optUp.toTop.safearea = vm.safearea;
|
||||
}
|
||||
|
||||
// 全局配置监听
|
||||
uni.$on("setMescrollGlobalOption", options=>{
|
||||
if(!options) return;
|
||||
let i18nType = options.i18n ? options.i18n.type : null
|
||||
if(i18nType && vm.mescroll.i18n.type != i18nType){
|
||||
vm.mescroll.i18n.type = i18nType
|
||||
mescrollI18n.setType(i18nType)
|
||||
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
|
||||
}
|
||||
if(options.down){
|
||||
let down = MeScroll.extend({}, options.down)
|
||||
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
|
||||
}
|
||||
if(options.up){
|
||||
let up = MeScroll.extend({}, options.up)
|
||||
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
|
||||
}
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
// 设置容器的高度
|
||||
this.setClientHeight()
|
||||
},
|
||||
destroyed() {
|
||||
// 注销全局配置监听
|
||||
uni.$off("setMescrollGlobalOption")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
@import "./mescroll-uni.css";
|
||||
@import "./components/mescroll-down.css";
|
||||
@import './components/mescroll-up.css';
|
||||
</style>
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
/**
|
||||
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期
|
||||
*/
|
||||
const MescrollCompMixin = {
|
||||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级)
|
||||
onPageScroll(e) {
|
||||
this.handlePageScroll(e)
|
||||
},
|
||||
onReachBottom() {
|
||||
this.handleReachBottom()
|
||||
},
|
||||
// 当down的native: true时, 还需传递此方法进到子组件
|
||||
onPullDownRefresh(){
|
||||
this.handlePullDownRefresh()
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
|
||||
onPageScroll: e=>{
|
||||
this.handlePageScroll(e)
|
||||
},
|
||||
onReachBottom: ()=>{
|
||||
this.handleReachBottom()
|
||||
},
|
||||
onPullDownRefresh: ()=>{
|
||||
this.handlePullDownRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
methods:{
|
||||
handlePageScroll(e){
|
||||
let item = this.$refs["mescrollItem"];
|
||||
if(item && item.mescroll) item.mescroll.onPageScroll(e);
|
||||
},
|
||||
handleReachBottom(){
|
||||
let item = this.$refs["mescrollItem"];
|
||||
if(item && item.mescroll) item.mescroll.onReachBottom();
|
||||
},
|
||||
handlePullDownRefresh(){
|
||||
let item = this.$refs["mescrollItem"];
|
||||
if(item && item.mescroll) item.mescroll.onPullDownRefresh();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MescrollCompMixin;
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/**
|
||||
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
|
||||
*/
|
||||
const MescrollMoreItemMixin = {
|
||||
// 支付宝小程序不支持props的mixin,需写在具体的页面中
|
||||
// #ifndef MP-ALIPAY || MP-DINGTALK
|
||||
props:{
|
||||
i: Number, // 每个tab页的专属下标
|
||||
index: { // 当前tab的下标
|
||||
type: Number,
|
||||
default(){
|
||||
return 0
|
||||
}
|
||||
}
|
||||
},
|
||||
// #endif
|
||||
data() {
|
||||
return {
|
||||
downOption:{
|
||||
auto:false // 不自动加载
|
||||
},
|
||||
upOption:{
|
||||
auto:false // 不自动加载
|
||||
},
|
||||
isInit: false // 当前tab是否已初始化
|
||||
}
|
||||
},
|
||||
watch:{
|
||||
// 监听下标的变化
|
||||
index(val){
|
||||
if (this.i === val && !this.isInit) this.mescrollTrigger()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
|
||||
mescrollInitByRef() {
|
||||
if(!this.mescroll || !this.mescroll.resetUpScroll){
|
||||
// 字节跳动小程序编辑器不支持一个页面存在相同的ref, 多mescroll的ref需动态生成, 格式为'mescrollRef下标'
|
||||
let mescrollRef = this.$refs.mescrollRef || this.$refs['mescrollRef'+this.i];
|
||||
if(mescrollRef) this.mescroll = mescrollRef.mescroll
|
||||
}
|
||||
},
|
||||
// mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
|
||||
mescrollInit(mescroll) {
|
||||
this.mescroll = mescroll;
|
||||
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
|
||||
// 自动加载当前tab的数据
|
||||
if(this.i === this.index){
|
||||
this.mescrollTrigger()
|
||||
}
|
||||
},
|
||||
// 主动触发加载
|
||||
mescrollTrigger(){
|
||||
this.isInit = true; // 标记为true
|
||||
if (this.mescroll) {
|
||||
if (this.mescroll.optDown.use) {
|
||||
this.mescroll.triggerDownScroll();
|
||||
} else{
|
||||
this.mescroll.triggerUpScroll();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MescrollMoreItemMixin;
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
/**
|
||||
* mescroll-body写在子组件时, 需通过mescroll的mixins补充子组件缺少的生命周期
|
||||
*/
|
||||
const MescrollMoreMixin = {
|
||||
data() {
|
||||
return {
|
||||
tabIndex: 0, // 当前tab下标
|
||||
mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
|
||||
onPageScroll: e=>{
|
||||
this.handlePageScroll(e)
|
||||
},
|
||||
onReachBottom: ()=>{
|
||||
this.handleReachBottom()
|
||||
},
|
||||
onPullDownRefresh: ()=>{
|
||||
this.handlePullDownRefresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
||||
onPageScroll(e) {
|
||||
this.handlePageScroll(e)
|
||||
},
|
||||
onReachBottom() {
|
||||
this.handleReachBottom()
|
||||
},
|
||||
// 当down的native: true时, 还需传递此方法进到子组件
|
||||
onPullDownRefresh(){
|
||||
this.handlePullDownRefresh()
|
||||
},
|
||||
methods:{
|
||||
handlePageScroll(e){
|
||||
let mescroll = this.getMescroll(this.tabIndex);
|
||||
mescroll && mescroll.onPageScroll(e);
|
||||
},
|
||||
handleReachBottom(){
|
||||
let mescroll = this.getMescroll(this.tabIndex);
|
||||
mescroll && mescroll.onReachBottom();
|
||||
},
|
||||
handlePullDownRefresh(){
|
||||
let mescroll = this.getMescroll(this.tabIndex);
|
||||
mescroll && mescroll.onPullDownRefresh();
|
||||
},
|
||||
// 根据下标获取对应子组件的mescroll
|
||||
getMescroll(i){
|
||||
if(!this.mescrollItems) this.mescrollItems = [];
|
||||
if(!this.mescrollItems[i]) {
|
||||
// v-for中的refs
|
||||
let vForItem = this.$refs["mescrollItem"];
|
||||
if(vForItem){
|
||||
this.mescrollItems[i] = vForItem[i]
|
||||
}else{
|
||||
// 普通的refs,不可重复
|
||||
this.mescrollItems[i] = this.$refs["mescrollItem"+i];
|
||||
}
|
||||
}
|
||||
let item = this.mescrollItems[i]
|
||||
return item ? item.mescroll : null
|
||||
},
|
||||
// 切换tab,恢复滚动条位置
|
||||
tabChange(i){
|
||||
let mescroll = this.getMescroll(i);
|
||||
if(mescroll){
|
||||
// 延时(比$nextTick靠谱一些),确保元素已渲染
|
||||
setTimeout(()=>{
|
||||
mescroll.scrollTo(mescroll.getScrollTop(),0)
|
||||
},30)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default MescrollMoreMixin;
|
||||
|
|
@ -1,109 +0,0 @@
|
|||
// 定义在wxs (含renderjs) 逻辑层的数据和方法, 与视图层相互通信
|
||||
const WxsMixin = {
|
||||
data() {
|
||||
return {
|
||||
// 传入wxs视图层的数据 (响应式)
|
||||
wxsProp: {
|
||||
optDown:{}, // 下拉刷新的配置
|
||||
scrollTop:0, // 滚动条的距离
|
||||
bodyHeight:0, // body的高度
|
||||
isDownScrolling:false, // 是否正在下拉刷新中
|
||||
isUpScrolling:false, // 是否正在上拉加载中
|
||||
isScrollBody:true, // 是否为mescroll-body滚动
|
||||
isUpBoth:true, // 上拉加载时,是否同时可以下拉刷新
|
||||
t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
|
||||
},
|
||||
|
||||
// 标记调用wxs视图层的方法
|
||||
callProp: {
|
||||
callType: '', // 方法名
|
||||
t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
|
||||
},
|
||||
|
||||
// 不用wxs的平台使用此处的wxsBiz对象,抹平wxs的写法 (微信小程序和APP使用的wxsBiz对象是./wxs/wxs.wxs)
|
||||
// #ifndef MP-WEIXIN || MP-QQ || APP-PLUS || H5
|
||||
wxsBiz: {
|
||||
//注册列表touchstart事件,用于下拉刷新
|
||||
touchstartEvent: e=> {
|
||||
this.mescroll.touchstartEvent(e);
|
||||
},
|
||||
//注册列表touchmove事件,用于下拉刷新
|
||||
touchmoveEvent: e=> {
|
||||
this.mescroll.touchmoveEvent(e);
|
||||
},
|
||||
//注册列表touchend事件,用于下拉刷新
|
||||
touchendEvent: e=> {
|
||||
this.mescroll.touchendEvent(e);
|
||||
},
|
||||
propObserver(){}, // 抹平wxs的写法
|
||||
callObserver(){} // 抹平wxs的写法
|
||||
},
|
||||
// #endif
|
||||
|
||||
// 不用renderjs的平台使用此处的renderBiz对象,抹平renderjs的写法 (app 和 h5 使用的renderBiz对象是./wxs/renderjs.js)
|
||||
// #ifndef APP-PLUS || H5
|
||||
renderBiz: {
|
||||
propObserver(){} // 抹平renderjs的写法
|
||||
}
|
||||
// #endif
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
// wxs视图层调用逻辑层的回调
|
||||
wxsCall(msg){
|
||||
if(msg.type === 'setWxsProp'){
|
||||
// 更新wxsProp数据 (值改变才触发更新)
|
||||
this.wxsProp = {
|
||||
optDown: this.mescroll.optDown,
|
||||
scrollTop: this.mescroll.getScrollTop(),
|
||||
bodyHeight: this.mescroll.getBodyHeight(),
|
||||
isDownScrolling: this.mescroll.isDownScrolling,
|
||||
isUpScrolling: this.mescroll.isUpScrolling,
|
||||
isUpBoth: this.mescroll.optUp.isBoth,
|
||||
isScrollBody:this.mescroll.isScrollBody,
|
||||
t: Date.now()
|
||||
}
|
||||
}else if(msg.type === 'setLoadType'){
|
||||
// 设置inOffset,outOffset的状态
|
||||
this.downLoadType = msg.downLoadType
|
||||
// 状态挂载到mescroll对象, 以便在其他组件中使用, 比如<me-video>中
|
||||
this.$set(this.mescroll, 'downLoadType', this.downLoadType)
|
||||
// 重置是否加载成功的状态
|
||||
this.$set(this.mescroll, 'isDownEndSuccess', null)
|
||||
}else if(msg.type === 'triggerDownScroll'){
|
||||
// 主动触发下拉刷新
|
||||
this.mescroll.triggerDownScroll();
|
||||
}else if(msg.type === 'endDownScroll'){
|
||||
// 结束下拉刷新
|
||||
this.mescroll.endDownScroll();
|
||||
}else if(msg.type === 'triggerUpScroll'){
|
||||
// 主动触发上拉加载
|
||||
this.mescroll.triggerUpScroll(true);
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5
|
||||
// 配置主动触发wxs显示加载进度的回调
|
||||
this.mescroll.optDown.afterLoading = ()=>{
|
||||
this.callProp = {callType: "showLoading", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
||||
}
|
||||
// 配置主动触发wxs隐藏加载进度的回调
|
||||
this.mescroll.optDown.afterEndDownScroll = ()=>{
|
||||
this.callProp = {callType: "endDownScroll", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
||||
let delay = 300 + (this.mescroll.optDown.beforeEndDelay || 0)
|
||||
setTimeout(()=>{
|
||||
if(this.downLoadType === 4 || this.downLoadType === 0){
|
||||
this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
||||
}
|
||||
// 状态挂载到mescroll对象, 以便在其他组件中使用, 比如<me-video>中
|
||||
this.$set(this.mescroll, 'downLoadType', this.downLoadType)
|
||||
}, delay)
|
||||
}
|
||||
// 初始化wxs的数据
|
||||
this.wxsCall({type: 'setWxsProp'})
|
||||
// #endif
|
||||
}
|
||||
}
|
||||
|
||||
export default WxsMixin;
|
||||
|
|
@ -1,92 +0,0 @@
|
|||
// 使用renderjs直接操作window对象,实现动态控制app和h5的bounce
|
||||
// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止)
|
||||
// https://uniapp.dcloud.io/frame?id=renderjs
|
||||
|
||||
// 与wxs的me实例一致
|
||||
var me = {}
|
||||
|
||||
// 初始化window对象的touch事件 (仅初始化一次)
|
||||
if(window && !window.$mescrollRenderInit){
|
||||
window.$mescrollRenderInit = true
|
||||
|
||||
|
||||
window.addEventListener('touchstart', function(e){
|
||||
if (me.disabled()) return;
|
||||
me.startPoint = me.getPoint(e); // 记录起点
|
||||
}, {passive: true})
|
||||
|
||||
|
||||
window.addEventListener('touchmove', function(e){
|
||||
if (me.disabled()) return;
|
||||
if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce
|
||||
|
||||
var curPoint = me.getPoint(e); // 当前点
|
||||
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
||||
// 向下拉
|
||||
if (moveY > 0) {
|
||||
// 可下拉的条件
|
||||
if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) {
|
||||
|
||||
// 只有touch在mescroll的view上面,才禁止bounce
|
||||
var el = e.target;
|
||||
var isMescrollTouch = false;
|
||||
while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") {
|
||||
var cls = el.classList;
|
||||
if (cls && cls.contains('mescroll-render-touch')) {
|
||||
isMescrollTouch = true
|
||||
break;
|
||||
}
|
||||
el = el.parentNode; // 继续检查其父元素
|
||||
}
|
||||
// 禁止bounce (不会对swiper和iOS侧滑返回造成影响)
|
||||
if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault();
|
||||
}
|
||||
}
|
||||
}, {passive: false})
|
||||
}
|
||||
|
||||
/* 获取滚动条的位置 */
|
||||
me.getScrollTop = function() {
|
||||
return me.scrollTop || 0
|
||||
}
|
||||
|
||||
/* 是否禁用下拉刷新 */
|
||||
me.disabled = function(){
|
||||
return !me.optDown || !me.optDown.use || me.optDown.native
|
||||
}
|
||||
|
||||
/* 根据点击滑动事件获取第一个手指的坐标 */
|
||||
me.getPoint = function(e) {
|
||||
if (!e) {
|
||||
return {x: 0,y: 0}
|
||||
}
|
||||
if (e.touches && e.touches[0]) {
|
||||
return {x: e.touches[0].pageX,y: e.touches[0].pageY}
|
||||
} else if (e.changedTouches && e.changedTouches[0]) {
|
||||
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
|
||||
} else {
|
||||
return {x: e.clientX,y: e.clientY}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听逻辑层数据的变化 (实时更新数据)
|
||||
*/
|
||||
function propObserver(wxsProp) {
|
||||
me.optDown = wxsProp.optDown
|
||||
me.scrollTop = wxsProp.scrollTop
|
||||
me.isDownScrolling = wxsProp.isDownScrolling
|
||||
me.isUpScrolling = wxsProp.isUpScrolling
|
||||
me.isUpBoth = wxsProp.isUpBoth
|
||||
}
|
||||
|
||||
/* 导出模块 */
|
||||
const renderBiz = {
|
||||
data() {
|
||||
return {
|
||||
propObserver: propObserver,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default renderBiz;
|
||||
|
|
@ -1,268 +0,0 @@
|
|||
// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响
|
||||
// https://uniapp.dcloud.io/frame?id=wxs
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
|
||||
|
||||
// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致
|
||||
var me = {}
|
||||
|
||||
// ------ 自定义下拉刷新动画 start ------
|
||||
|
||||
/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */
|
||||
me.onMoving = function (ins, rate, downHight){
|
||||
ins.requestAnimationFrame(function () {
|
||||
ins.selectComponent('.mescroll-wxs-content').setStyle({
|
||||
'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题
|
||||
'transform': 'translateY(' + downHight + 'px)',
|
||||
'transition': ''
|
||||
})
|
||||
// 环形进度条
|
||||
var progress = ins.selectComponent('.mescroll-wxs-progress')
|
||||
progress && progress.setStyle({transform: 'rotate(' + 360 * rate + 'deg)'})
|
||||
})
|
||||
}
|
||||
|
||||
/* 显示下拉刷新进度 */
|
||||
me.showLoading = function (ins){
|
||||
me.downHight = me.optDown.offset
|
||||
ins.requestAnimationFrame(function () {
|
||||
ins.selectComponent('.mescroll-wxs-content').setStyle({
|
||||
'will-change': 'auto',
|
||||
'transform': 'translateY(' + me.downHight + 'px)',
|
||||
'transition': 'transform 300ms'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* 结束下拉 */
|
||||
me.endDownScroll = function (ins){
|
||||
me.downHight = 0;
|
||||
me.isDownScrolling = false;
|
||||
ins.requestAnimationFrame(function () {
|
||||
ins.selectComponent('.mescroll-wxs-content').setStyle({
|
||||
'will-change': 'auto',
|
||||
'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空)
|
||||
'transition': 'transform 300ms'
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */
|
||||
me.clearTransform = function (ins){
|
||||
ins.requestAnimationFrame(function () {
|
||||
ins.selectComponent('.mescroll-wxs-content').setStyle({
|
||||
'will-change': '',
|
||||
'transform': '',
|
||||
'transition': ''
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// ------ 自定义下拉刷新动画 end ------
|
||||
|
||||
/**
|
||||
* 监听逻辑层数据的变化 (实时更新数据)
|
||||
*/
|
||||
function propObserver(wxsProp) {
|
||||
me.optDown = wxsProp.optDown
|
||||
me.scrollTop = wxsProp.scrollTop
|
||||
me.bodyHeight = wxsProp.bodyHeight
|
||||
me.isDownScrolling = wxsProp.isDownScrolling
|
||||
me.isUpScrolling = wxsProp.isUpScrolling
|
||||
me.isUpBoth = wxsProp.isUpBoth
|
||||
me.isScrollBody = wxsProp.isScrollBody
|
||||
me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确
|
||||
}
|
||||
|
||||
/**
|
||||
* 监听逻辑层数据的变化 (调用wxs的方法)
|
||||
*/
|
||||
function callObserver(callProp, oldValue, ins) {
|
||||
if (me.disabled()) return;
|
||||
if(callProp.callType){
|
||||
// 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style
|
||||
if(callProp.callType === 'showLoading'){
|
||||
me.showLoading(ins)
|
||||
}else if(callProp.callType === 'endDownScroll'){
|
||||
me.endDownScroll(ins)
|
||||
}else if(callProp.callType === 'clearTransform'){
|
||||
me.clearTransform(ins)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* touch事件
|
||||
*/
|
||||
function touchstartEvent(e, ins) {
|
||||
me.downHight = 0; // 下拉的距离
|
||||
me.startPoint = me.getPoint(e); // 记录起点
|
||||
me.startTop = me.getScrollTop(); // 记录此时的滚动条位置
|
||||
me.startAngle = 0; // 初始角度
|
||||
me.lastPoint = me.startPoint; // 重置上次move的点
|
||||
me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
|
||||
me.inTouchend = false; // 标记不是touchend
|
||||
|
||||
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
|
||||
}
|
||||
|
||||
function touchmoveEvent(e, ins) {
|
||||
var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
|
||||
|
||||
if (me.disabled()) return isPrevent;
|
||||
|
||||
var scrollTop = me.getScrollTop(); // 当前滚动条的距离
|
||||
var curPoint = me.getPoint(e); // 当前点
|
||||
|
||||
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
||||
|
||||
// 向下拉 && 在顶部
|
||||
// mescroll-body,直接判定在顶部即可
|
||||
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
|
||||
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
|
||||
if (moveY > 0 && (
|
||||
(me.isScrollBody && scrollTop <= 0)
|
||||
||
|
||||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
|
||||
)) {
|
||||
// 可下拉的条件
|
||||
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
|
||||
me.isUpBoth))) {
|
||||
|
||||
// 下拉的角度是否在配置的范围内
|
||||
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
|
||||
if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新
|
||||
|
||||
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
|
||||
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
|
||||
me.inTouchend = true; // 标记执行touchend
|
||||
touchendEvent(e, ins); // 提前触发touchend
|
||||
return isPrevent;
|
||||
}
|
||||
|
||||
isPrevent = false // 小程序是return false
|
||||
|
||||
var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
|
||||
|
||||
// 下拉距离 < 指定距离
|
||||
if (me.downHight < me.optDown.offset) {
|
||||
if (me.movetype !== 1) {
|
||||
me.movetype = 1; // 加入标记,保证只执行一次
|
||||
// me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
|
||||
me.callMethod(ins, {type: 'setLoadType', downLoadType: 1})
|
||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
||||
}
|
||||
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
|
||||
|
||||
// 指定距离 <= 下拉距离
|
||||
} else {
|
||||
if (me.movetype !== 2) {
|
||||
me.movetype = 2; // 加入标记,保证只执行一次
|
||||
// me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
|
||||
me.callMethod(ins, {type: 'setLoadType', downLoadType: 2})
|
||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
|
||||
}
|
||||
if (diff > 0) { // 向下拉
|
||||
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
|
||||
} else { // 向上收
|
||||
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
|
||||
}
|
||||
}
|
||||
|
||||
me.downHight = Math.round(me.downHight) // 取整
|
||||
var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
|
||||
// me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
|
||||
me.onMoving(ins, rate, me.downHight)
|
||||
}
|
||||
}
|
||||
|
||||
me.lastPoint = curPoint; // 记录本次移动的点
|
||||
|
||||
return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
|
||||
}
|
||||
|
||||
function touchendEvent(e, ins) {
|
||||
// 如果下拉区域高度已改变,则需重置回来
|
||||
if (me.isMoveDown) {
|
||||
if (me.downHight >= me.optDown.offset) {
|
||||
// 符合触发刷新的条件
|
||||
me.downHight = me.optDown.offset; // 更新下拉区域高度
|
||||
// me.triggerDownScroll();
|
||||
me.callMethod(ins, {type: 'triggerDownScroll'})
|
||||
} else {
|
||||
// 不符合的话 则重置
|
||||
me.downHight = 0;
|
||||
// me.optDown.endDownScroll && me.optDown.endDownScroll(me);
|
||||
me.callMethod(ins, {type: 'endDownScroll'})
|
||||
}
|
||||
me.movetype = 0;
|
||||
me.isMoveDown = false;
|
||||
} else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件
|
||||
var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
||||
// 上滑
|
||||
if (isScrollUp) {
|
||||
// 需检查滑动的角度
|
||||
var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90]
|
||||
if (angle > 80) {
|
||||
// 检查并触发上拉
|
||||
// me.triggerUpScroll(true);
|
||||
me.callMethod(ins, {type: 'triggerUpScroll'})
|
||||
}
|
||||
}
|
||||
}
|
||||
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
|
||||
}
|
||||
|
||||
/* 是否禁用下拉刷新 */
|
||||
me.disabled = function(){
|
||||
return !me.optDown || !me.optDown.use || me.optDown.native
|
||||
}
|
||||
|
||||
/* 根据点击滑动事件获取第一个手指的坐标 */
|
||||
me.getPoint = function(e) {
|
||||
if (!e) {
|
||||
return {x: 0,y: 0}
|
||||
}
|
||||
if (e.touches && e.touches[0]) {
|
||||
return {x: e.touches[0].pageX,y: e.touches[0].pageY}
|
||||
} else if (e.changedTouches && e.changedTouches[0]) {
|
||||
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
|
||||
} else {
|
||||
return {x: e.clientX,y: e.clientY}
|
||||
}
|
||||
}
|
||||
|
||||
/* 计算两点之间的角度: 区间 [0,90]*/
|
||||
me.getAngle = function (p1, p2) {
|
||||
var x = Math.abs(p1.x - p2.x);
|
||||
var y = Math.abs(p1.y - p2.y);
|
||||
var z = Math.sqrt(x * x + y * y);
|
||||
var angle = 0;
|
||||
if (z !== 0) {
|
||||
angle = Math.asin(y / z) / Math.PI * 180;
|
||||
}
|
||||
return angle
|
||||
}
|
||||
|
||||
/* 获取滚动条的位置 */
|
||||
me.getScrollTop = function() {
|
||||
return me.scrollTop || 0
|
||||
}
|
||||
|
||||
/* 获取body的高度 */
|
||||
me.getBodyHeight = function() {
|
||||
return me.bodyHeight || 0;
|
||||
}
|
||||
|
||||
/* 调用逻辑层的方法 */
|
||||
me.callMethod = function(ins, param) {
|
||||
if(ins) ins.callMethod('wxsCall', param)
|
||||
}
|
||||
|
||||
/* 导出模块 */
|
||||
module.exports = {
|
||||
propObserver: propObserver,
|
||||
callObserver: callObserver,
|
||||
touchstartEvent: touchstartEvent,
|
||||
touchmoveEvent: touchmoveEvent,
|
||||
touchendEvent: touchendEvent
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
{
|
||||
"id": "mescroll-uni",
|
||||
"displayName": "【wxs+renderjs实现】高性能的下拉刷新上拉加载组件",
|
||||
"version": "1.3.7",
|
||||
"description": "支持uni-app的下拉刷新和上拉加载的组件,支持原生页面和局部区域滚动,支持国际化",
|
||||
"keywords": [
|
||||
"下拉刷新",
|
||||
"上拉加载",
|
||||
"翻页",
|
||||
"分页",
|
||||
"wxs"
|
||||
],
|
||||
"repository": "https://github.com/mescroll/mescroll",
|
||||
"engines": {
|
||||
"HBuilderX": "^3.1.0"
|
||||
},
|
||||
"dcloudext": {
|
||||
"category": [
|
||||
"前端组件",
|
||||
"通用组件"
|
||||
],
|
||||
"sale": {
|
||||
"regular": {
|
||||
"price": "0.00"
|
||||
},
|
||||
"sourcecode": {
|
||||
"price": "0.00"
|
||||
}
|
||||
},
|
||||
"contact": {
|
||||
"qq": ""
|
||||
},
|
||||
"declaration": {
|
||||
"ads": "无",
|
||||
"data": "无",
|
||||
"permissions": "无"
|
||||
},
|
||||
"npmurl": "https://www.npmjs.com/package/mescroll-uni"
|
||||
},
|
||||
"uni_modules": {
|
||||
"dependencies": [],
|
||||
"encrypt": [],
|
||||
"platforms": {
|
||||
"cloud": {
|
||||
"tcb": "y",
|
||||
"aliyun": "y"
|
||||
},
|
||||
"client": {
|
||||
"App": {
|
||||
"app-vue": "y",
|
||||
"app-nvue": "y"
|
||||
},
|
||||
"H5-mobile": {
|
||||
"Safari": "y",
|
||||
"Android Browser": "y",
|
||||
"微信浏览器(Android)": "y",
|
||||
"QQ浏览器(Android)": "y"
|
||||
},
|
||||
"H5-pc": {
|
||||
"Chrome": "y",
|
||||
"IE": "y",
|
||||
"Edge": "y",
|
||||
"Firefox": "y",
|
||||
"Safari": "y"
|
||||
},
|
||||
"小程序": {
|
||||
"微信": "y",
|
||||
"阿里": "y",
|
||||
"百度": "y",
|
||||
"字节跳动": "y",
|
||||
"QQ": "y"
|
||||
},
|
||||
"快应用": {
|
||||
"华为": "y",
|
||||
"联盟": "y"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
## mescroll --【wxs+renderjs实现】高性能的下拉刷新上拉加载组件
|
||||
1. mescroll的uni版本 是专门用在uni-app的下拉刷新和上拉加载的组件
|
||||
|
||||
2. mescroll的uni版本 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 ..
|
||||
|
||||
3. mescroll的uni版本 丰富的案例, 自由灵活的api, 超详细的注释, 可让您快速自定义真正属于自己的下拉上拉组件
|
||||
|
||||
<br/>
|
||||
|
||||
|
||||
## 最新文档(1.3.7版本): <a href="https://www.mescroll.com/uni.html">https://www.mescroll.com/uni.html</a>
|
||||
2021-04-13 by 小瑾同学 (文档可能会有缓存,建议打开时刷新一下)
|
||||
|
||||
|
||||
## 1.3.5版本已调整为[uni_modules](https://uniapp.dcloud.io/uni_modules)
|
||||
uni_modules版本的mescroll-body 和 mescroll-empty 支持 [easycom规范](https://uniapp.dcloud.io/collocation/pages?id=easycom)
|
||||
所以 main.js 无需再为mescroll-body注册全局组件
|
||||
所以个别页面要单独使用 mescroll-empty , 也无需手动注册
|
||||
#### 1.3.5以前的用户升级为uni_modules版本:
|
||||
```
|
||||
1. 删除原来的 @/components/mescroll-uni 组件
|
||||
2. 删除 main.js 注册的 mescroll 组件
|
||||
3. 从插件市场导入最新mescroll组件 (1.3.5+uni_modules版本)
|
||||
4. 全局搜索 '@/components/mescroll-uni/' 替换为 '@/uni_modules/mescroll-uni/components/mescroll-uni/'
|
||||
5. mescroll-empty遵循easycom规范, 若某些页面单独使用 'mescroll-empty.vue', 可删除手动导入的代码
|
||||
```
|
||||
|
||||
## 近期已更新优化的内容:
|
||||
1. 微信小程序, app, h5使用高性能wxs和renderjs, 下拉刷新更流畅丝滑, 尤其能明显解决Android小程序下拉卡顿的问题
|
||||
2. 新增`入门极简`示例, 国际化`mescroll-i18n.vue`示例, 轮播吸顶菜单`mescroll-swiper-sticky.vue`示例
|
||||
3. 新增 "局部区域滚动" 的案例: mescroll-body-part.vue 和 mescroll-uni-part.vue
|
||||
4. 新增 me-video 视频组件, 解决APP端视频下拉悬浮错位的问题, 参考 mescroll-options.vue 示例
|
||||
5. 新增 me-tabs 组件,tabs支持水平滑动; 优化mescroll-more和mescroll-swiper的案例, 顶部tab支持水平滑动
|
||||
6. 吸顶悬浮提供了原生sticky和监听滚动条实现的示例: sticky.vue 和 sticky-scroll.vue (推荐使用sticky样式实现)
|
||||
7. mescroll.scrollTo(y)的y支持css选择器, 包括跨自定义组件的后代选择器, 支持滚动到子组件的view (参考 mescroll-options.vue)
|
||||
8. topbar 顶部是否预留状态栏的高度, 默认false; 还可支持设置状态栏背景: 如 '#ffff00', 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)'
|
||||
9. down.bgColor 和 up.bgColor 加载区域的背景,不仅支持色值, 而且还是支持背景图和渐变: 如 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)'
|
||||
10. topbar,bgColor支持一行代码定义background: [https://www.runoob.com/cssref/css3-pr-background.html](https://www.runoob.com/cssref/css3-pr-background.html)
|
||||
<br/>
|
||||
<br/>
|
||||
<a href="https://ext.dcloud.net.cn/plugin?id=343&update_log">查看更多 ... </a>
|
||||
|
||||
<br/>
|
||||
|
||||
#### mescroll不支持nvue,也暂无支持的计划哈,so sorry~
|
||||
|
|
@ -1,500 +0,0 @@
|
|||
import { getTreeData } from '@/utils'
|
||||
// import sss from './sss'
|
||||
const zipCode = []
|
||||
|
||||
|
||||
|
||||
|
||||
var addressList = []; //地址列表
|
||||
var zipCodeList = []; //邮编列表
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 地址数据处理
|
||||
* @param addressList-各级数据对象
|
||||
* @param index-对应的省/市/县区/街道
|
||||
* @param province-只有直辖市会处理为 北京市北京市
|
||||
* @returns <array>
|
||||
*/
|
||||
function formatAddresList(addressList, index, province) {
|
||||
if (index === 1) {
|
||||
//省
|
||||
addressList.province = addressList.name;
|
||||
addressList.type = "province";
|
||||
}
|
||||
if (index === 2) {
|
||||
//市
|
||||
if (addressList.name == "市辖区") {
|
||||
addressList.name = province.name;
|
||||
}
|
||||
addressList.city = addressList.name;
|
||||
addressList.type = "city";
|
||||
}
|
||||
if (index === 3) {
|
||||
//区或者县
|
||||
addressList.area = addressList.name;
|
||||
// addressList.type = "county";
|
||||
addressList.type = "area";
|
||||
}
|
||||
if (index === 4) {
|
||||
//街道
|
||||
addressList.street = addressList.name;
|
||||
addressList.type = "street";
|
||||
}
|
||||
if (addressList.children) {
|
||||
index++;
|
||||
addressList.children?.forEach((res) => {
|
||||
formatAddresList(res, index, addressList);
|
||||
});
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 解析邮编
|
||||
* @param
|
||||
* @returns <array>
|
||||
*/
|
||||
function zipCodeFormat(zipCode) {
|
||||
let list = [];
|
||||
zipCode.forEach((el) => {
|
||||
if (el.child) {
|
||||
el.child.forEach((event) => {
|
||||
if (event.child) {
|
||||
event.child.forEach((element) => {
|
||||
list.push(element.zipcode);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return list;
|
||||
}
|
||||
|
||||
var smartObj = {};
|
||||
/**
|
||||
* 解析邮编
|
||||
* @param event识别的地址
|
||||
* @returns <obj>
|
||||
*/
|
||||
function smart(event,address=[]) {
|
||||
|
||||
let pcassCode = address
|
||||
|
||||
addressList = pcassCode;
|
||||
addressList.forEach((item) => {
|
||||
formatAddresList(item, 1, "");
|
||||
});
|
||||
|
||||
zipCodeList = zipCodeFormat(zipCode);
|
||||
|
||||
event = stripscript(event); //过滤特殊字符
|
||||
|
||||
let obj = {};
|
||||
let copyaddress = JSON.parse(JSON.stringify(event));
|
||||
copyaddress = copyaddress.split(" ");
|
||||
|
||||
copyaddress.forEach((res, index) => {
|
||||
if (res) {
|
||||
if (res.length == 1) {
|
||||
res += "XX"; // 过滤掉一位的名字或者地址
|
||||
}
|
||||
|
||||
let addressObj = smatrAddress(res);
|
||||
obj = Object.assign(obj, addressObj);
|
||||
if (JSON.stringify(addressObj) === "{}") {
|
||||
obj.name = res.replace("XX", "");
|
||||
}
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
}
|
||||
|
||||
function smatrAddress(event) {
|
||||
smartObj = {};
|
||||
let address = event;
|
||||
//address= event.replace(/\s/g, ''); //去除空格
|
||||
address = stripscript(address); //过滤特殊字符
|
||||
//身份证号匹配
|
||||
if (IdentityCodeValid(address)) {
|
||||
smartObj.idCard = address;
|
||||
address = address.replace(address, "");
|
||||
}
|
||||
|
||||
//电话匹配
|
||||
let phone = address.match(
|
||||
/(86-[1][0-9]{10}) | (86[1][0-9]{10})|([1][0-9]{10})/g
|
||||
);
|
||||
if (phone) {
|
||||
smartObj.phone = phone[0];
|
||||
address = address.replace(phone[0], "");
|
||||
}
|
||||
|
||||
//邮编匹配
|
||||
zipCodeList.forEach((res) => {
|
||||
if (address.indexOf(res) != -1) {
|
||||
let num = address.indexOf(res);
|
||||
let code = address.slice(num, num + 6);
|
||||
smartObj.zipCode = code;
|
||||
address = address.replace(code, "");
|
||||
}
|
||||
});
|
||||
|
||||
let matchAddress = "";
|
||||
//省匹配 比如输入北京市朝阳区,会用北 北京 北京市 北京市朝 以此类推在addressList里的province中做匹配,会得到北京市 河北省 天津市等等;
|
||||
let matchProvince = []; //粗略匹配上的省份
|
||||
// for (let begIndex = 0; begIndex < address.length; begIndex++) {
|
||||
matchAddress = "";
|
||||
for (let endIndex = 0; endIndex < address.length; endIndex++) {
|
||||
// if (endIndex > begIndex) {
|
||||
matchAddress = address.slice(0, endIndex + 2);
|
||||
addressList.forEach((res) => {
|
||||
if (res["province"].indexOf(matchAddress) != -1) {
|
||||
matchProvince.push({
|
||||
province: res.province,
|
||||
provinceCode: res.code,
|
||||
matchValue: matchAddress,
|
||||
});
|
||||
}
|
||||
});
|
||||
// }
|
||||
}
|
||||
|
||||
// }
|
||||
|
||||
//统计筛选初略统计出的省份
|
||||
matchProvince.forEach((res) => {
|
||||
res.index = 0;
|
||||
matchProvince.forEach((el) => {
|
||||
if (res.province == el.province) {
|
||||
el.index++;
|
||||
if (res.matchValue.length > el.matchValue.length) {
|
||||
el.matchValue = res.matchValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (matchProvince.length != 0) {
|
||||
let province = matchProvince.reduce((p, v) => (p.index < v.index ? v : p));
|
||||
smartObj.province = province.province;
|
||||
smartObj.provinceCode = province.provinceCode;
|
||||
address = address.replace(province.matchValue, "");
|
||||
}
|
||||
//市查找
|
||||
let matchCity = []; //粗略匹配上的市
|
||||
matchAddress = "";
|
||||
|
||||
for (let endIndex = 0; endIndex < address.length; endIndex++) {
|
||||
matchAddress = address.slice(0, endIndex + 2);
|
||||
addressList.forEach((el) => {
|
||||
// if (el.name == smartObj.province) {
|
||||
if (el.code == smartObj.provinceCode || !smartObj.provinceCode) {
|
||||
if (
|
||||
smartObj.province == "北京市" ||
|
||||
smartObj.province == "天津市" ||
|
||||
smartObj.province == "上海市" ||
|
||||
smartObj.province == "重庆市"
|
||||
) {
|
||||
el.children?.forEach((item) => {
|
||||
item.children?.forEach((res) => {
|
||||
if (res["area"].indexOf(matchAddress) != -1) {
|
||||
matchCity.push({
|
||||
area: res.area,
|
||||
areaCode: res.code,
|
||||
city: item.city,
|
||||
cityCode: item.code,
|
||||
matchValue: matchAddress,
|
||||
province: el.province,
|
||||
provinceCode: el.code,
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
} else {
|
||||
el.children?.forEach((res) => {
|
||||
if(res["city"] == '东莞市' || res["city"] == '中山市'){
|
||||
res.children?.forEach((item) => {
|
||||
if (item["area"].indexOf(matchAddress) != -1) {
|
||||
matchCity.push({
|
||||
area: item.area,
|
||||
areaCode: item.code,
|
||||
city: res.city,
|
||||
cityCode: res.code,
|
||||
matchValue: matchAddress,
|
||||
province: el.province,
|
||||
provinceCode: el.code,
|
||||
});
|
||||
}
|
||||
});
|
||||
}else if (res["city"].indexOf(matchAddress) != -1) {
|
||||
matchCity.push({
|
||||
city: res.city,
|
||||
cityCode: res.code,
|
||||
matchValue: matchAddress,
|
||||
province: el.province,
|
||||
provinceCode: el.code,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// }
|
||||
});
|
||||
}
|
||||
|
||||
//统计筛选初略统计出的市
|
||||
matchCity.forEach((res) => {
|
||||
res.index = 0;
|
||||
matchCity.forEach((el) => {
|
||||
if (res.city == el.city) {
|
||||
el.index++;
|
||||
if (res.matchValue.length > el.matchValue.length) {
|
||||
el.matchValue = res.matchValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if (matchCity.length != 0) {
|
||||
let city = matchCity.reduce((p, v) => (p.index < v.index ? v : p));
|
||||
smartObj.city = city.city;
|
||||
smartObj.cityCode = city.cityCode;
|
||||
smartObj.area = city.area;
|
||||
smartObj.areaCode = city.areaCode;
|
||||
if (!smartObj.province) {
|
||||
smartObj.province = city.province;
|
||||
smartObj.provinceCode = city.provinceCode;
|
||||
}
|
||||
address = address.replace(city.matchValue, "");
|
||||
}
|
||||
|
||||
//区县查找
|
||||
let matchCounty = []; //粗略匹配上的区县
|
||||
matchAddress = "";
|
||||
for (let endIndex = 0; endIndex < address.length; endIndex++) {
|
||||
matchAddress = address.slice(0, endIndex + 2);
|
||||
addressList.forEach((el) => {
|
||||
// if (el.name == smartObj.province) {
|
||||
if (
|
||||
smartObj.province == "北京市" ||
|
||||
smartObj.province == "天津市" ||
|
||||
smartObj.province == "上海市" ||
|
||||
smartObj.province == "重庆市"
|
||||
) {
|
||||
//nothing
|
||||
} else {
|
||||
el.children?.forEach((item) => {
|
||||
// if (item.name == smartObj.city) {
|
||||
item.children?.forEach((res) => {
|
||||
if (res["area"].indexOf(matchAddress) != -1) {
|
||||
if (smartObj.province) {
|
||||
if (res.parent_id == smartObj.cityCode) {
|
||||
matchCounty.push({
|
||||
area: res.area,
|
||||
areaCode: res.code,
|
||||
city: item.city,
|
||||
cityCode: item.code,
|
||||
matchValue: matchAddress,
|
||||
province: el.province,
|
||||
provinceCode: el.code,
|
||||
});
|
||||
}
|
||||
} else if (!smartObj.province && !smartObj.city) {
|
||||
matchCounty.push({
|
||||
area: res.area,
|
||||
areaCode: res.code,
|
||||
city: item.city,
|
||||
cityCode: item.code,
|
||||
matchValue: matchAddress,
|
||||
province: el.province,
|
||||
provinceCode: el.code,
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
// }
|
||||
});
|
||||
}
|
||||
// }
|
||||
});
|
||||
}
|
||||
//统计筛选初略统计出的区县
|
||||
matchCounty.forEach((res) => {
|
||||
res.index = 0;
|
||||
matchCounty.forEach((el) => {
|
||||
if (res.city == el.city) {
|
||||
el.index++;
|
||||
if (res.matchValue.length > el.matchValue.length) {
|
||||
el.matchValue = res.matchValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
if (matchCounty.length != 0) {
|
||||
let city = matchCounty.reduce((p, v) => (p.index < v.index ? v : p));
|
||||
smartObj.area = city.area;
|
||||
smartObj.areaCode = city.areaCode;
|
||||
if (!smartObj.province) {
|
||||
smartObj.province = city.province;
|
||||
smartObj.provinceCode = city.provinceCode;
|
||||
}
|
||||
if (!smartObj.city) {
|
||||
smartObj.city = city.city;
|
||||
smartObj.cityCode = city.cityCode;
|
||||
}
|
||||
address = address.replace(city.matchValue, "");
|
||||
}
|
||||
|
||||
//街道查找
|
||||
let matchStreet = []; //粗略匹配上的街道查
|
||||
matchAddress = "";
|
||||
for (let endIndex = 0; endIndex < address.length; endIndex++) {
|
||||
matchAddress = address.slice(0, endIndex + 3);
|
||||
addressList.forEach((el) => {
|
||||
if (el.name == smartObj.province) {
|
||||
if (
|
||||
smartObj.province == "北京市" ||
|
||||
smartObj.province == "天津市" ||
|
||||
smartObj.province == "上海市" ||
|
||||
smartObj.province == "重庆市"
|
||||
) {
|
||||
//nothing
|
||||
} else {
|
||||
el.children.forEach((element) => {
|
||||
if (element.name == smartObj.city) {
|
||||
element.children?.forEach((item) => {
|
||||
if (item.name == smartObj.area) {
|
||||
item.children?.forEach((res) => {
|
||||
if (res["street"].indexOf(matchAddress) != -1) {
|
||||
matchStreet.push({
|
||||
street: res.street,
|
||||
streetCode: res.code,
|
||||
matchValue: matchAddress,
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
//统计筛选初略统计出的区县
|
||||
matchStreet.forEach((res) => {
|
||||
res.index = 0;
|
||||
matchStreet.forEach((el) => {
|
||||
if (res.city == el.city) {
|
||||
el.index++;
|
||||
if (res.matchValue.length > el.matchValue.length) {
|
||||
el.matchValue = res.matchValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
if (matchStreet.length != 0) {
|
||||
let city = matchStreet.reduce((p, v) => (p.index < v.index ? v : p));
|
||||
smartObj.street = city.street;
|
||||
smartObj.streetCode = city.streetCode;
|
||||
address = address.replace(city.matchValue, "");
|
||||
}
|
||||
//姓名查找
|
||||
if (smartObj.province) {
|
||||
smartObj.address = address;
|
||||
}
|
||||
|
||||
return smartObj;
|
||||
}
|
||||
////过滤特殊字符
|
||||
function stripscript(s) {
|
||||
s = s.replace(/(\d{3})-(\d{4})-(\d{4})/g, "$1$2$3");
|
||||
s = s.replace(/(\d{3}) (\d{4}) (\d{4})/g, "$1$2$3");
|
||||
var pattern = new RegExp(
|
||||
"[`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“’。,、?-]"
|
||||
);
|
||||
var rs = "";
|
||||
for (var i = 0; i < s.length; i++) {
|
||||
rs = rs + s.substr(i, 1).replace(pattern, " ");
|
||||
}
|
||||
rs = rs.replace(/[\r\n]/g, "");
|
||||
return rs;
|
||||
}
|
||||
|
||||
function IdentityCodeValid(code) {
|
||||
let pass;
|
||||
var city = {
|
||||
11: "北京",
|
||||
12: "天津",
|
||||
13: "河北",
|
||||
14: "山西",
|
||||
15: "内蒙古",
|
||||
21: "辽宁",
|
||||
22: "吉林",
|
||||
23: "黑龙江 ",
|
||||
31: "上海",
|
||||
32: "江苏",
|
||||
33: "浙江",
|
||||
34: "安徽",
|
||||
35: "福建",
|
||||
36: "江西",
|
||||
37: "山东",
|
||||
41: "河南",
|
||||
42: "湖北 ",
|
||||
43: "湖南",
|
||||
44: "广东",
|
||||
45: "广西",
|
||||
46: "海南",
|
||||
50: "重庆",
|
||||
51: "四川",
|
||||
52: "贵州",
|
||||
53: "云南",
|
||||
54: "西藏 ",
|
||||
61: "陕西",
|
||||
62: "甘肃",
|
||||
63: "青海",
|
||||
64: "宁夏",
|
||||
65: "新疆",
|
||||
71: "台湾",
|
||||
81: "香港",
|
||||
82: "澳门",
|
||||
91: "国外 ",
|
||||
};
|
||||
var tip = "";
|
||||
pass = true;
|
||||
|
||||
if (!code || !/^\d{17}(\d|X)$/i.test(code)) {
|
||||
tip = "身份证号格式错误";
|
||||
pass = false;
|
||||
} else if (!city[code.substr(0, 2)]) {
|
||||
tip = "地址编码错误";
|
||||
pass = false;
|
||||
} else {
|
||||
//18位身份证需要验证最后一位校验位
|
||||
if (code.length == 18) {
|
||||
code = code.split("");
|
||||
//∑(ai×Wi)(mod 11)
|
||||
//加权因子
|
||||
var factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
|
||||
//校验位
|
||||
var parity = [1, 0, "X", 9, 8, 7, 6, 5, 4, 3, 2];
|
||||
var sum = 0;
|
||||
var ai = 0;
|
||||
var wi = 0;
|
||||
for (var i = 0; i < 17; i++) {
|
||||
ai = code[i];
|
||||
wi = factor[i];
|
||||
sum += ai * wi;
|
||||
}
|
||||
var last = parity[sum % 11];
|
||||
if (parity[sum % 11] != code[17]) {
|
||||
tip = "校验位错误";
|
||||
pass = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return pass;
|
||||
}
|
||||
|
||||
export default smart;
|
||||
|
|
@ -1,121 +0,0 @@
|
|||
import {
|
||||
http
|
||||
} from '@/utils/request'
|
||||
|
||||
const PACKAGE_INFO_KEY = '__package_info__'
|
||||
|
||||
export function callCheckVersion() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef APP-PLUS
|
||||
const {
|
||||
platform
|
||||
} = uni.getSystemInfoSync()
|
||||
plus.runtime.getProperty(plus.runtime.appid, widgetInfo => {
|
||||
// console.log("==========plus");
|
||||
// console.log(plus.runtime);
|
||||
// console.log("==========widget");
|
||||
// console.log(widgetInfo);
|
||||
|
||||
http.get('/v1/app-version', {
|
||||
params: {
|
||||
cate: platform,
|
||||
// v:plus.runtime.versionCode,
|
||||
v: process.env.VUE_APP_VERSION_CODE
|
||||
},
|
||||
custom: {
|
||||
toast: false
|
||||
}
|
||||
}).then(resData => {
|
||||
resolve({
|
||||
...resData,
|
||||
platform
|
||||
})
|
||||
})
|
||||
})
|
||||
// #endif
|
||||
// #ifndef APP-PLUS
|
||||
reject({
|
||||
message: '请在App中使用'
|
||||
})
|
||||
// #endif
|
||||
})
|
||||
}
|
||||
|
||||
export function checkUpdate() {
|
||||
// #ifdef APP-PLUS
|
||||
return new Promise((resolve, reject) => {
|
||||
callCheckVersion().then(e => {
|
||||
if (!e) return;
|
||||
// if (e.v > plus.runtime.versionCode) {
|
||||
// uni.showModal({
|
||||
// title: '提示',
|
||||
// content: `${e.v}:${e.name}======${process.env.VUE_APP_VERSION_CODE}:${process.env.VUE_APP_VERSION}`,
|
||||
// success: function(res) {
|
||||
// if (res.confirm) {
|
||||
// console.log('用户点击确定');
|
||||
// } else if (res.cancel) {
|
||||
// console.log('用户点击取消');
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
|
||||
if (e.v > process.env.VUE_APP_VERSION_CODE) {
|
||||
const point = e.link.lastIndexOf('.') + 1
|
||||
const pp = e.link.substr(point)
|
||||
const result = {
|
||||
is_silently: pp == 'wgt',
|
||||
url: e.link,
|
||||
platform: e.platform,
|
||||
type: pp == 'wgt' ? 'wgt' : e.platform,
|
||||
version: e.v,
|
||||
is_mandatory: e.is_force,
|
||||
...e
|
||||
}
|
||||
|
||||
resolve(result)
|
||||
|
||||
uni.setStorageSync(PACKAGE_INFO_KEY, result)
|
||||
|
||||
// 静默更新,只有wgt有
|
||||
if (result.is_silently) {
|
||||
uni.downloadFile({
|
||||
url: result.url,
|
||||
success: res => {
|
||||
if (res.statusCode == 200) {
|
||||
// 下载好直接安装,下次启动生效
|
||||
plus.runtime.install(res.tempFilePath, {
|
||||
force: true
|
||||
}, () => {
|
||||
console.log("热更成功");
|
||||
uni.navigateTo({
|
||||
url: `/pages/upgrade_popup/index?local_storage_key=${PACKAGE_INFO_KEY}`,
|
||||
fail: (err) => {
|
||||
uni.removeStorageSync(PACKAGE_INFO_KEY)
|
||||
}
|
||||
})
|
||||
}, (e) => {
|
||||
console.log("热更失败");
|
||||
console.log(e);
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
uni.navigateTo({
|
||||
url: `/pages/upgrade_popup/index?local_storage_key=${PACKAGE_INFO_KEY}`,
|
||||
fail: (err) => {
|
||||
uni.removeStorageSync(PACKAGE_INFO_KEY)
|
||||
}
|
||||
})
|
||||
}
|
||||
}).catch(err => {
|
||||
reject(err)
|
||||
})
|
||||
})
|
||||
// // #endif
|
||||
}
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
|
||||
import { division } from './index'
|
||||
|
||||
export function payStatusText(value) {
|
||||
const status = [{
|
||||
value: -1,
|
||||
label: '其它'
|
||||
}, {
|
||||
value: 0,
|
||||
label: '待付款'
|
||||
}, {
|
||||
value: 1,
|
||||
label: '待发货'
|
||||
}, {
|
||||
value: 2,
|
||||
label: '发货中'
|
||||
}, , {
|
||||
value: 3,
|
||||
label: '已发货'
|
||||
}, {
|
||||
value: 9,
|
||||
label: '已完成'
|
||||
}, {
|
||||
value: 10,
|
||||
label: '已取消'
|
||||
}]
|
||||
const obj = status.find((e) => e?.value == value) ?? {}
|
||||
return obj?.label ?? value
|
||||
}
|
||||
|
||||
//售后提示文字
|
||||
export function saleText(value) {
|
||||
const status = [
|
||||
{
|
||||
value: 1,
|
||||
label: '待补充资料'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '待客服审核中'
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '待客户确认中'
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '待处理'
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
label: '已完成'
|
||||
},
|
||||
{
|
||||
value: 7,
|
||||
label: '已取消'
|
||||
},
|
||||
]
|
||||
const obj = status.find((e) => e?.value == value) ?? {}
|
||||
return obj?.label ?? value
|
||||
}
|
||||
//物流状态
|
||||
export function LogisticsText(value) {
|
||||
const status = [
|
||||
{
|
||||
value: 0,
|
||||
label: '在途'
|
||||
},
|
||||
{
|
||||
value: 1,
|
||||
label: '揽收'
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '疑难'
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '签收'
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '退签'
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
label: '派件'
|
||||
},
|
||||
{
|
||||
value: 6,
|
||||
label: '拒签'
|
||||
},
|
||||
{
|
||||
value: 10,
|
||||
label: '未知'
|
||||
},
|
||||
{
|
||||
value: 11,
|
||||
label: '签收'
|
||||
},
|
||||
]
|
||||
const obj = status.find((e) => e?.value == value) ?? {}
|
||||
return obj?.label ?? value
|
||||
}
|
||||
export function typeList(value) {
|
||||
let status = [
|
||||
{
|
||||
value: 1,
|
||||
label: '退款退货',
|
||||
},
|
||||
{
|
||||
value: 2,
|
||||
label: '退款',
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '换货',
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '漏发',
|
||||
},
|
||||
{
|
||||
value: 5,
|
||||
label: '其他',
|
||||
},
|
||||
]
|
||||
const obj = status.find((e) => e?.value == value) ?? {}
|
||||
return obj?.label ?? value
|
||||
}
|
||||
//支付方式
|
||||
export function payment(val) {
|
||||
let pay
|
||||
switch (val) {
|
||||
case "wxpay":
|
||||
pay = "微信支付"
|
||||
break;
|
||||
case "alipay":
|
||||
pay = "支付宝"
|
||||
break;
|
||||
case "wallet":
|
||||
pay = "可提"
|
||||
break;
|
||||
case "balance":
|
||||
pay = "余额"
|
||||
break;
|
||||
case "offline":
|
||||
pay = "线下支付"
|
||||
break;
|
||||
}
|
||||
return pay
|
||||
}
|
||||
export function formatDate(timestamp, fmt = 'yyyy-MM-dd HH:mm:ss') {
|
||||
const date = new Date(timestamp)
|
||||
if (/(y+)/.test(fmt)) {
|
||||
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
|
||||
}
|
||||
const o = {
|
||||
'M+': date.getMonth() + 1,
|
||||
'd+': date.getDate(),
|
||||
'H+': date.getHours(),
|
||||
'm+': date.getMinutes(),
|
||||
's+': date.getSeconds(),
|
||||
'f+': date.getMilliseconds()
|
||||
}
|
||||
for (const k in o) {
|
||||
if (new RegExp(`(${k})`).test(fmt)) {
|
||||
const str = o[k] + ''
|
||||
fmt = fmt.replace(RegExp.$1, RegExp.$1.length === 1 ? str : _pad(str))
|
||||
}
|
||||
}
|
||||
return fmt
|
||||
}
|
||||
|
||||
export function _pad(n, z = 2) {
|
||||
return ('00' + n).slice(-z)
|
||||
}
|
||||
//性别
|
||||
export function filterGender(val) {
|
||||
let gender
|
||||
switch (val) {
|
||||
case "unknown":
|
||||
gender = "未知 "
|
||||
break;
|
||||
case 'male':
|
||||
gender = "男"
|
||||
break;
|
||||
case 'female':
|
||||
gender = "女"
|
||||
break
|
||||
}
|
||||
return gender
|
||||
}
|
||||
|
||||
//截取小数点后面的数字
|
||||
export function intercept(val) {
|
||||
return String(val).split('.')[0]
|
||||
}
|
||||
//手机号处理
|
||||
export function showPhone(val) {
|
||||
return val?.replace(/^(\d{3})\d+(\d{4})$/, '$1****$2')
|
||||
}
|
||||
//银行卡处理
|
||||
export function hideBankCard(value) {
|
||||
if (value && value.length > 8) {
|
||||
return `${value.substring(0, 4)} ${"*".repeat(value.length - 8).replace(/(.{4})/g, `$1 `)}${value.length % 4 ? " " : ""}${value.slice(-4)}`;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
//价格格式化
|
||||
export function numFormat(num, n = 2) {
|
||||
if (num >= 10000) {
|
||||
num = parseFloat(division(num , 10000).toFixed(n)) + 'W';
|
||||
}
|
||||
return num;
|
||||
}
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
/**
|
||||
* 获取月份天数
|
||||
* @param {number} year
|
||||
* @param {number} month
|
||||
* @returns 天数
|
||||
*/
|
||||
export function getDayCount(year, month) {
|
||||
if (month == 2) {
|
||||
if (year % 400 === 0 || (year % 4 === 0 && year % 100)) { // 闰年
|
||||
return 29;
|
||||
}
|
||||
return 28;
|
||||
}
|
||||
return [1, 3, 5, 7, 8, 10, 12].indexOf(month) >= 0 ? 31 : 30;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 计算指定日期 后几天的日期
|
||||
* @param {Date} date
|
||||
* @param {Number} count
|
||||
*/
|
||||
export function dateAfterDayCount(date = Date.now(), count = 1) {
|
||||
date = new Date(date)
|
||||
date.setDate(date.getDate() + count)
|
||||
return date
|
||||
}
|
||||
|
||||
|
||||
// 加法函数(精度丢失问题)
|
||||
export function add(arg1, arg2) {
|
||||
let r1, r2, m;
|
||||
try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
|
||||
try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }
|
||||
m = Math.pow(10, Math.max(r1, r2));
|
||||
return (arg1 * m + arg2 * m) / m
|
||||
}
|
||||
|
||||
|
||||
// 减法函数(精度丢失问题)
|
||||
export function sub(arg1, arg2) {
|
||||
let r1, r2, m, n;
|
||||
try { r1 = arg1.toString().split(".")[1].length } catch (e) { r1 = 0 }
|
||||
try { r2 = arg2.toString().split(".")[1].length } catch (e) { r2 = 0 }
|
||||
m = Math.pow(10, Math.max(r1, r2));
|
||||
n = (r1 >= r2) ? r1 : r2;
|
||||
return Number(((arg1 * m - arg2 * m) / m).toFixed(n));
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 乘法函数(精度丢失问题)
|
||||
export function mcl(num1, num2) {
|
||||
let m = 0, s1 = num1.toString(), s2 = num2.toString();
|
||||
try { m += s1.split(".")[1].length } catch (e) { }
|
||||
try { m += s2.split(".")[1].length } catch (e) { }
|
||||
return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// 除法函数(精度丢失问题)
|
||||
export function division(num1, num2) {
|
||||
let t1, t2, r1, r2;
|
||||
try {
|
||||
t1 = num1.toString().split('.')[1].length;
|
||||
} catch (e) {
|
||||
t1 = 0;
|
||||
}
|
||||
try {
|
||||
t2 = num2.toString().split(".")[1].length;
|
||||
} catch (e) {
|
||||
t2 = 0;
|
||||
}
|
||||
r1 = Number(num1.toString().replace(".", ""));
|
||||
r2 = Number(num2.toString().replace(".", ""));
|
||||
return (r1 / r2) * Math.pow(10, t2 - t1);
|
||||
}
|
||||
|
||||
|
||||
//递归生成树形结构
|
||||
export function getTreeData(data, pid, pidName = 'parentId', idName = 'id', childrenName = 'children', key = 'key', isNull = true) {
|
||||
let arr = [];
|
||||
|
||||
for (let i = 0; i < data.length; i++) {
|
||||
if (data[i][pidName] == pid) {
|
||||
data[i][key] = data[i][idName];
|
||||
data[i][childrenName] = getTreeData(data, data[i][idName], pidName, idName, childrenName, key, isNull);
|
||||
if (isNull && data[i][childrenName].length == 0) delete data[i][childrenName]
|
||||
arr.push(data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return arr;
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
import {
|
||||
http
|
||||
} from '@/utils/request'
|
||||
|
||||
import store from '@/store'
|
||||
|
||||
// 获取登录凭证(code)
|
||||
export function getWxCode() {
|
||||
return new Promise((resolve, reject) => {
|
||||
uni.login({
|
||||
success(res) {
|
||||
resolve(res.code);
|
||||
},
|
||||
fail(res) {
|
||||
reject(res);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
//小程序静默登录
|
||||
export async function wxMnpLogin(){
|
||||
store.commit('user/LOGOUT')
|
||||
const code = await getWxCode()
|
||||
silentLogin({code})
|
||||
}
|
||||
|
||||
|
||||
async function silentLogin(data){
|
||||
const provider = 'wechat-mini'
|
||||
const resData = await http.post(`/v1/socialite/code-auth/${provider}`,data,{
|
||||
custom:{
|
||||
toast:false
|
||||
}
|
||||
})
|
||||
store.commit('user/LOGIN', resData.token)
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
|
||||
var base64EncodeChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
var base64DecodeChars = new Array(
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
|
||||
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
|
||||
52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, -1, -1, -1,
|
||||
-1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
|
||||
15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
|
||||
-1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
|
||||
41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1);
|
||||
function encode(str) {
|
||||
var out, i, len;
|
||||
var c1, c2, c3;
|
||||
len = str.length;
|
||||
i = 0;
|
||||
out = "";
|
||||
while (i < len) {
|
||||
c1 = str.charCodeAt(i++) & 0xff;
|
||||
if (i == len) {
|
||||
out += base64EncodeChars.charAt(c1 >> 2);
|
||||
out += base64EncodeChars.charAt((c1 & 0x3) << 4);
|
||||
out += "==";
|
||||
break;
|
||||
}
|
||||
c2 = str.charCodeAt(i++);
|
||||
if (i == len) {
|
||||
out += base64EncodeChars.charAt(c1 >> 2);
|
||||
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
|
||||
out += base64EncodeChars.charAt((c2 & 0xF) << 2);
|
||||
out += "=";
|
||||
break;
|
||||
}
|
||||
c3 = str.charCodeAt(i++);
|
||||
out += base64EncodeChars.charAt(c1 >> 2);
|
||||
out += base64EncodeChars.charAt(((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4));
|
||||
out += base64EncodeChars.charAt(((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6));
|
||||
out += base64EncodeChars.charAt(c3 & 0x3F);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function decode(str) {
|
||||
var c1, c2, c3, c4;
|
||||
var i, len, out;
|
||||
len = str.length;
|
||||
i = 0;
|
||||
out = "";
|
||||
while (i < len) {
|
||||
/* c1 */
|
||||
do {
|
||||
c1 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
|
||||
} while (i < len && c1 == -1);
|
||||
if (c1 == -1)
|
||||
break;
|
||||
/* c2 */
|
||||
do {
|
||||
c2 = base64DecodeChars[str.charCodeAt(i++) & 0xff];
|
||||
} while (i < len && c2 == -1);
|
||||
if (c2 == -1)
|
||||
break;
|
||||
out += String.fromCharCode((c1 << 2) | ((c2 & 0x30) >> 4));
|
||||
/* c3 */
|
||||
do {
|
||||
c3 = str.charCodeAt(i++) & 0xff;
|
||||
if (c3 == 61)
|
||||
return out;
|
||||
c3 = base64DecodeChars[c3];
|
||||
} while (i < len && c3 == -1);
|
||||
if (c3 == -1)
|
||||
break;
|
||||
out += String.fromCharCode(((c2 & 0XF) << 4) | ((c3 & 0x3C) >> 2));
|
||||
/* c4 */
|
||||
do {
|
||||
c4 = str.charCodeAt(i++) & 0xff;
|
||||
if (c4 == 61)
|
||||
return out;
|
||||
c4 = base64DecodeChars[c4];
|
||||
} while (i < len && c4 == -1);
|
||||
if (c4 == -1)
|
||||
break;
|
||||
out += String.fromCharCode(((c3 & 0x03) << 6) | c4);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
function utf16to8(str) {
|
||||
var out, i, len, c;
|
||||
out = "";
|
||||
len = str.length;
|
||||
for (i = 0; i < len; i++) {
|
||||
c = str.charCodeAt(i);
|
||||
if ((c >= 0x0001) && (c <= 0x007F)) {
|
||||
out += str.charAt(i);
|
||||
} else if (c > 0x07FF) {
|
||||
out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
|
||||
out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
|
||||
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
|
||||
} else {
|
||||
out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
|
||||
out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function utf8to16(str) {
|
||||
var out, i, len, c;
|
||||
var char2, char3;
|
||||
out = "";
|
||||
len = str.length;
|
||||
i = 0;
|
||||
while (i < len) {
|
||||
c = str.charCodeAt(i++);
|
||||
switch (c >> 4) {
|
||||
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
|
||||
// 0xxxxxxx
|
||||
out += str.charAt(i - 1);
|
||||
break;
|
||||
case 12: case 13:
|
||||
// 110x xxxx 10xx xxxx
|
||||
char2 = str.charCodeAt(i++);
|
||||
out += String.fromCharCode(((c & 0x1F) << 6) | (char2 & 0x3F));
|
||||
break;
|
||||
case 14:
|
||||
// 1110 xxxx 10xx xxxx 10xx xxxx
|
||||
char2 = str.charCodeAt(i++);
|
||||
char3 = str.charCodeAt(i++);
|
||||
out += String.fromCharCode(((c & 0x0F) << 12) |
|
||||
((char2 & 0x3F) << 6) |
|
||||
((char3 & 0x3F) << 0));
|
||||
break;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
module.exports = {
|
||||
encode: encode,
|
||||
decode: decode,
|
||||
utf16to8: utf16to8,
|
||||
utf8to16: utf8to16
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
const Crypto = {};
|
||||
|
||||
(function(){
|
||||
|
||||
var base64map = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
|
||||
// Crypto utilities
|
||||
var util = Crypto.util = {
|
||||
|
||||
// Bit-wise rotate left
|
||||
rotl: function (n, b) {
|
||||
return (n << b) | (n >>> (32 - b));
|
||||
},
|
||||
|
||||
// Bit-wise rotate right
|
||||
rotr: function (n, b) {
|
||||
return (n << (32 - b)) | (n >>> b);
|
||||
},
|
||||
|
||||
// Swap big-endian to little-endian and vice versa
|
||||
endian: function (n) {
|
||||
|
||||
// If number given, swap endian
|
||||
if (n.constructor == Number) {
|
||||
return util.rotl(n, 8) & 0x00FF00FF |
|
||||
util.rotl(n, 24) & 0xFF00FF00;
|
||||
}
|
||||
|
||||
// Else, assume array and swap all items
|
||||
for (var i = 0; i < n.length; i++)
|
||||
n[i] = util.endian(n[i]);
|
||||
return n;
|
||||
|
||||
},
|
||||
|
||||
// Generate an array of any length of random bytes
|
||||
randomBytes: function (n) {
|
||||
for (var bytes = []; n > 0; n--)
|
||||
bytes.push(Math.floor(Math.random() * 256));
|
||||
return bytes;
|
||||
},
|
||||
|
||||
// Convert a string to a byte array
|
||||
stringToBytes: function (str) {
|
||||
var bytes = [];
|
||||
for (var i = 0; i < str.length; i++)
|
||||
bytes.push(str.charCodeAt(i));
|
||||
return bytes;
|
||||
},
|
||||
|
||||
// Convert a byte array to a string
|
||||
bytesToString: function (bytes) {
|
||||
var str = [];
|
||||
for (var i = 0; i < bytes.length; i++)
|
||||
str.push(String.fromCharCode(bytes[i]));
|
||||
return str.join("");
|
||||
},
|
||||
|
||||
// Convert a string to big-endian 32-bit words
|
||||
stringToWords: function (str) {
|
||||
var words = [];
|
||||
for (var c = 0, b = 0; c < str.length; c++, b += 8)
|
||||
words[b >>> 5] |= str.charCodeAt(c) << (24 - b % 32);
|
||||
return words;
|
||||
},
|
||||
|
||||
// Convert a byte array to big-endian 32-bits words
|
||||
bytesToWords: function (bytes) {
|
||||
var words = [];
|
||||
for (var i = 0, b = 0; i < bytes.length; i++, b += 8)
|
||||
words[b >>> 5] |= bytes[i] << (24 - b % 32);
|
||||
return words;
|
||||
},
|
||||
|
||||
// Convert big-endian 32-bit words to a byte array
|
||||
wordsToBytes: function (words) {
|
||||
var bytes = [];
|
||||
for (var b = 0; b < words.length * 32; b += 8)
|
||||
bytes.push((words[b >>> 5] >>> (24 - b % 32)) & 0xFF);
|
||||
return bytes;
|
||||
},
|
||||
|
||||
// Convert a byte array to a hex string
|
||||
bytesToHex: function (bytes) {
|
||||
var hex = [];
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
hex.push((bytes[i] >>> 4).toString(16));
|
||||
hex.push((bytes[i] & 0xF).toString(16));
|
||||
}
|
||||
return hex.join("");
|
||||
},
|
||||
|
||||
// Convert a hex string to a byte array
|
||||
hexToBytes: function (hex) {
|
||||
var bytes = [];
|
||||
for (var c = 0; c < hex.length; c += 2)
|
||||
bytes.push(parseInt(hex.substr(c, 2), 16));
|
||||
return bytes;
|
||||
},
|
||||
|
||||
// Convert a byte array to a base-64 string
|
||||
bytesToBase64: function (bytes) {
|
||||
|
||||
// Use browser-native function if it exists
|
||||
// if (typeof btoa == "function") return btoa(util.bytesToString(bytes));
|
||||
|
||||
var base64 = [],
|
||||
overflow;
|
||||
|
||||
for (var i = 0; i < bytes.length; i++) {
|
||||
switch (i % 3) {
|
||||
case 0:
|
||||
base64.push(base64map.charAt(bytes[i] >>> 2));
|
||||
overflow = (bytes[i] & 0x3) << 4;
|
||||
break;
|
||||
case 1:
|
||||
base64.push(base64map.charAt(overflow | (bytes[i] >>> 4)));
|
||||
overflow = (bytes[i] & 0xF) << 2;
|
||||
break;
|
||||
case 2:
|
||||
base64.push(base64map.charAt(overflow | (bytes[i] >>> 6)));
|
||||
base64.push(base64map.charAt(bytes[i] & 0x3F));
|
||||
overflow = -1;
|
||||
}
|
||||
}
|
||||
|
||||
// Encode overflow bits, if there are any
|
||||
if (overflow != undefined && overflow != -1)
|
||||
base64.push(base64map.charAt(overflow));
|
||||
|
||||
// Add padding
|
||||
while (base64.length % 4 != 0) base64.push("=");
|
||||
|
||||
return base64.join("");
|
||||
|
||||
},
|
||||
|
||||
// Convert a base-64 string to a byte array
|
||||
base64ToBytes: function (base64) {
|
||||
|
||||
// Use browser-native function if it exists
|
||||
if (typeof atob == "function") return util.stringToBytes(atob(base64));
|
||||
|
||||
// Remove non-base-64 characters
|
||||
base64 = base64.replace(/[^A-Z0-9+\/]/ig, "");
|
||||
|
||||
var bytes = [];
|
||||
|
||||
for (var i = 0; i < base64.length; i++) {
|
||||
switch (i % 4) {
|
||||
case 1:
|
||||
bytes.push((base64map.indexOf(base64.charAt(i - 1)) << 2) |
|
||||
(base64map.indexOf(base64.charAt(i)) >>> 4));
|
||||
break;
|
||||
case 2:
|
||||
bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & 0xF) << 4) |
|
||||
(base64map.indexOf(base64.charAt(i)) >>> 2));
|
||||
break;
|
||||
case 3:
|
||||
bytes.push(((base64map.indexOf(base64.charAt(i - 1)) & 0x3) << 6) |
|
||||
(base64map.indexOf(base64.charAt(i))));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bytes;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
// Crypto mode namespace
|
||||
Crypto.mode = {};
|
||||
|
||||
})();
|
||||
|
||||
module.exports = Crypto;
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
const Crypto = require('./crypto.js');
|
||||
|
||||
(function(){
|
||||
|
||||
// Shortcut
|
||||
var util = Crypto.util;
|
||||
|
||||
Crypto.HMAC = function (hasher, message, key, options) {
|
||||
|
||||
// Allow arbitrary length keys
|
||||
key = key.length > hasher._blocksize * 4 ?
|
||||
hasher(key, { asBytes: true }) :
|
||||
util.stringToBytes(key);
|
||||
|
||||
// XOR keys with pad constants
|
||||
var okey = key,
|
||||
ikey = key.slice(0);
|
||||
for (var i = 0; i < hasher._blocksize * 4; i++) {
|
||||
okey[i] ^= 0x5C;
|
||||
ikey[i] ^= 0x36;
|
||||
}
|
||||
|
||||
var hmacbytes = hasher(util.bytesToString(okey) +
|
||||
hasher(util.bytesToString(ikey) + message, { asString: true }),
|
||||
{ asBytes: true });
|
||||
return options && options.asBytes ? hmacbytes :
|
||||
options && options.asString ? util.bytesToString(hmacbytes) :
|
||||
util.bytesToHex(hmacbytes);
|
||||
|
||||
};
|
||||
|
||||
})();
|
||||
|
||||
module.exports = Crypto;
|
||||
|
|
@ -1,79 +0,0 @@
|
|||
const Crypto = require('./crypto.js');
|
||||
|
||||
(function(){
|
||||
|
||||
// Shortcut
|
||||
var util = Crypto.util;
|
||||
|
||||
// Public API
|
||||
var SHA1 = Crypto.SHA1 = function (message, options) {
|
||||
var digestbytes = util.wordsToBytes(SHA1._sha1(message));
|
||||
return options && options.asBytes ? digestbytes :
|
||||
options && options.asString ? util.bytesToString(digestbytes) :
|
||||
util.bytesToHex(digestbytes);
|
||||
};
|
||||
|
||||
// The core
|
||||
SHA1._sha1 = function (message) {
|
||||
|
||||
var m = util.stringToWords(message),
|
||||
l = message.length * 8,
|
||||
w = [],
|
||||
H0 = 1732584193,
|
||||
H1 = -271733879,
|
||||
H2 = -1732584194,
|
||||
H3 = 271733878,
|
||||
H4 = -1009589776;
|
||||
|
||||
// Padding
|
||||
m[l >> 5] |= 0x80 << (24 - l % 32);
|
||||
m[((l + 64 >>> 9) << 4) + 15] = l;
|
||||
|
||||
for (var i = 0; i < m.length; i += 16) {
|
||||
|
||||
var a = H0,
|
||||
b = H1,
|
||||
c = H2,
|
||||
d = H3,
|
||||
e = H4;
|
||||
|
||||
for (var j = 0; j < 80; j++) {
|
||||
|
||||
if (j < 16) w[j] = m[i + j];
|
||||
else {
|
||||
var n = w[j-3] ^ w[j-8] ^ w[j-14] ^ w[j-16];
|
||||
w[j] = (n << 1) | (n >>> 31);
|
||||
}
|
||||
|
||||
var t = ((H0 << 5) | (H0 >>> 27)) + H4 + (w[j] >>> 0) + (
|
||||
j < 20 ? (H1 & H2 | ~H1 & H3) + 1518500249 :
|
||||
j < 40 ? (H1 ^ H2 ^ H3) + 1859775393 :
|
||||
j < 60 ? (H1 & H2 | H1 & H3 | H2 & H3) - 1894007588 :
|
||||
(H1 ^ H2 ^ H3) - 899497514);
|
||||
|
||||
H4 = H3;
|
||||
H3 = H2;
|
||||
H2 = (H1 << 30) | (H1 >>> 2);
|
||||
H1 = H0;
|
||||
H0 = t;
|
||||
|
||||
}
|
||||
|
||||
H0 += a;
|
||||
H1 += b;
|
||||
H2 += c;
|
||||
H3 += d;
|
||||
H4 += e;
|
||||
|
||||
}
|
||||
|
||||
return [H0, H1, H2, H3, H4];
|
||||
|
||||
};
|
||||
|
||||
// Package private blocksize
|
||||
SHA1._blocksize = 16;
|
||||
|
||||
})();
|
||||
|
||||
module.exports = Crypto;
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
// const env = require('./config.js'); //配置文件,在这文件里配置你的OSS keyId和KeySecret,timeout:87600;
|
||||
|
||||
const base64 = require('./base64.js');//Base64,hmac,sha1,crypto相关算法
|
||||
require('./hmac.js');
|
||||
require('./sha1.js');
|
||||
const Crypto = require('./crypto.js');
|
||||
|
||||
/*
|
||||
*上传文件到阿里云oss
|
||||
*@param - filePath :图片的本地资源路径
|
||||
*@param - dir:表示要传到哪个目录下
|
||||
*@param - successc:成功回调
|
||||
*@param - failc:失败回调
|
||||
*/
|
||||
const uploadFile = function (filePath,env, successc, failc) {
|
||||
console.log(env);
|
||||
if (!filePath || filePath.length < 9) {
|
||||
uni.showModal({
|
||||
title: '图片错误',
|
||||
content: '请重试',
|
||||
showCancel: false,
|
||||
})
|
||||
return;
|
||||
}
|
||||
|
||||
//图片名字 可以自行定义, 这里是采用当前的时间戳 + 150内的随机数来给图片命名的
|
||||
const aliyunFileKey = env.dir + new Date().getTime() + Math.floor(Math.random() * 150) + '.png';
|
||||
|
||||
const aliyunServerURL = env.uploadImageUrl;//OSS地址,需要https
|
||||
const accessid = env.OSSAccessKeyId;
|
||||
const policyBase64 = getPolicyBase64(env);
|
||||
const signature = getSignature(policyBase64,env);//获取签名
|
||||
console.log(env.stsToken);
|
||||
uni.uploadFile({
|
||||
url: aliyunServerURL,//开发者服务器 url
|
||||
filePath: filePath,//要上传文件资源的路径
|
||||
name: 'file',//必须填file
|
||||
formData: {
|
||||
'key': aliyunFileKey,
|
||||
'policy': policyBase64,
|
||||
'OSSAccessKeyId': accessid,
|
||||
'signature': signature,
|
||||
'success_action_status': '200',
|
||||
'x-oss-security-token': env.stsToken,
|
||||
},
|
||||
success: function (res) {
|
||||
console.log(res);
|
||||
if (res.statusCode != 200) {
|
||||
failc(new Error('上传错误:' + JSON.stringify(res)))
|
||||
return;
|
||||
}
|
||||
successc(aliyunServerURL+aliyunFileKey);
|
||||
},
|
||||
fail: function (err) {
|
||||
err.wxaddinfo = aliyunServerURL;
|
||||
failc(err);
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const getPolicyBase64 = function (env) {
|
||||
console.log(env);
|
||||
let date = new Date();
|
||||
date.setHours(date.getHours() + env.timeout);
|
||||
let srcT = date.toISOString();
|
||||
const policyText = {
|
||||
"expiration": srcT, //设置该Policy的失效时间,超过这个失效时间之后,就没有办法通过这个policy上传文件了
|
||||
"conditions": [
|
||||
["content-length-range", 0, 5 * 1024 * 1024] // 设置上传文件的大小限制,5mb
|
||||
]
|
||||
};
|
||||
|
||||
const policyBase64 = base64.encode(JSON.stringify(policyText));
|
||||
console.log(policyBase64);
|
||||
return policyBase64;
|
||||
}
|
||||
|
||||
const getSignature = function (policyBase64,env) {
|
||||
const accesskey = env.AccessKeySecret;
|
||||
|
||||
const bytes = Crypto.HMAC(Crypto.SHA1, policyBase64, accesskey, {
|
||||
asBytes: true
|
||||
});
|
||||
const signature = Crypto.util.bytesToBase64(bytes);
|
||||
console.log(signature);
|
||||
return signature;
|
||||
}
|
||||
|
||||
module.exports = uploadFile;
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
import {
|
||||
isWeixinClient
|
||||
} from './tools'
|
||||
|
||||
export function wxpay(opt) {
|
||||
|
||||
console.log(opt);
|
||||
//#ifdef H5
|
||||
if(isWeixinClient()) {
|
||||
return wechath5.wxPay(opt)
|
||||
}else {
|
||||
location.href=opt.mweb_url
|
||||
}
|
||||
// #endif
|
||||
|
||||
//#ifndef H5
|
||||
return new Promise((resolve, reject) => {
|
||||
// #ifdef MP-WEIXIN
|
||||
const params = {
|
||||
timeStamp: opt.timeStamp,
|
||||
// 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
|
||||
nonceStr: opt.nonceStr,
|
||||
// 支付签名随机串,不长于 32 位
|
||||
package: opt.package,
|
||||
// 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
|
||||
signType: opt.signType,
|
||||
// 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
|
||||
paySign: opt.paySign,
|
||||
}
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
const params = {
|
||||
orderInfo: opt
|
||||
}
|
||||
// #endif
|
||||
uni.requestPayment({
|
||||
provider: 'wxpay',
|
||||
...params,
|
||||
success: res => {
|
||||
console.log(res);
|
||||
resolve('success');
|
||||
},
|
||||
cancel: res => {
|
||||
resolve('fail');
|
||||
console.log(res);
|
||||
},
|
||||
fail: res => {
|
||||
console.log(res);
|
||||
resolve('fail');
|
||||
}
|
||||
});
|
||||
})
|
||||
//#endif
|
||||
}
|
||||
|
||||
export function alipay(opt) {
|
||||
//#ifdef H5
|
||||
const div = document.createElement('div')
|
||||
console.log(opt)
|
||||
/* 此处form就是后台返回接收到的数据 */
|
||||
div.innerHTML = opt
|
||||
document.body.appendChild(div)
|
||||
document.forms[0].submit()
|
||||
// #endif
|
||||
// #ifdef APP-PLUS
|
||||
return new Promise((resolve, reject) => {
|
||||
console.log(opt.body);
|
||||
const params = {
|
||||
orderInfo: opt.body
|
||||
}
|
||||
uni.requestPayment({
|
||||
provider: 'alipay',
|
||||
...params,
|
||||
success: res => {
|
||||
resolve('success');
|
||||
},
|
||||
cancel: res => {
|
||||
console.log(res)
|
||||
resolve('fail');
|
||||
},
|
||||
fail: res => {
|
||||
console.log(res)
|
||||
resolve('fail');
|
||||
}
|
||||
});
|
||||
})
|
||||
// #endif
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
import Request from "luch-request";
|
||||
import {
|
||||
v4 as uuidv4
|
||||
} from 'uuid'
|
||||
import store from '@/store'
|
||||
import throttle from 'uview-ui/libs/function/throttle.js'
|
||||
const http = new Request()
|
||||
|
||||
http.setConfig((config) => {
|
||||
config.baseURL = process.env.VUE_APP_BASE_API
|
||||
config.header = {
|
||||
...config.header,
|
||||
Accept: 'application/json;version=1.4.0'
|
||||
}
|
||||
|
||||
// 是否显示加载状态
|
||||
config.custom = {
|
||||
loading: false,
|
||||
toast: true,
|
||||
silence:false
|
||||
}
|
||||
|
||||
return config
|
||||
})
|
||||
|
||||
|
||||
http.interceptors.request.use(async (config) => {
|
||||
|
||||
config.header = {
|
||||
...config.header,
|
||||
'X-Request-ID': uuidv4(),
|
||||
'client-app': process.env.VUE_APP_LANGUAGE,
|
||||
'client-version': process.env.VUE_APP_VERSION,
|
||||
'client-platform': store.getters.platform,
|
||||
'client-os': store.getters.system
|
||||
}
|
||||
|
||||
if (store.getters.token) {
|
||||
config.header['Authorization'] = 'Bearer ' + store.getters.token
|
||||
}
|
||||
|
||||
if (config.custom.loading) {
|
||||
uni.showLoading({
|
||||
title: '加载中',
|
||||
mask: true
|
||||
});
|
||||
}
|
||||
return config
|
||||
}, (config) => {
|
||||
return Promise.reject(config)
|
||||
})
|
||||
|
||||
|
||||
http.interceptors.response.use(async (response) => {
|
||||
|
||||
if (response.config.custom.loading) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
const {
|
||||
data
|
||||
} = response
|
||||
return data
|
||||
}, (response) => {
|
||||
if (response.config.custom.loading) {
|
||||
uni.hideLoading()
|
||||
}
|
||||
const {
|
||||
statusCode,
|
||||
data
|
||||
} = response
|
||||
|
||||
if ([2000].includes(data?.errcode)) {
|
||||
return Promise.reject(response)
|
||||
}
|
||||
|
||||
if (statusCode == 422) {
|
||||
const {
|
||||
errors,
|
||||
errcode
|
||||
} = data
|
||||
for (const key in errors) {
|
||||
if(response.config.custom.toast){
|
||||
uni.showToast({
|
||||
title: errors[key][0],
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
break
|
||||
}
|
||||
return Promise.reject(data)
|
||||
} else if (statusCode == 401) {
|
||||
store.dispatch('user/resetToken')
|
||||
|
||||
if(!response.config.custom.silence){
|
||||
if(response.config.custom.toast){
|
||||
uni.showToast({
|
||||
title: '登录已过期,请重新登录',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
throttle(() => uni.navigateTo({ url: '/pages/login/index' }), 2000)
|
||||
}
|
||||
return Promise.reject(data)
|
||||
}
|
||||
|
||||
if(response.config.custom.toast){
|
||||
uni.showToast({
|
||||
title: data?.message ?? '系统繁忙,请稍后再试',
|
||||
icon: 'none',
|
||||
duration: 1500
|
||||
})
|
||||
}
|
||||
|
||||
return Promise.reject(data)
|
||||
})
|
||||
|
||||
export {
|
||||
http
|
||||
}
|
||||
|
|
@ -1,80 +0,0 @@
|
|||
|
||||
// 微信
|
||||
//1wxpay_mini微信小程序 2wxpay_jsapi 3ios 4android 6wxpay_h5
|
||||
//支付宝
|
||||
//3alipay_app 4alipay_app
|
||||
|
||||
let client = null
|
||||
// #ifdef MP-WEIXIN
|
||||
client = 1
|
||||
// #endif
|
||||
|
||||
// #ifdef H5
|
||||
client = isWeixinClient() ? 2 : 6
|
||||
// #endif
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
client = 3;
|
||||
uni.getSystemInfo({
|
||||
success: res => {
|
||||
client = res.platform == 'ios' ? 3 : 4;
|
||||
},
|
||||
fail: res => {
|
||||
client = 3
|
||||
}
|
||||
})
|
||||
// #endif
|
||||
|
||||
const payList = [{
|
||||
key: 1,
|
||||
label: '微信小程序',
|
||||
aliValue: '',
|
||||
wxValue: 'wxpay_mini'
|
||||
}, {
|
||||
key: 2,
|
||||
label: '微信浏览器',
|
||||
aliValue: '',
|
||||
value: 'wxpay_jsapi'
|
||||
}, {
|
||||
key: 3,
|
||||
label: 'IOS',
|
||||
aliValue: 'alipay_app',
|
||||
wxValue: 'wxpay_app'
|
||||
}, {
|
||||
key: 4,
|
||||
label: '安卓',
|
||||
aliValue: 'alipay_app',
|
||||
wxValue: 'wxpay_app'
|
||||
}, {
|
||||
key: 6,
|
||||
label: 'H5',
|
||||
aliValue: '',
|
||||
wxValue: 'wxpay_h5'
|
||||
}]
|
||||
|
||||
const payWay = payList.find(e => e.key == client)
|
||||
|
||||
export {
|
||||
payWay,
|
||||
client
|
||||
}
|
||||
|
||||
//判断是否为微信环境
|
||||
export function isWeixinClient() {
|
||||
var ua = navigator.userAgent.toLowerCase();
|
||||
if (ua.match(/MicroMessenger/i) == "micromessenger") {
|
||||
//这是微信环境
|
||||
return true;
|
||||
} else {
|
||||
//这是非微信环境
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//判断是否为安卓环境
|
||||
export function isAndroid() {
|
||||
let u = navigator.userAgent;
|
||||
return u.indexOf('Android') > -1 || u.indexOf('Adr') > -1;
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
import throttle from 'uview-ui/libs/function/throttle.js'
|
||||
export default function uniCopy({content,success,error}) {
|
||||
if(!content) return error('复制的内容不能为空 !')
|
||||
content = typeof content === 'string' ? content : content.toString() // 复制内容,必须字符串,数字需要转换为字符串
|
||||
/**
|
||||
* 小程序端 和 app端的复制逻辑
|
||||
*/
|
||||
//#ifndef H5
|
||||
throttle(()=>{
|
||||
uni.setClipboardData({
|
||||
data: content,
|
||||
success: function() {
|
||||
success("复制成功~")
|
||||
console.log('success');
|
||||
},
|
||||
fail:function(){
|
||||
success("复制失败~")
|
||||
}
|
||||
});
|
||||
}, 2000)
|
||||
//#endif
|
||||
|
||||
/**
|
||||
* H5端的复制逻辑
|
||||
*/
|
||||
// #ifdef H5
|
||||
if (!document.queryCommandSupported('copy')) { //为了兼容有些浏览器 queryCommandSupported 的判断
|
||||
// 不支持
|
||||
error('浏览器不支持')
|
||||
}
|
||||
let textarea = document.createElement("textarea")
|
||||
textarea.value = content
|
||||
textarea.readOnly = "readOnly"
|
||||
document.body.appendChild(textarea)
|
||||
textarea.select() // 选择对象
|
||||
textarea.setSelectionRange(0, content.length) //核心
|
||||
let result = document.execCommand("copy") // 执行浏览器复制命令
|
||||
if(result){
|
||||
success("复制成功~")
|
||||
}else{
|
||||
error("复制失败,请检查h5中调用该方法的方式,是不是用户点击的方式调用的,如果不是请改为用户点击的方式触发该方法,因为h5中安全性,不能js直接调用!")
|
||||
}
|
||||
textarea.remove()
|
||||
// #endif
|
||||
}
|
||||
|
|
@ -1,31 +0,0 @@
|
|||
// #ifdef H5
|
||||
import weixin from "jweixin-module";
|
||||
class Wechath5 {
|
||||
// 支付
|
||||
wxPay(opt) {
|
||||
return new Promise((reslove, reject) => {
|
||||
weixin.ready(() => {
|
||||
weixin.chooseWXPay({
|
||||
timestamp: opt.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
|
||||
nonceStr: opt.nonceStr, // 支付签名随机串,不长于 32 位
|
||||
package: opt.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***)
|
||||
signType: opt.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
|
||||
paySign: opt.paySign, // 支付签名
|
||||
success: (res) => {
|
||||
reslove('success')
|
||||
},
|
||||
cancel: (res) => {
|
||||
reslove('fail')
|
||||
},
|
||||
fail: (res) => {
|
||||
reslove('fail')
|
||||
},
|
||||
});
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default new Wechath5()
|
||||
// #endif
|
||||