6
0
Fork 0

删除文件 src

hui.zhou
黄老板不差钱 2022-05-09 01:48:42 +00:00 committed by Gitee
parent 6f4e0ef457
commit c1e10e4364
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
95 changed files with 0 additions and 14306 deletions

View File

@ -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>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -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;

View File

@ -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'))
})
}

View File

@ -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-modelfalseloading
//
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);
},
// modalv-modelfalsemodal
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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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()

View File

@ -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" : {
//prompttemplate
"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"
}

View File

@ -1,13 +0,0 @@
import { mapGetters } from 'vuex'
export default {
onLoad(option) {
// #ifdef H5
// #endif
},
computed: {
...mapGetters(['isLogin', 'news_num']),
}
}

View File

@ -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)
}
}
}

View File

@ -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"
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 989 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 774 B

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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;
}

View File

@ -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;

View File

@ -1,6 +0,0 @@
## 1.3.72021-04-13
1. 新增`mescroll-swiper-sticky.vue`的示例, 轮播吸顶菜单导航
2. 新增`mescroll-empty.vue`的示例, 单独使用空布局组件
3. 简化tabs在具体项目中的使用,并简化对应的示例
4. mescroll-uni 支持动态禁止滚动的属性 disableScroll (注: mescroll-body不支持)
-by 小瑾同学

View File

@ -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);
}
}

View File

@ -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, // 0loading1loading2,END3,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,fixedmescroll
},
//
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%"windowHeight10%
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); //
}
},
// 使createdmescroll; mountedcssH5
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,inOffsettextInOffset
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;
// initmescroll
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;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使pagescroll,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
if(typeof y === 'string'){
// view (ycss)
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,vuesafearea
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>

View File

@ -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)}
}

View File

@ -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 // inOffset1 outOffset2 showLoading3 endDownScroll4
},
computed: {
// ,propdefault
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>

View File

@ -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, // 0loading1loading2,END3,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(H5tab)
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,fixedmescroll
},
//
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%"windowHeight10%
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); //
}
},
// 使createdmescroll; mountedcssH5
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,inOffsettextInOffset
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;
// initmescroll
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;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使pagescroll,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
if(typeof y === 'string'){
// view (ycss)
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,vuesafearea
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>

View File

@ -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

View File

@ -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), // mescrollid(,)
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: { // fixedmescroll, true
type: Boolean,
default: true
},
height: [String, Number], // mescroll, ,使fixed. (20, "20rpx", "20px", "20%", rpx, windowHeight)
bottombar:{ // TabBar(H5tab)
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,fixedmescroll
},
//
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%"windowHeight10%
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();
}
},
// 使createdmescroll; mountedcssH5
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;
// initmescroll
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;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使scrollview,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
vm.scrollAnim = (t !== 0); // t0,使
if(typeof y === 'string'){
// slotscroll-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,vuesafearea
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>

View File

@ -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);
}
}

View File

@ -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 // inOffset1 outOffset2 showLoading3 endDownScroll4
},
computed: {
// ,propdefault
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>

View File

@ -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);
}
}

View File

@ -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 // 0loading1loading2,END3,END
},
computed: {
// ,propdefault
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>

View File

@ -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, // 0loading1loading2,END3,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(H5tab)
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,fixedmescroll
},
//
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%"windowHeight10%
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); //
}
},
// 使createdmescroll; mountedcssH5
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,inOffsettextInOffset
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;
// initmescroll
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;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使pagescroll,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
if(typeof y === 'string'){
// view (ycss)
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,vuesafearea
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>

View File

@ -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

View File

@ -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), // mescrollid(,)
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: { // fixedmescroll, true
type: Boolean,
default: true
},
height: [String, Number], // mescroll, ,使fixed. (20, "20rpx", "20px", "20%", rpx, windowHeight)
bottombar:{ // TabBar(H5tab)
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,fixedmescroll
},
//
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%"windowHeight10%
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();
}
},
// 使createdmescroll; mountedcssH5
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;
// initmescroll
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;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使scrollview,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
vm.scrollAnim = (t !== 0); // t0,使
if(typeof y === 'string'){
// slotscroll-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,vuesafearea
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>

View File

@ -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>

View File

@ -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);
}
}

View File

@ -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, // inOffset1 outOffset2 showLoading3 endDownScroll4
rate: Number // (inOffset: rate<1; outOffset: rate>=1)
},
computed: {
// ,propdefault
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>

View File

@ -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: {
// ,propdefault
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>

View File

@ -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);
}
}

View File

@ -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 // 0loading1loading2
},
computed: {
// ,propdefault
mOption() {
return this.option || {};
},
//
isUpLoading() {
return this.type === 1;
},
//
isUpNoMore() {
return this.type === 2;
}
}
};
</script>
<style>
@import './mescroll-up.css';
</style>

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);
}
}

View File

@ -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()
}

View File

@ -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), // mescrollid(,)
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,fixedmescroll
},
//
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%"windowHeight10%
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();
}
},
// 使createdmescroll; mountedcssH5
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; //
// initmescroll
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;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使scrollview,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
vm.scrollAnim = (t !== 0); // t0,使
if(typeof y === 'string'){
// slotscroll-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,vuesafearea
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>

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
}

View File

@ -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"
}
}
}
}
}

View File

@ -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~

View File

@ -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;

File diff suppressed because it is too large Load Diff

View File

@ -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
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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
}

File diff suppressed because one or more lines are too long

View File

@ -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
}

View File

@ -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;
}

View File

@ -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
}

View File

@ -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