31 changed files with 9 additions and 3471 deletions
-
BINcomponents/amount/DINPro-Medium.ttf
-
220components/amount/_util/animate.js
-
22components/amount/_util/dom.js
-
18components/amount/_util/env.js
-
76components/amount/_util/formate-value.js
-
2components/amount/_util/index.js
-
139components/amount/index.vue
-
110components/amount/number-capital.js
-
55components/mescroll-uni/components/mescroll-down.css
-
47components/mescroll-uni/components/mescroll-down.vue
-
90components/mescroll-uni/components/mescroll-empty.vue
-
83components/mescroll-uni/components/mescroll-top.vue
-
47components/mescroll-uni/components/mescroll-up.css
-
39components/mescroll-uni/components/mescroll-up.vue
-
14components/mescroll-uni/mescroll-body.css
-
339components/mescroll-uni/mescroll-body.vue
-
BINcomponents/mescroll-uni/mescroll-empty.png
-
74components/mescroll-uni/mescroll-mixins.js
-
33components/mescroll-uni/mescroll-uni-option.js
-
36components/mescroll-uni/mescroll-uni.css
-
788components/mescroll-uni/mescroll-uni.js
-
424components/mescroll-uni/mescroll-uni.vue
-
50components/mescroll-uni/mixins/mescroll-comp.js
-
51components/mescroll-uni/mixins/mescroll-more-item.js
-
56components/mescroll-uni/mixins/mescroll-more.js
-
102components/mescroll-uni/wxs/mixins.js
-
92components/mescroll-uni/wxs/renderjs.js
-
268components/mescroll-uni/wxs/wxs.wxs
-
3pages/order/confirm-order.vue
-
18pages/order/index.vue
-
184pages/order/order-item.vue
@ -1,220 +0,0 @@ |
|||||
import {root} from './env' |
|
||||
|
|
||||
/* istanbul ignore file */ |
|
||||
const Animate = (global => { |
|
||||
/* istanbul ignore next */ |
|
||||
const time = |
|
||||
Date.now || |
|
||||
(() => { |
|
||||
return +new Date() |
|
||||
}) |
|
||||
const desiredFrames = 60 |
|
||||
const millisecondsPerSecond = 1000 |
|
||||
|
|
||||
let running = {} |
|
||||
let counter = 1 |
|
||||
|
|
||||
return { |
|
||||
/** |
|
||||
* A requestAnimationFrame wrapper / polyfill. |
|
||||
* |
|
||||
* @param callback {Function} The callback to be invoked before the next repaint. |
|
||||
* @param root {HTMLElement} The root element for the repaint |
|
||||
*/ |
|
||||
requestAnimationFrame: (() => { |
|
||||
// Check for request animation Frame support
|
|
||||
const requestFrame = |
|
||||
global.requestAnimationFrame || |
|
||||
global.webkitRequestAnimationFrame || |
|
||||
global.mozRequestAnimationFrame || |
|
||||
global.oRequestAnimationFrame |
|
||||
let isNative = !!requestFrame |
|
||||
|
|
||||
if (requestFrame && !/requestAnimationFrame\(\)\s*\{\s*\[native code\]\s*\}/i.test(requestFrame.toString())) { |
|
||||
isNative = false |
|
||||
} |
|
||||
|
|
||||
if (isNative) { |
|
||||
return (callback, root) => { |
|
||||
requestFrame(callback, root) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
const TARGET_FPS = 60 |
|
||||
let requests = {} |
|
||||
let requestCount = 0 |
|
||||
let rafHandle = 1 |
|
||||
let intervalHandle = null |
|
||||
let lastActive = +new Date() |
|
||||
|
|
||||
return callback => { |
|
||||
const callbackHandle = rafHandle++ |
|
||||
|
|
||||
// Store callback
|
|
||||
requests[callbackHandle] = callback |
|
||||
requestCount++ |
|
||||
|
|
||||
// Create timeout at first request
|
|
||||
if (intervalHandle === null) { |
|
||||
intervalHandle = setInterval(() => { |
|
||||
const time = +new Date() |
|
||||
const currentRequests = requests |
|
||||
|
|
||||
// Reset data structure before executing callbacks
|
|
||||
requests = {} |
|
||||
requestCount = 0 |
|
||||
|
|
||||
for (const key in currentRequests) { |
|
||||
if (currentRequests.hasOwnProperty(key)) { |
|
||||
currentRequests[key](time) |
|
||||
lastActive = time |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Disable the timeout when nothing happens for a certain
|
|
||||
// period of time
|
|
||||
if (time - lastActive > 2500) { |
|
||||
clearInterval(intervalHandle) |
|
||||
intervalHandle = null |
|
||||
} |
|
||||
}, 1000 / TARGET_FPS) |
|
||||
} |
|
||||
|
|
||||
return callbackHandle |
|
||||
} |
|
||||
})(), |
|
||||
|
|
||||
/** |
|
||||
* Stops the given animation. |
|
||||
* |
|
||||
* @param id {Integer} Unique animation ID |
|
||||
* @return {Boolean} Whether the animation was stopped (aka, was running before) |
|
||||
*/ |
|
||||
stop(id) { |
|
||||
const cleared = running[id] != null |
|
||||
cleared && (running[id] = null) |
|
||||
return cleared |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Whether the given animation is still running. |
|
||||
* |
|
||||
* @param id {Integer} Unique animation ID |
|
||||
* @return {Boolean} Whether the animation is still running |
|
||||
*/ |
|
||||
isRunning(id) { |
|
||||
return running[id] != null |
|
||||
}, |
|
||||
|
|
||||
/** |
|
||||
* Start the animation. |
|
||||
* |
|
||||
* @param stepCallback {Function} Pointer to function which is executed on every step. |
|
||||
* Signature of the method should be `function(percent, now, virtual) { return continueWithAnimation; }` |
|
||||
* @param verifyCallback {Function} Executed before every animation step. |
|
||||
* Signature of the method should be `function() { return continueWithAnimation; }` |
|
||||
* @param completedCallback {Function} |
|
||||
* Signature of the method should be `function(droppedFrames, finishedAnimation) {}` |
|
||||
* @param duration {Integer} Milliseconds to run the animation |
|
||||
* @param easingMethod {Function} Pointer to easing function |
|
||||
* Signature of the method should be `function(percent) { return modifiedValue; }` |
|
||||
* @param root {Element ? document.body} Render root, when available. Used for internal |
|
||||
* usage of requestAnimationFrame. |
|
||||
* @return {Integer} Identifier of animation. Can be used to stop it any time. |
|
||||
*/ |
|
||||
start(stepCallback, verifyCallback, completedCallback, duration, easingMethod, root) { |
|
||||
const start = time() |
|
||||
let lastFrame = start |
|
||||
let percent = 0 |
|
||||
let dropCounter = 0 |
|
||||
const id = counter++ |
|
||||
|
|
||||
if (!root) { |
|
||||
// root = document.body
|
|
||||
} |
|
||||
|
|
||||
// Compacting running db automatically every few new animations
|
|
||||
if (id % 20 === 0) { |
|
||||
const newRunning = {} |
|
||||
for (const usedId in running) { |
|
||||
newRunning[usedId] = true |
|
||||
} |
|
||||
running = newRunning |
|
||||
} |
|
||||
|
|
||||
// This is the internal step method which is called every few milliseconds
|
|
||||
const step = virtual => { |
|
||||
// Normalize virtual value
|
|
||||
const render = virtual !== true |
|
||||
|
|
||||
// Get current time
|
|
||||
const now = time() |
|
||||
|
|
||||
// Verification is executed before next animation step
|
|
||||
if (!running[id] || (verifyCallback && !verifyCallback(id))) { |
|
||||
running[id] = null |
|
||||
completedCallback && |
|
||||
completedCallback(desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), id, false) |
|
||||
return |
|
||||
} |
|
||||
|
|
||||
// For the current rendering to apply let's update omitted steps in memory.
|
|
||||
// This is important to bring internal state variables up-to-date with progress in time.
|
|
||||
if (render) { |
|
||||
const droppedFrames = Math.round((now - lastFrame) / (millisecondsPerSecond / desiredFrames)) - 1 |
|
||||
for (let j = 0; j < Math.min(droppedFrames, 4); j++) { |
|
||||
step(true) |
|
||||
dropCounter++ |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Compute percent value
|
|
||||
if (duration) { |
|
||||
percent = (now - start) / duration |
|
||||
if (percent > 1) { |
|
||||
percent = 1 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Execute step callback, then...
|
|
||||
let value = easingMethod ? easingMethod(percent) : percent |
|
||||
value = isNaN(value) ? 0 : value |
|
||||
if ((stepCallback(value, now, render) === false || percent === 1) && render) { |
|
||||
running[id] = null |
|
||||
completedCallback && |
|
||||
completedCallback( |
|
||||
desiredFrames - dropCounter / ((now - start) / millisecondsPerSecond), |
|
||||
id, |
|
||||
percent === 1 || duration == null, |
|
||||
) |
|
||||
} else if (render) { |
|
||||
lastFrame = now |
|
||||
this.requestAnimationFrame(step, root) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// Mark as running
|
|
||||
running[id] = true |
|
||||
|
|
||||
// Init first step
|
|
||||
this.requestAnimationFrame(step, root) |
|
||||
|
|
||||
// Return unique animation ID
|
|
||||
return id |
|
||||
}, |
|
||||
} |
|
||||
})(root) |
|
||||
|
|
||||
export const easeOutCubic = pos => { |
|
||||
return Math.pow(pos - 1, 3) + 1 |
|
||||
} |
|
||||
|
|
||||
export const easeInOutCubic = pos => { |
|
||||
if ((pos /= 0.5) < 1) { |
|
||||
return 0.5 * Math.pow(pos, 3) |
|
||||
} |
|
||||
|
|
||||
return 0.5 * (Math.pow(pos - 2, 3) + 2) |
|
||||
} |
|
||||
|
|
||||
export default Animate |
|
||||
@ -1,22 +0,0 @@ |
|||||
import {inBrowser} from './env' |
|
||||
|
|
||||
class Dom { |
|
||||
appendChild() {} |
|
||||
removeChild() {} |
|
||||
querySelector() {} |
|
||||
addEventListener() {} |
|
||||
removeEventListener() {} |
|
||||
} |
|
||||
|
|
||||
const dom = new Dom() |
|
||||
let mdDocument = dom |
|
||||
let mdBody = dom |
|
||||
|
|
||||
mdDocument.body = mdBody |
|
||||
|
|
||||
if (inBrowser) { |
|
||||
// mdDocument = window.document
|
|
||||
// mdBody = document.body
|
|
||||
} |
|
||||
|
|
||||
export {mdDocument, mdBody, dom} |
|
||||
@ -1,18 +0,0 @@ |
|||||
import Vue from 'vue' |
|
||||
|
|
||||
// Development environment
|
|
||||
export const isProd = process.env.NODE_ENV === 'production' |
|
||||
|
|
||||
// Browser environment sniffing
|
|
||||
export const inBrowser = !Vue.prototype.$isServer |
|
||||
export const UA = inBrowser |
|
||||
export const isAndroid = UA |
|
||||
export const isIOS = UA |
|
||||
export const root = typeof window !== 'undefined' ? window : global |
|
||||
|
|
||||
|
|
||||
// export const inBrowser = !Vue.prototype.$isServer || typeof window !== 'undefined'
|
|
||||
// export const UA = inBrowser && window.navigator.userAgent.toLowerCase()
|
|
||||
// export const isAndroid = UA && UA.indexOf('android') > 0
|
|
||||
// export const isIOS = UA && /iphone|ipad|ipod|ios/.test(UA)
|
|
||||
// export const root = typeof window !== 'undefined' ? window : global
|
|
||||
@ -1,76 +0,0 @@ |
|||||
export function formatValueByGapRule(gapRule, value, gap = ' ', range, isAdd = 1) { |
|
||||
const arr = value ? value.split('') : [] |
|
||||
let showValue = '' |
|
||||
const rule = [] |
|
||||
gapRule.split('|').some((n, j) => { |
|
||||
rule[j] = +n + (rule[j - 1] ? +rule[j - 1] : 0) |
|
||||
}) |
|
||||
let j = 0 |
|
||||
arr.some((n, i) => { |
|
||||
// Remove the excess part
|
|
||||
if (i > rule[rule.length - 1] - 1) { |
|
||||
return |
|
||||
} |
|
||||
if (i > 0 && i === rule[j]) { |
|
||||
showValue = showValue + gap + n |
|
||||
j++ |
|
||||
} else { |
|
||||
showValue = showValue + '' + n |
|
||||
} |
|
||||
}) |
|
||||
let adapt = 0 |
|
||||
rule.some((n, j) => { |
|
||||
if (range === +n + 1 + j) { |
|
||||
adapt = 1 * isAdd |
|
||||
} |
|
||||
}) |
|
||||
range = typeof range !== 'undefined' ? (range === 0 ? 0 : range + adapt) : showValue.length |
|
||||
return {value: showValue, range: range} |
|
||||
} |
|
||||
|
|
||||
export function formatValueByGapStep(step, value, gap = ' ', direction = 'right', range, isAdd = 1, oldValue = '') { |
|
||||
if (value.length === 0) { |
|
||||
return {value, range} |
|
||||
} |
|
||||
|
|
||||
const arr = value && value.split('') |
|
||||
let _range = range |
|
||||
let showValue = '' |
|
||||
|
|
||||
if (direction === 'right') { |
|
||||
for (let j = arr.length - 1, k = 0; j >= 0; j--, k++) { |
|
||||
const m = arr[j] |
|
||||
showValue = k > 0 && k % step === 0 ? m + gap + showValue : m + '' + showValue |
|
||||
} |
|
||||
if (isAdd === 1) { |
|
||||
// 在添加的情况下,如果添加前字符串的长度减去新的字符串的长度为2,说明多了一个间隔符,需要调整range
|
|
||||
if (oldValue.length - showValue.length === -2) { |
|
||||
_range = range + 1 |
|
||||
} |
|
||||
} else { |
|
||||
// 在删除情况下,如果删除前字符串的长度减去新的字符串的长度为2,说明少了一个间隔符,需要调整range
|
|
||||
if (oldValue.length - showValue.length === 2) { |
|
||||
_range = range - 1 |
|
||||
} |
|
||||
// 删除到最开始,range 保持 0
|
|
||||
if (_range <= 0) { |
|
||||
_range = 0 |
|
||||
} |
|
||||
} |
|
||||
} else { |
|
||||
arr.some((n, i) => { |
|
||||
showValue = i > 0 && i % step === 0 ? showValue + gap + n : showValue + '' + n |
|
||||
}) |
|
||||
const adapt = range % (step + 1) === 0 ? 1 * isAdd : 0 |
|
||||
_range = typeof range !== 'undefined' ? (range === 0 ? 0 : range + adapt) : showValue.length |
|
||||
} |
|
||||
|
|
||||
return {value: showValue, range: _range} |
|
||||
} |
|
||||
|
|
||||
export function trimValue(value, gap = ' ') { |
|
||||
value = typeof value === 'undefined' ? '' : value |
|
||||
const reg = new RegExp(gap, 'g') |
|
||||
value = value.toString().replace(reg, '') |
|
||||
return value |
|
||||
} |
|
||||
@ -1,2 +0,0 @@ |
|||||
export * from './env' |
|
||||
export * from './dom' |
|
||||
@ -1,139 +0,0 @@ |
|||||
<template> |
|
||||
<text class="md-amount" :class="{numerical: !isCapital}"> |
|
||||
<text v-if="!isCapital">{{ formatValue | doPrecision(legalPrecision, isRoundUp) | doFormat(hasSeparator, separator) }}</text> |
|
||||
<text v-else> {{ formatValue | doPrecision(4, isRoundUp) | doCapital }} </text> |
|
||||
</text> |
|
||||
</template> |
|
||||
|
|
||||
<script>
import {noop, inBrowser} from './_util' |
|
||||
import Animate from './_util/animate' |
|
||||
|
|
||||
import {formatValueByGapStep} from './_util/formate-value' |
|
||||
import numberCapital from './number-capital' |
|
||||
|
|
||||
export default { |
|
||||
name: 'md-amount', |
|
||||
|
|
||||
filters: { |
|
||||
doPrecision(value, precision, isRoundUp) { |
|
||||
const exponentialForm = Number(`${value}e${precision}`) |
|
||||
const rounded = isRoundUp ? Math.round(exponentialForm) : Math.floor(exponentialForm) |
|
||||
return Number(`${rounded}e-${precision}`).toFixed(precision) |
|
||||
}, |
|
||||
doFormat(value, hasSeparator, separator) { |
|
||||
if (!hasSeparator) { |
|
||||
return value |
|
||||
} |
|
||||
|
|
||||
const numberParts = value.split('.') |
|
||||
const integerValue = numberParts[0] |
|
||||
const decimalValue = numberParts[1] || '' |
|
||||
const formateValue = formatValueByGapStep(3, integerValue, separator, 'right', 0, 1) |
|
||||
return decimalValue ? `${formateValue.value}.${decimalValue}` : `${formateValue.value}` |
|
||||
}, |
|
||||
doCapital(value) { |
|
||||
return numberCapital(value) |
|
||||
}, |
|
||||
}, |
|
||||
|
|
||||
props: { |
|
||||
value: { |
|
||||
type: Number, |
|
||||
default: 0, |
|
||||
}, |
|
||||
precision: { |
|
||||
type: Number, |
|
||||
default: 2, |
|
||||
}, |
|
||||
isRoundUp: { |
|
||||
type: Boolean, |
|
||||
default: true, |
|
||||
}, |
|
||||
hasSeparator: { |
|
||||
type: Boolean, |
|
||||
default: false, |
|
||||
}, |
|
||||
separator: { |
|
||||
type: String, |
|
||||
default: ',', |
|
||||
}, |
|
||||
isAnimated: { |
|
||||
type: Boolean, |
|
||||
default: false, |
|
||||
}, |
|
||||
transition: { |
|
||||
type: Boolean, |
|
||||
default: false, |
|
||||
}, |
|
||||
isCapital: { |
|
||||
type: Boolean, |
|
||||
default: false, |
|
||||
}, |
|
||||
duration: { |
|
||||
type: Number, |
|
||||
default: 1000, |
|
||||
}, |
|
||||
}, |
|
||||
|
|
||||
data() { |
|
||||
return { |
|
||||
formatValue: 0, |
|
||||
isMounted: false, |
|
||||
} |
|
||||
}, |
|
||||
|
|
||||
watch: { |
|
||||
value: { |
|
||||
handler(val, oldVal) { |
|
||||
/* istanbul ignore if */ |
|
||||
if (!inBrowser && !this.isMounted) { |
|
||||
this.formatValue = val |
|
||||
return |
|
||||
} |
|
||||
if (this.isAnimated || this.transition) { |
|
||||
this.$_doAnimateDisplay(oldVal, val) |
|
||||
} else { |
|
||||
this.formatValue = val |
|
||||
} |
|
||||
}, |
|
||||
immediate: true, |
|
||||
}, |
|
||||
}, |
|
||||
|
|
||||
computed: { |
|
||||
legalPrecision() { |
|
||||
return this.precision > 0 ? this.precision : 0 |
|
||||
}, |
|
||||
}, |
|
||||
|
|
||||
mounted() { |
|
||||
this.isMounted = true |
|
||||
}, |
|
||||
|
|
||||
methods: { |
|
||||
// MARK: private methods |
|
||||
$_doAnimateDisplay(fromValue = 0, toValue = 0) { |
|
||||
/* istanbul ignore next */ |
|
||||
const step = percent => { |
|
||||
if (percent === 1) { |
|
||||
this.formatValue = toValue |
|
||||
return |
|
||||
} |
|
||||
this.formatValue = fromValue + (toValue - fromValue) * percent |
|
||||
} |
|
||||
|
|
||||
/* istanbul ignore next */ |
|
||||
const verify = id => id |
|
||||
Animate.start(step, verify, noop, this.duration) |
|
||||
}, |
|
||||
}, |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss"> |
|
||||
.md-amount{ |
|
||||
&.numerical{ |
|
||||
font-family: font-family-number |
|
||||
} |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,110 +0,0 @@ |
|||||
const cnNums = ['\u96f6', '\u58f9', '\u8d30', '\u53c1', '\u8086', '\u4f0d', '\u9646', '\u67d2', '\u634c', '\u7396'] |
|
||||
|
|
||||
// 拾 \u62fe 佰 \u4f70 仟 \u4edf
|
|
||||
const cnIntRadice = ['', '\u62fe', '\u4f70', '\u4edf'] |
|
||||
|
|
||||
// 万 \u4e07 亿 \u4ebf 兆 \u5146
|
|
||||
const cnIntUnits = ['', '\u4e07', '\u4ebf', '兆'] |
|
||||
|
|
||||
// 角 \u89d2 分 \u5206 毫 \u6beb 厘 \u5398
|
|
||||
const cnDecUnits = ['\u89d2', '\u5206', '\u6beb', '\u5398'] |
|
||||
const cnInteger = '\u6574' // 整 \u6574
|
|
||||
const cnIntLast = '\u5143' // 元 \u5143
|
|
||||
|
|
||||
const cnNegative = '\u8d1f' // 负
|
|
||||
|
|
||||
// Maximum number
|
|
||||
const maxNum = 999999999999999.9999 |
|
||||
|
|
||||
export default function(number) { |
|
||||
let negative |
|
||||
// Integral part
|
|
||||
let integerNum |
|
||||
// Decimal part
|
|
||||
let decimalNum |
|
||||
// Capital number
|
|
||||
let capitalStr = '' |
|
||||
|
|
||||
let parts |
|
||||
|
|
||||
/* istanbul ignore if */ |
|
||||
if (number === '') { |
|
||||
return '' |
|
||||
} |
|
||||
|
|
||||
number = parseFloat(number) |
|
||||
|
|
||||
if (number < 0) { |
|
||||
negative = true |
|
||||
number = Math.abs(number) |
|
||||
} |
|
||||
|
|
||||
/* istanbul ignore if */ |
|
||||
if (number >= maxNum) { |
|
||||
return '' |
|
||||
} |
|
||||
|
|
||||
/* istanbul ignore if */ |
|
||||
if (number === 0) { |
|
||||
capitalStr = cnNums[0] + cnIntLast + cnInteger |
|
||||
return capitalStr |
|
||||
} |
|
||||
|
|
||||
// Convert to String
|
|
||||
number += '' |
|
||||
|
|
||||
if (number.indexOf('.') === -1) { |
|
||||
integerNum = number |
|
||||
decimalNum = '' |
|
||||
} else { |
|
||||
parts = number.split('.') |
|
||||
integerNum = parts[0] |
|
||||
decimalNum = parts[1].substr(0, 4) |
|
||||
} |
|
||||
|
|
||||
// Convert integer part
|
|
||||
if (parseInt(integerNum, 10) > 0) { |
|
||||
let zeroCount = 0 |
|
||||
for (let i = 0, IntLen = integerNum.length; i < IntLen; i++) { |
|
||||
const n = integerNum.substr(i, 1) |
|
||||
const p = IntLen - i - 1 |
|
||||
const q = p / 4 |
|
||||
const m = p % 4 |
|
||||
if (n === '0') { |
|
||||
zeroCount++ |
|
||||
} else { |
|
||||
if (zeroCount > 0) { |
|
||||
capitalStr += cnNums[0] |
|
||||
} |
|
||||
zeroCount = 0 |
|
||||
capitalStr += cnNums[parseInt(n)] + cnIntRadice[m] |
|
||||
} |
|
||||
if (m === 0 && zeroCount < 4) { |
|
||||
capitalStr += cnIntUnits[q] |
|
||||
} |
|
||||
} |
|
||||
capitalStr += cnIntLast |
|
||||
} |
|
||||
|
|
||||
// Convert decimal part
|
|
||||
if (decimalNum !== '') { |
|
||||
for (let i = 0, decLen = decimalNum.length; i < decLen; i++) { |
|
||||
const n = decimalNum.substr(i, 1) |
|
||||
if (n !== '0') { |
|
||||
capitalStr += cnNums[Number(n)] + cnDecUnits[i] |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/* istanbul ignore if */ |
|
||||
if (capitalStr === '') { |
|
||||
capitalStr += cnNums[0] + cnIntLast + cnInteger |
|
||||
} else if (decimalNum === '') { |
|
||||
capitalStr += cnInteger |
|
||||
} |
|
||||
|
|
||||
if (negative) { |
|
||||
capitalStr = `${cnNegative}${capitalStr}` |
|
||||
} |
|
||||
return capitalStr |
|
||||
} |
|
||||
@ -1,55 +0,0 @@ |
|||||
/* 下拉刷新区域 */ |
|
||||
.mescroll-downwarp { |
|
||||
position: absolute; |
|
||||
top: -100%; |
|
||||
left: 0; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
/* 下拉刷新--内容区,定位于区域底部 */ |
|
||||
.mescroll-downwarp .downwarp-content { |
|
||||
position: absolute; |
|
||||
left: 0; |
|
||||
bottom: 0; |
|
||||
width: 100%; |
|
||||
min-height: 60rpx; |
|
||||
padding: 20rpx 0; |
|
||||
text-align: center; |
|
||||
} |
|
||||
|
|
||||
/* 下拉刷新--提示文本 */ |
|
||||
.mescroll-downwarp .downwarp-tip { |
|
||||
display: inline-block; |
|
||||
font-size: 28rpx; |
|
||||
vertical-align: middle; |
|
||||
margin-left: 16rpx; |
|
||||
/* color: gray; 已在style设置color,此处删去*/ |
|
||||
} |
|
||||
|
|
||||
/* 下拉刷新--旋转进度条 */ |
|
||||
.mescroll-downwarp .downwarp-progress { |
|
||||
display: inline-block; |
|
||||
width: 32rpx; |
|
||||
height: 32rpx; |
|
||||
border-radius: 50%; |
|
||||
border: 2rpx solid gray; |
|
||||
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/ |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
|
|
||||
/* 旋转动画 */ |
|
||||
.mescroll-downwarp .mescroll-rotate { |
|
||||
animation: mescrollDownRotate 0.6s linear infinite; |
|
||||
} |
|
||||
|
|
||||
@keyframes mescrollDownRotate { |
|
||||
0% { |
|
||||
transform: rotate(0deg); |
|
||||
} |
|
||||
|
|
||||
100% { |
|
||||
transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
@ -1,47 +0,0 @@ |
|||||
<!-- 下拉刷新区域 --> |
|
||||
<template> |
|
||||
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}"> |
|
||||
<view class="downwarp-content"> |
|
||||
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mOption.textColor, 'transform':downRotate}"></view> |
|
||||
<view class="downwarp-tip">{{downText}}</view> |
|
||||
</view> |
|
||||
</view> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
export default { |
|
||||
props: { |
|
||||
option: Object , // down的配置项 |
|
||||
type: Number, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4) |
|
||||
rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|
||||
}, |
|
||||
computed: { |
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|
||||
mOption(){ |
|
||||
return this.option || {} |
|
||||
}, |
|
||||
// 是否在加载中 |
|
||||
isDownLoading(){ |
|
||||
return this.type === 3 |
|
||||
}, |
|
||||
// 旋转的角度 |
|
||||
downRotate(){ |
|
||||
return 'rotate(' + 360 * this.rate + 'deg)' |
|
||||
}, |
|
||||
// 文本提示 |
|
||||
downText(){ |
|
||||
switch (this.type){ |
|
||||
case 1: return this.mOption.textInOffset; |
|
||||
case 2: return this.mOption.textOutOffset; |
|
||||
case 3: return this.mOption.textLoading; |
|
||||
case 4: return this.mOption.textLoading; |
|
||||
default: return this.mOption.textInOffset; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
</script> |
|
||||
|
|
||||
<style> |
|
||||
@import "./mescroll-down.css"; |
|
||||
</style> |
|
||||
@ -1,90 +0,0 @@ |
|||||
<!--空布局 |
|
||||
|
|
||||
可作为独立的组件, 不使用mescroll的页面也能单独引入, 以便APP全局统一管理: |
|
||||
import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue'; |
|
||||
<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="option.btnText" class="empty-btn" @click="emptyClick">{{ option.btnText }}</view> |
|
||||
</view> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
// 引入全局配置 |
|
||||
import GlobalOption from './../mescroll-uni-option.js'; |
|
||||
export default { |
|
||||
props: { |
|
||||
// empty的配置项: 默认为GlobalOption.up.empty |
|
||||
option: { |
|
||||
type: Object, |
|
||||
default() { |
|
||||
return {}; |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
// 使用computed获取配置,用于支持option的动态配置 |
|
||||
computed: { |
|
||||
// 图标 |
|
||||
icon() { |
|
||||
return this.option.icon == null ? GlobalOption.up.empty.icon : this.option.icon; // 此处不使用短路求值, 用于支持传空串不显示图标 |
|
||||
}, |
|
||||
// 文本提示 |
|
||||
tip() { |
|
||||
return this.option.tip == null ? GlobalOption.up.empty.tip : this.option.tip; // 此处不使用短路求值, 用于支持传空串不显示文本提示 |
|
||||
} |
|
||||
}, |
|
||||
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: 360rpx; |
|
||||
height: 360rpx; |
|
||||
} |
|
||||
|
|
||||
.mescroll-empty .empty-tip { |
|
||||
margin-top: 20rpx; |
|
||||
font-size: 24rpx; |
|
||||
color: gray; |
|
||||
} |
|
||||
|
|
||||
.mescroll-empty .empty-btn { |
|
||||
display: inline-block; |
|
||||
margin-top: 40rpx; |
|
||||
min-width: 200rpx; |
|
||||
padding: 18rpx; |
|
||||
font-size: 28rpx; |
|
||||
border: 1rpx solid #e04b28; |
|
||||
border-radius: 60rpx; |
|
||||
color: #e04b28; |
|
||||
} |
|
||||
|
|
||||
.mescroll-empty .empty-btn:active { |
|
||||
opacity: 0.75; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,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" |
|
||||
@click="toTopClick" |
|
||||
/> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
export default { |
|
||||
props: { |
|
||||
// up.toTop的配置项 |
|
||||
option: Object, |
|
||||
// 是否显示 |
|
||||
value: false |
|
||||
}, |
|
||||
computed: { |
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|
||||
mOption(){ |
|
||||
return this.option || {} |
|
||||
}, |
|
||||
// 优先显示左边 |
|
||||
left(){ |
|
||||
return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto'; |
|
||||
}, |
|
||||
// 右边距离 (优先显示左边) |
|
||||
right() { |
|
||||
return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right); |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
addUnit(num){ |
|
||||
if(!num) return 0; |
|
||||
if(typeof num === 'number') return num + 'rpx'; |
|
||||
return num |
|
||||
}, |
|
||||
toTopClick() { |
|
||||
this.$emit('input', false); // 使v-model生效 |
|
||||
this.$emit('click'); // 派发点击事件 |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
</script> |
|
||||
|
|
||||
<style> |
|
||||
/* 回到顶部的按钮 */ |
|
||||
.mescroll-totop { |
|
||||
z-index: 9990; |
|
||||
position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */ |
|
||||
right: 20rpx; |
|
||||
bottom: 120rpx; |
|
||||
width: 72rpx; |
|
||||
height: auto; |
|
||||
border-radius: 50%; |
|
||||
opacity: 0; |
|
||||
transition: opacity 0.5s; /* 过渡 */ |
|
||||
margin-bottom: var(--window-bottom); /* css变量 */ |
|
||||
} |
|
||||
|
|
||||
/* 适配 iPhoneX */ |
|
||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { |
|
||||
.mescroll-totop-safearea { |
|
||||
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */ |
|
||||
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom)); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/* 显示 -- 淡入 */ |
|
||||
.mescroll-totop-in { |
|
||||
opacity: 1; |
|
||||
} |
|
||||
|
|
||||
/* 隐藏 -- 淡出且不接收事件*/ |
|
||||
.mescroll-totop-out { |
|
||||
opacity: 0; |
|
||||
pointer-events: none; |
|
||||
} |
|
||||
</style> |
|
||||
@ -1,47 +0,0 @@ |
|||||
/* 上拉加载区域 */ |
|
||||
.mescroll-upwarp { |
|
||||
box-sizing: border-box; |
|
||||
min-height: 110rpx; |
|
||||
padding: 0 0 30rpx 0; |
|
||||
text-align: center; |
|
||||
clear: both; |
|
||||
} |
|
||||
|
|
||||
/*提示文本 */ |
|
||||
.mescroll-upwarp .upwarp-tip, |
|
||||
.mescroll-upwarp .upwarp-nodata { |
|
||||
display: inline-block; |
|
||||
font-size: 28rpx; |
|
||||
vertical-align: middle; |
|
||||
/* color: gray; 已在style设置color,此处删去*/ |
|
||||
} |
|
||||
|
|
||||
.mescroll-upwarp .upwarp-tip { |
|
||||
margin-left: 16rpx; |
|
||||
} |
|
||||
|
|
||||
/*旋转进度条 */ |
|
||||
.mescroll-upwarp .upwarp-progress { |
|
||||
display: inline-block; |
|
||||
width: 32rpx; |
|
||||
height: 32rpx; |
|
||||
border-radius: 50%; |
|
||||
border: 2rpx solid gray; |
|
||||
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/ |
|
||||
vertical-align: middle; |
|
||||
} |
|
||||
|
|
||||
/* 旋转动画 */ |
|
||||
.mescroll-upwarp .mescroll-rotate { |
|
||||
animation: mescrollUpRotate 0.6s linear infinite; |
|
||||
} |
|
||||
|
|
||||
@keyframes mescrollUpRotate { |
|
||||
0% { |
|
||||
transform: rotate(0deg); |
|
||||
} |
|
||||
|
|
||||
100% { |
|
||||
transform: rotate(360deg); |
|
||||
} |
|
||||
} |
|
||||
@ -1,39 +0,0 @@ |
|||||
<!-- 上拉加载区域 --> |
|
||||
<template> |
|
||||
<view class="mescroll-upwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}"> |
|
||||
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) --> |
|
||||
<view v-show="isUpLoading"> |
|
||||
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mOption.textColor}"></view> |
|
||||
<view class="upwarp-tip">{{ mOption.textLoading }}</view> |
|
||||
</view> |
|
||||
<!-- 无数据 --> |
|
||||
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view> |
|
||||
</view> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
export default { |
|
||||
props: { |
|
||||
option: Object, // up的配置项 |
|
||||
type: Number // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了) |
|
||||
}, |
|
||||
computed: { |
|
||||
// 支付宝小程序需写成计算属性,prop定义default仍报错 |
|
||||
mOption() { |
|
||||
return this.option || {}; |
|
||||
}, |
|
||||
// 加载中 |
|
||||
isUpLoading() { |
|
||||
return this.type === 1; |
|
||||
}, |
|
||||
// 没有更多了 |
|
||||
isUpNoMore() { |
|
||||
return this.type === 2; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
</script> |
|
||||
|
|
||||
<style> |
|
||||
@import './mescroll-up.css'; |
|
||||
</style> |
|
||||
@ -1,14 +0,0 @@ |
|||||
.mescroll-body { |
|
||||
position: relative; /* 下拉刷新区域相对自身定位 */ |
|
||||
height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/ |
|
||||
overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */ |
|
||||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ |
|
||||
} |
|
||||
|
|
||||
/* 适配 iPhoneX */ |
|
||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { |
|
||||
.mescroll-safearea { |
|
||||
padding-bottom: constant(safe-area-inset-bottom); |
|
||||
padding-bottom: env(safe-area-inset-bottom); |
|
||||
} |
|
||||
} |
|
||||
@ -1,339 +0,0 @@ |
|||||
<template> |
|
||||
<view class="mescroll-body mescroll-render-touch" :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"> |
|
||||
<image style="width: 80rpx;" src="../../static/images/logo.gif" mode="widthFix"></image> |
|
||||
<view class="margin-top-sm"> |
|
||||
<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> |
|
||||
</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 flex justify-center" :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" @click="toTopClick"></mescroll-top> |
|
||||
|
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS || H5 --> |
|
||||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|
||||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|
||||
<!-- #endif --> |
|
||||
</view> |
|
||||
</template> |
|
||||
|
|
||||
<!-- 微信小程序, app, h5使用wxs --> |
|
||||
<!-- #ifdef MP-WEIXIN || 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 MescrollEmpty from './components/mescroll-empty.vue'; |
|
||||
// 引入回到顶部组件 |
|
||||
import MescrollTop from './components/mescroll-top.vue'; |
|
||||
// 引入兼容wxs(含renderjs)写法的mixins |
|
||||
import WxsMixin from './wxs/mixins.js'; |
|
||||
|
|
||||
export default { |
|
||||
mixins: [WxsMixin], |
|
||||
components: { |
|
||||
MescrollEmpty, |
|
||||
MescrollTop |
|
||||
}, |
|
||||
data() { |
|
||||
return { |
|
||||
mescroll: { |
|
||||
optDown: {}, |
|
||||
optUp: {} |
|
||||
}, // mescroll实例 |
|
||||
downHight: 0, //下拉刷新: 容器高度 |
|
||||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1) |
|
||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|
||||
upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示) |
|
||||
isShowEmpty: false, // 是否显示空布局 |
|
||||
isShowToTop: false, // 是否显示回到顶部按钮 |
|
||||
windowHeight: 0, // 可使用窗口的高度 |
|
||||
windowBottom: 0, // 可使用窗口的底部位置 |
|
||||
statusBarHeight: 0 // 状态栏高度 |
|
||||
}; |
|
||||
}, |
|
||||
props: { |
|
||||
down: Object, // 下拉刷新的参数配置 |
|
||||
up: Object, // 上拉加载的参数配置 |
|
||||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|
||||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|
||||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|
||||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|
||||
height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|
||||
bottombar: { // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|
||||
type: Boolean, |
|
||||
default: true |
|
||||
} |
|
||||
}, |
|
||||
computed: { |
|
||||
// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉 |
|
||||
minHeight() { |
|
||||
return (this.toPx(this.height || '100%') - 45) + 'px' |
|
||||
}, |
|
||||
// 下拉布局往下偏移的距离 (px) |
|
||||
numTop() { |
|
||||
return this.toPx(this.top) |
|
||||
}, |
|
||||
padTop() { |
|
||||
return this.numTop + 'px'; |
|
||||
}, |
|
||||
// 上拉布局往上偏移 (px) |
|
||||
numBottom() { |
|
||||
return this.toPx(this.bottom); |
|
||||
}, |
|
||||
padBottom() { |
|
||||
return this.numBottom + 'px'; |
|
||||
}, |
|
||||
// 是否为重置下拉的状态 |
|
||||
isDownReset() { |
|
||||
return this.downLoadType === 3 || this.downLoadType === 4; |
|
||||
}, |
|
||||
// 过渡 |
|
||||
transition() { |
|
||||
return this.isDownReset ? 'transform 300ms' : ''; |
|
||||
}, |
|
||||
translateY() { |
|
||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|
||||
}, |
|
||||
// 是否在加载中 |
|
||||
isDownLoading() { |
|
||||
return this.downLoadType === 3 |
|
||||
}, |
|
||||
// 旋转的角度 |
|
||||
downRotate() { |
|
||||
return 'rotate(' + 360 * this.downRate + 'deg)' |
|
||||
}, |
|
||||
// 文本提示 |
|
||||
downText() { |
|
||||
if (!this.mescroll) return ""; // 避免头条小程序初始化时报错 |
|
||||
switch (this.downLoadType) { |
|
||||
case 1: |
|
||||
return this.mescroll.optDown.textInOffset; |
|
||||
case 2: |
|
||||
return this.mescroll.optDown.textOutOffset; |
|
||||
case 3: |
|
||||
return this.mescroll.optDown.textLoading; |
|
||||
case 4: |
|
||||
return this.mescroll.optDown.textLoading; |
|
||||
default: |
|
||||
return this.mescroll.optDown.textInOffset; |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
//number,rpx,upx,px,% --> px的数值 |
|
||||
toPx(num) { |
|
||||
if (typeof num === 'string') { |
|
||||
if (num.indexOf('px') !== -1) { |
|
||||
if (num.indexOf('rpx') !== -1) { |
|
||||
// "10rpx" |
|
||||
num = num.replace('rpx', ''); |
|
||||
} else if (num.indexOf('upx') !== -1) { |
|
||||
// "10upx" |
|
||||
num = num.replace('upx', ''); |
|
||||
} else { |
|
||||
// "10px" |
|
||||
return Number(num.replace('px', '')); |
|
||||
} |
|
||||
} else if (num.indexOf('%') !== -1) { |
|
||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|
||||
let rate = Number(num.replace('%', '')) / 100; |
|
||||
return this.windowHeight * rate; |
|
||||
} |
|
||||
} |
|
||||
return num ? uni.upx2px(Number(num)) : 0; |
|
||||
}, |
|
||||
// 点击空布局的按钮回调 |
|
||||
emptyClick() { |
|
||||
this.$emit('emptyclick', this.mescroll); |
|
||||
}, |
|
||||
// 点击回到顶部的按钮回调 |
|
||||
toTopClick() { |
|
||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|
||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|
||||
} |
|
||||
}, |
|
||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|
||||
created() { |
|
||||
let vm = this; |
|
||||
|
|
||||
let diyOption = { |
|
||||
// 下拉刷新的配置 |
|
||||
down: { |
|
||||
inOffset() { |
|
||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|
||||
}, |
|
||||
outOffset() { |
|
||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|
||||
}, |
|
||||
onMoving(mescroll, rate, downHight) { |
|
||||
// 下拉过程中的回调,滑动过程一直在执行; |
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|
||||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|
||||
}, |
|
||||
showLoading(mescroll, downHight) { |
|
||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|
||||
}, |
|
||||
endDownScroll() { |
|
||||
vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删) |
|
||||
vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|
||||
if (vm.downResetTimer) { |
|
||||
clearTimeout(vm.downResetTimer); |
|
||||
vm.downResetTimer = null |
|
||||
} // 移除重置倒计时 |
|
||||
vm.downResetTimer = setTimeout(() => { // 过渡动画执行完毕后,需重置为0的状态,避免下次inOffset不及时显示textInOffset |
|
||||
if (vm.downLoadType === 4) vm.downLoadType = 0 |
|
||||
}, 300) |
|
||||
}, |
|
||||
// 派发下拉刷新的回调 |
|
||||
callback: function(mescroll) { |
|
||||
vm.$emit('down', mescroll); |
|
||||
} |
|
||||
}, |
|
||||
// 上拉加载的配置 |
|
||||
up: { |
|
||||
use: false, //是否启用上拉加载 |
|
||||
// 显示加载中的回调 |
|
||||
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.extend(diyOption, GlobalOption); // 混入全局的配置 |
|
||||
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为滚动区域 |
|
||||
// init回调mescroll对象 |
|
||||
vm.$emit('init', vm.mescroll); |
|
||||
|
|
||||
// 设置高度 |
|
||||
const sys = uni.getSystemInfoSync(); |
|
||||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|
||||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|
||||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|
||||
// 使down的bottomOffset生效 |
|
||||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|
||||
|
|
||||
// 因为使用的是page的scroll,这里需自定义scrollTo |
|
||||
vm.mescroll.resetScrollTo((y, t) => { |
|
||||
if (typeof y === 'string') { |
|
||||
// 滚动到指定view (y必须为元素的id,不带#) |
|
||||
setTimeout(() => { // 延时确保view已渲染; 不使用$nextTick |
|
||||
uni.createSelectorQuery().select('#' + y).boundingClientRect(function(rect) { |
|
||||
let top = rect.top |
|
||||
top += vm.mescroll.getScrollTop() |
|
||||
uni.pageScrollTo({ |
|
||||
scrollTop: top, |
|
||||
duration: t |
|
||||
}) |
|
||||
}).exec() |
|
||||
}, 30) |
|
||||
} else { |
|
||||
// 滚动到指定位置 (y必须为数字) |
|
||||
uni.pageScrollTo({ |
|
||||
scrollTop: y, |
|
||||
duration: t |
|
||||
}) |
|
||||
} |
|
||||
}); |
|
||||
|
|
||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|
||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|
||||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|
||||
} |
|
||||
} |
|
||||
}; |
|
||||
</script> |
|
||||
|
|
||||
<style> |
|
||||
@import "./mescroll-body.css"; |
|
||||
@import "./components/mescroll-down.css"; |
|
||||
@import './components/mescroll-up.css'; |
|
||||
</style> |
|
||||
|
Before Width: 330 | Height: 330 | Size: 30 KiB |
@ -1,74 +0,0 @@ |
|||||
// mescroll-body 和 mescroll-uni 通用
|
|
||||
|
|
||||
// import MescrollUni from "./mescroll-uni.vue";
|
|
||||
// import MescrollBody from "./mescroll-body.vue";
|
|
||||
|
|
||||
const MescrollMixin = { |
|
||||
// components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册
|
|
||||
// MescrollUni,
|
|
||||
// MescrollBody
|
|
||||
// },
|
|
||||
data() { |
|
||||
return { |
|
||||
mescroll: null, //mescroll实例对象
|
|
||||
// 上拉
|
|
||||
upOpt: { |
|
||||
|
|
||||
}, |
|
||||
// 下拉
|
|
||||
downOpt: { |
|
||||
auto: false, |
|
||||
inOffsetRate: 0.9 |
|
||||
}, |
|
||||
} |
|
||||
}, |
|
||||
// 注册系统自带的下拉刷新 (配置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对象 (兼容字节跳动小程序: http://www.mescroll.com/qa.html?v=20200107#q26)
|
|
||||
mescrollInitByRef() { |
|
||||
if(!this.mescroll || !this.mescroll.resetUpScroll){ |
|
||||
let mescrollRef = this.$refs.mescrollRef; |
|
||||
if(mescrollRef) this.mescroll = mescrollRef.mescroll |
|
||||
} |
|
||||
}, |
|
||||
// 下拉刷新的回调 (mixin默认resetUpScroll)
|
|
||||
downCallback() { |
|
||||
if(this.mescroll.optUp.use){ |
|
||||
this.mescroll.resetUpScroll() |
|
||||
}else{ |
|
||||
setTimeout(()=>{ |
|
||||
this.mescroll.endSuccess(); |
|
||||
}, 500) |
|
||||
} |
|
||||
}, |
|
||||
// 上拉加载的回调
|
|
||||
upCallback() { |
|
||||
// mixin默认延时500自动结束加载
|
|
||||
setTimeout(()=>{ |
|
||||
this.mescroll.endErr(); |
|
||||
}, 500) |
|
||||
} |
|
||||
}, |
|
||||
mounted() { |
|
||||
this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
|
|
||||
} |
|
||||
|
|
||||
} |
|
||||
|
|
||||
export default MescrollMixin; |
|
||||
@ -1,33 +0,0 @@ |
|||||
// 全局配置
|
|
||||
// mescroll-body 和 mescroll-uni 通用
|
|
||||
const GlobalOption = { |
|
||||
down: { |
|
||||
// 其他down的配置参数也可以写,这里只展示了常用的配置:
|
|
||||
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
|
|
||||
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
|
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
|
||||
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
|
|
||||
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
|
|
||||
}, |
|
||||
up: { |
|
||||
// 其他up的配置参数也可以写,这里只展示了常用的配置:
|
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
|
||||
textNoMore: '没有更多数据啦~', // 没有更多数据的提示文本
|
|
||||
offset: 80, // 距底部多远时,触发upCallback
|
|
||||
toTop: { |
|
||||
// 回到顶部按钮,需配置src才显示
|
|
||||
src: "http://www.mescroll.com/img/mescroll-totop.png?v=1", // 图片路径 (建议放入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: require('./mescroll-empty.png'), // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
|
|
||||
tip: '暂无相关数据' // 提示
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default GlobalOption |
|
||||
@ -1,36 +0,0 @@ |
|||||
.mescroll-uni-warp{ |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
.mescroll-uni-content{ |
|
||||
height: 100%; |
|
||||
} |
|
||||
|
|
||||
.mescroll-uni { |
|
||||
position: relative; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
min-height: 200rpx; |
|
||||
overflow-y: auto; |
|
||||
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */ |
|
||||
} |
|
||||
|
|
||||
/* 定位的方式固定高度 */ |
|
||||
.mescroll-uni-fixed{ |
|
||||
z-index: 1; |
|
||||
position: fixed; |
|
||||
top: 0; |
|
||||
left: 0; |
|
||||
right: 0; |
|
||||
bottom: 0; |
|
||||
width: auto; /* 使right生效 */ |
|
||||
height: auto; /* 使bottom生效 */ |
|
||||
} |
|
||||
|
|
||||
/* 适配 iPhoneX */ |
|
||||
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) { |
|
||||
.mescroll-safearea { |
|
||||
padding-bottom: constant(safe-area-inset-bottom); |
|
||||
padding-bottom: env(safe-area-inset-bottom); |
|
||||
} |
|
||||
} |
|
||||
@ -1,788 +0,0 @@ |
|||||
/* mescroll |
|
||||
* version 1.3.1 |
|
||||
* 2020-07-27 wenju |
|
||||
* http://www.mescroll.com
|
|
||||
*/ |
|
||||
|
|
||||
export default function MeScroll(options, isScrollBody) { |
|
||||
let me = this; |
|
||||
me.version = '1.3.1'; // 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: '加载中 ...', // 加载中的提示文本
|
|
||||
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: 80, // 距底部多远时,触发upCallback
|
|
||||
textLoading: '加载中 ...', // 加载中的提示文本
|
|
||||
textNoMore: '没有更多数据啦~', // 没有更多数据的提示文本
|
|
||||
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.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 (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.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) { |
|
||||
let page = this.optUp.page; |
|
||||
if (page && this.prePageNum) { |
|
||||
page.num = this.prePageNum; |
|
||||
page.time = this.prePageTime; |
|
||||
} |
|
||||
this.endDownScroll(); |
|
||||
} |
|
||||
// 结束上拉,回调失败重置回原来的页码
|
|
||||
if (this.isUpScrolling) { |
|
||||
this.optUp.page.num--; |
|
||||
this.endUpScroll(false); |
|
||||
// 如果是mescroll-body,则需往回滚一定距离
|
|
||||
if(this.isScrollBody && errDistance !== 0){ // 不处理0
|
|
||||
if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
|
|
||||
this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
|
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/* 显示空布局 */ |
|
||||
MeScroll.prototype.showEmpty = function() { |
|
||||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true) |
|
||||
} |
|
||||
|
|
||||
/* 移除空布局 */ |
|
||||
MeScroll.prototype.removeEmpty = function() { |
|
||||
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false) |
|
||||
} |
|
||||
|
|
||||
/* 显示回到顶部的按钮 */ |
|
||||
MeScroll.prototype.showTopBtn = function() { |
|
||||
if (!this.topBtnShow) { |
|
||||
this.topBtnShow = true; |
|
||||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(true); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/* 隐藏回到顶部的按钮 */ |
|
||||
MeScroll.prototype.hideTopBtn = function() { |
|
||||
if (this.topBtnShow) { |
|
||||
this.topBtnShow = false; |
|
||||
this.optUp.toTop.onShow && this.optUp.toTop.onShow(false); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/* 获取滚动条的位置 */ |
|
||||
MeScroll.prototype.getScrollTop = function() { |
|
||||
return this.scrollTop || 0 |
|
||||
} |
|
||||
|
|
||||
/* 记录滚动条的位置 */ |
|
||||
MeScroll.prototype.setScrollTop = function(y) { |
|
||||
this.scrollTop = y; |
|
||||
} |
|
||||
|
|
||||
/* 滚动到指定位置 */ |
|
||||
MeScroll.prototype.scrollTo = function(y, t) { |
|
||||
this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
|
|
||||
} |
|
||||
|
|
||||
/* 自定义scrollTo */ |
|
||||
MeScroll.prototype.resetScrollTo = function(myScrollTo) { |
|
||||
this.myScrollTo = myScrollTo |
|
||||
} |
|
||||
|
|
||||
/* 滚动条到底部的距离 */ |
|
||||
MeScroll.prototype.getScrollBottom = function() { |
|
||||
return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop() |
|
||||
} |
|
||||
|
|
||||
/* 计步器 |
|
||||
star: 开始值 |
|
||||
end: 结束值 |
|
||||
callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器; |
|
||||
t: 计步时长,传0则直接回调end值;不传则默认300ms |
|
||||
rate: 周期;不传则默认30ms计步一次 |
|
||||
* */ |
|
||||
MeScroll.prototype.getStep = function(star, end, callback, t, rate) { |
|
||||
let diff = end - star; // 差值
|
|
||||
if (t === 0 || diff === 0) { |
|
||||
callback && callback(end); |
|
||||
return; |
|
||||
} |
|
||||
t = t || 300; // 时长 300ms
|
|
||||
rate = rate || 30; // 周期 30ms
|
|
||||
let count = t / rate; // 次数
|
|
||||
let step = diff / count; // 步长
|
|
||||
let i = 0; // 计数
|
|
||||
let timer = setInterval(function() { |
|
||||
if (i < count - 1) { |
|
||||
star += step; |
|
||||
callback && callback(star, timer); |
|
||||
i++; |
|
||||
} else { |
|
||||
callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
|
|
||||
clearInterval(timer); |
|
||||
} |
|
||||
}, rate); |
|
||||
} |
|
||||
|
|
||||
/* 滚动容器的高度 */ |
|
||||
MeScroll.prototype.getClientHeight = function(isReal) { |
|
||||
let h = this.clientHeight || 0 |
|
||||
if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
|
|
||||
h = this.getBodyHeight() |
|
||||
} |
|
||||
return h |
|
||||
} |
|
||||
MeScroll.prototype.setClientHeight = function(h) { |
|
||||
this.clientHeight = h; |
|
||||
} |
|
||||
|
|
||||
/* 滚动内容的高度 */ |
|
||||
MeScroll.prototype.getScrollHeight = function() { |
|
||||
return this.scrollHeight || 0; |
|
||||
} |
|
||||
MeScroll.prototype.setScrollHeight = function(h) { |
|
||||
this.scrollHeight = h; |
|
||||
} |
|
||||
|
|
||||
/* body的高度 */ |
|
||||
MeScroll.prototype.getBodyHeight = function() { |
|
||||
return this.bodyHeight || 0; |
|
||||
} |
|
||||
MeScroll.prototype.setBodyHeight = function(h) { |
|
||||
this.bodyHeight = h; |
|
||||
} |
|
||||
|
|
||||
/* 阻止浏览器默认滚动事件 */ |
|
||||
MeScroll.prototype.preventDefault = function(e) { |
|
||||
// 小程序不支持e.preventDefault, 已在wxs中禁止
|
|
||||
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止
|
|
||||
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
|
|
||||
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault() |
|
||||
} |
|
||||
@ -1,424 +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-into-view="scrollToViewId" :scroll-with-animation="scrollAnim" @scroll="scroll" |
|
||||
:scroll-y='scrollable' :enable-back-to-top="true"> |
|
||||
<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"> |
|
||||
<!-- <image style="width: 160rpx; height: 40rpx;" src="/static/images/logo.png" mode="widthFix"></image> --> |
|
||||
<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" @click="toTopClick"></mescroll-top> |
|
||||
|
|
||||
<!-- #ifdef MP-WEIXIN || APP-PLUS || H5 --> |
|
||||
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 --> |
|
||||
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view> |
|
||||
<!-- #endif --> |
|
||||
</view> |
|
||||
</template> |
|
||||
|
|
||||
<!-- 微信小程序, app, h5使用wxs --> |
|
||||
<!-- #ifdef MP-WEIXIN || 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 MescrollEmpty from './components/mescroll-empty.vue'; |
|
||||
// 引入回到顶部组件 |
|
||||
import MescrollTop from './components/mescroll-top.vue'; |
|
||||
// 引入兼容wxs(含renderjs)写法的mixins |
|
||||
import WxsMixin from './wxs/mixins.js'; |
|
||||
|
|
||||
export default { |
|
||||
mixins: [WxsMixin], |
|
||||
components: { |
|
||||
MescrollEmpty, |
|
||||
MescrollTop |
|
||||
}, |
|
||||
data() { |
|
||||
return { |
|
||||
mescroll: { |
|
||||
optDown: {}, |
|
||||
optUp: {} |
|
||||
}, // mescroll实例 |
|
||||
viewId: 'id_' + Math.random().toString(36).substr(2, 16), // 随机生成mescroll的id(不能数字开头,否则找不到元素) |
|
||||
downHight: 0, //下拉刷新: 容器高度 |
|
||||
downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1) |
|
||||
downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll) |
|
||||
upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示) |
|
||||
isShowEmpty: false, // 是否显示空布局 |
|
||||
isShowToTop: false, // 是否显示回到顶部按钮 |
|
||||
scrollTop: 0, // 滚动条的位置 |
|
||||
scrollAnim: false, // 是否开启滚动动画 |
|
||||
windowTop: 0, // 可使用窗口的顶部位置 |
|
||||
windowBottom: 0, // 可使用窗口的底部位置 |
|
||||
windowHeight: 0, // 可使用窗口的高度 |
|
||||
statusBarHeight: 0, // 状态栏高度 |
|
||||
scrollToViewId: '' // 滚动到指定view的id |
|
||||
} |
|
||||
}, |
|
||||
props: { |
|
||||
down: Object, // 下拉刷新的参数配置 |
|
||||
up: Object, // 上拉加载的参数配置 |
|
||||
top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|
||||
topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变) |
|
||||
bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|
||||
safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用) |
|
||||
fixed: { // 是否通过fixed固定mescroll的高度, 默认true |
|
||||
type: Boolean, |
|
||||
default: true |
|
||||
}, |
|
||||
height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight) |
|
||||
bottombar: { // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) |
|
||||
type: Boolean, |
|
||||
default: true |
|
||||
} |
|
||||
}, |
|
||||
computed: { |
|
||||
// 是否使用fixed定位 (当height有值,则不使用) |
|
||||
isFixed() { |
|
||||
return !this.height && this.fixed |
|
||||
}, |
|
||||
// mescroll的高度 |
|
||||
scrollHeight() { |
|
||||
if (this.isFixed) { |
|
||||
return "auto" |
|
||||
} else if (this.height) { |
|
||||
return this.toPx(this.height) + 'px' |
|
||||
} else { |
|
||||
return "100%" |
|
||||
} |
|
||||
}, |
|
||||
// 下拉布局往下偏移的距离 (px) |
|
||||
numTop() { |
|
||||
return this.toPx(this.top) |
|
||||
}, |
|
||||
fixedTop() { |
|
||||
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0 |
|
||||
}, |
|
||||
padTop() { |
|
||||
return !this.isFixed ? this.numTop + 'px' : 0 |
|
||||
}, |
|
||||
// 上拉布局往上偏移 (px) |
|
||||
numBottom() { |
|
||||
return this.toPx(this.bottom) |
|
||||
}, |
|
||||
fixedBottom() { |
|
||||
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0 |
|
||||
}, |
|
||||
padBottom() { |
|
||||
return !this.isFixed ? this.numBottom + 'px' : 0 |
|
||||
}, |
|
||||
// 是否为重置下拉的状态 |
|
||||
isDownReset() { |
|
||||
return this.downLoadType === 3 || this.downLoadType === 4 |
|
||||
}, |
|
||||
// 过渡 |
|
||||
transition() { |
|
||||
return this.isDownReset ? 'transform 300ms' : ''; |
|
||||
}, |
|
||||
translateY() { |
|
||||
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外 |
|
||||
}, |
|
||||
// 列表是否可滑动 |
|
||||
scrollable() { |
|
||||
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.optDown.textLoading; |
|
||||
default: |
|
||||
return this.mescroll.optDown.textInOffset; |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
//number,rpx,upx,px,% --> px的数值 |
|
||||
toPx(num) { |
|
||||
if (typeof num === "string") { |
|
||||
if (num.indexOf('px') !== -1) { |
|
||||
if (num.indexOf('rpx') !== -1) { // "10rpx" |
|
||||
num = num.replace('rpx', ''); |
|
||||
} else if (num.indexOf('upx') !== -1) { // "10upx" |
|
||||
num = num.replace('upx', ''); |
|
||||
} else { // "10px" |
|
||||
return Number(num.replace('px', '')) |
|
||||
} |
|
||||
} else if (num.indexOf('%') !== -1) { |
|
||||
// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10% |
|
||||
let rate = Number(num.replace("%", "")) / 100 |
|
||||
return this.windowHeight * rate |
|
||||
} |
|
||||
} |
|
||||
return num ? uni.upx2px(Number(num)) : 0 |
|
||||
}, |
|
||||
//注册列表滚动事件,用于下拉刷新和上拉加载 |
|
||||
scroll(e) { |
|
||||
this.mescroll.scroll(e.detail, () => { |
|
||||
this.$emit('scroll', this.mescroll) // 此时可直接通过 this.mescroll.scrollTop获取滚动条位置; this.mescroll.isScrollUp获取是否向上滑动 |
|
||||
}) |
|
||||
}, |
|
||||
// 点击空布局的按钮回调 |
|
||||
emptyClick() { |
|
||||
this.$emit('emptyclick', this.mescroll) |
|
||||
}, |
|
||||
// 点击回到顶部的按钮回调 |
|
||||
toTopClick() { |
|
||||
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); // 执行回到顶部 |
|
||||
this.$emit('topclick', this.mescroll); // 派发点击回到顶部按钮的回调 |
|
||||
}, |
|
||||
// 更新滚动区域的高度 (使内容不满屏和到底,都可继续翻页) |
|
||||
setClientHeight() { |
|
||||
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) { |
|
||||
this.isExec = true; // 避免多次获取 |
|
||||
this.$nextTick(() => { // 确保dom已渲染 |
|
||||
let query = uni.createSelectorQuery(); |
|
||||
// #ifndef MP-ALIPAY |
|
||||
query = query.in(this) // 支付宝小程序不支持in(this),而字节跳动小程序必须写in(this), 否则都取不到值 |
|
||||
// #endif |
|
||||
let view = query.select('#' + this.viewId); |
|
||||
view.boundingClientRect(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) |
|
||||
} |
|
||||
}).exec(); |
|
||||
}) |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效 |
|
||||
created() { |
|
||||
let vm = this; |
|
||||
|
|
||||
let diyOption = { |
|
||||
// 下拉刷新的配置 |
|
||||
down: { |
|
||||
inOffset() { |
|
||||
vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删) |
|
||||
}, |
|
||||
outOffset() { |
|
||||
vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删) |
|
||||
}, |
|
||||
onMoving(mescroll, rate, downHight) { |
|
||||
// 下拉过程中的回调,滑动过程一直在执行; |
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|
||||
vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1) |
|
||||
}, |
|
||||
showLoading(mescroll, downHight) { |
|
||||
vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删) |
|
||||
vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删) |
|
||||
}, |
|
||||
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() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
MeScroll.extend(diyOption, GlobalOption); // 混入全局的配置 |
|
||||
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 |
|
||||
// init回调mescroll对象 |
|
||||
vm.$emit('init', vm.mescroll); |
|
||||
|
|
||||
// 设置高度 |
|
||||
const sys = uni.getSystemInfoSync(); |
|
||||
if (sys.windowTop) vm.windowTop = sys.windowTop; |
|
||||
if (sys.windowBottom) vm.windowBottom = sys.windowBottom; |
|
||||
if (sys.windowHeight) vm.windowHeight = sys.windowHeight; |
|
||||
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight; |
|
||||
// 使down的bottomOffset生效 |
|
||||
vm.mescroll.setBodyHeight(sys.windowHeight); |
|
||||
|
|
||||
// 因为使用的是scrollview,这里需自定义scrollTo |
|
||||
vm.mescroll.resetScrollTo((y, t) => { |
|
||||
vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡 |
|
||||
if (typeof y === 'string') { // 第一个参数如果为字符串,则使用scroll-into-view |
|
||||
// #ifdef MP-WEIXIN |
|
||||
// 微信小程序暂不支持slot里面的scroll-into-view,只能计算位置实现 |
|
||||
uni.createSelectorQuery().select('#' + vm.viewId).boundingClientRect(function(rect) { |
|
||||
let mescrollTop = rect.top // mescroll到顶部的距离 |
|
||||
uni.createSelectorQuery().select('#' + y).boundingClientRect(function(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 |
|
||||
}) |
|
||||
}).exec() |
|
||||
}).exec() |
|
||||
// #endif |
|
||||
|
|
||||
// #ifndef MP-WEIXIN |
|
||||
if (vm.scrollToViewId != y) { |
|
||||
vm.scrollToViewId = y; |
|
||||
} else { |
|
||||
vm.scrollToViewId = ''; // scrollToViewId必须变化才会生效,所以此处先置空再赋值 |
|
||||
vm.$nextTick(function() { |
|
||||
vm.scrollToViewId = y; |
|
||||
}) |
|
||||
} |
|
||||
// #endif |
|
||||
return; |
|
||||
} |
|
||||
let curY = vm.mescroll.getScrollTop() |
|
||||
if (t === 0 || t === 300) { // 当t使用默认配置的300时,则使用系统自带的动画过渡 |
|
||||
vm.scrollTop = curY; |
|
||||
vm.$nextTick(function() { |
|
||||
vm.scrollTop = y |
|
||||
}) |
|
||||
} else { |
|
||||
vm.mescroll.getStep(curY, y, step => { // 此写法可支持配置t |
|
||||
vm.scrollTop = step |
|
||||
}, t) |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
// 具体的界面如果不配置up.toTop.safearea,则取本vue的safearea值 |
|
||||
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else { |
|
||||
vm.mescroll.optUp.toTop.safearea = vm.safearea; |
|
||||
} |
|
||||
}, |
|
||||
mounted() { |
|
||||
// 设置容器的高度 |
|
||||
this.setClientHeight() |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style> |
|
||||
@import "./mescroll-uni.css"; |
|
||||
@import "./components/mescroll-down.css"; |
|
||||
@import './components/mescroll-up.css'; |
|
||||
</style> |
|
||||
@ -1,50 +0,0 @@ |
|||||
/** |
|
||||
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期: |
|
||||
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例) |
|
||||
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例) |
|
||||
*/ |
|
||||
const MescrollCompMixin = { |
|
||||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级)
|
|
||||
onPageScroll(e) { |
|
||||
this.handlePageScroll(e) |
|
||||
}, |
|
||||
onReachBottom() { |
|
||||
this.handleReachBottom() |
|
||||
}, |
|
||||
// 当down的native: true时, 还需传递此方法进到子组件
|
|
||||
onPullDownRefresh(){ |
|
||||
this.handlePullDownRefresh() |
|
||||
}, |
|
||||
// mescroll-body写在子子子...组件的情况 (多级)
|
|
||||
data() { |
|
||||
return { |
|
||||
mescroll: { |
|
||||
onPageScroll: e=>{ |
|
||||
this.handlePageScroll(e) |
|
||||
}, |
|
||||
onReachBottom: ()=>{ |
|
||||
this.handleReachBottom() |
|
||||
}, |
|
||||
onPullDownRefresh: ()=>{ |
|
||||
this.handlePullDownRefresh() |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
methods:{ |
|
||||
handlePageScroll(e){ |
|
||||
let item = this.$refs["mescrollItem"]; |
|
||||
if(item && item.mescroll) item.mescroll.onPageScroll(e); |
|
||||
}, |
|
||||
handleReachBottom(){ |
|
||||
let item = this.$refs["mescrollItem"]; |
|
||||
if(item && item.mescroll) item.mescroll.onReachBottom(); |
|
||||
}, |
|
||||
handlePullDownRefresh(){ |
|
||||
let item = this.$refs["mescrollItem"]; |
|
||||
if(item && item.mescroll) item.mescroll.onPullDownRefresh(); |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default MescrollCompMixin; |
|
||||
@ -1,51 +0,0 @@ |
|||||
/** |
|
||||
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例) |
|
||||
*/ |
|
||||
const MescrollMoreItemMixin = { |
|
||||
// 支付宝小程序不支持props的mixin,需写在具体的页面中
|
|
||||
// #ifndef MP-ALIPAY
|
|
||||
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.isInit = true; // 标记为true
|
|
||||
this.mescroll && this.mescroll.triggerDownScroll(); |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
// mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
|
|
||||
mescrollInit(mescroll) { |
|
||||
this.mescroll = mescroll; |
|
||||
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
|
|
||||
// 自动加载当前tab的数据
|
|
||||
if(this.i === this.index){ |
|
||||
this.isInit = true; // 标记为true
|
|
||||
this.mescroll.triggerDownScroll(); |
|
||||
} |
|
||||
}, |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default MescrollMoreItemMixin; |
|
||||
@ -1,56 +0,0 @@ |
|||||
/** |
|
||||
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期: |
|
||||
* 当一个页面只有一个mescroll-body写在子组件时, 则使用mescroll-comp.js (参考 mescroll-comp 案例) |
|
||||
* 当一个页面有多个mescroll-body写在子组件时, 则使用mescroll-more.js (参考 mescroll-more 案例) |
|
||||
*/ |
|
||||
const MescrollMoreMixin = { |
|
||||
data() { |
|
||||
return { |
|
||||
tabIndex: 0 // 当前tab下标
|
|
||||
} |
|
||||
}, |
|
||||
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
|
|
||||
onPageScroll(e) { |
|
||||
let mescroll = this.getMescroll(this.tabIndex); |
|
||||
mescroll && mescroll.onPageScroll(e); |
|
||||
}, |
|
||||
onReachBottom() { |
|
||||
let mescroll = this.getMescroll(this.tabIndex); |
|
||||
mescroll && mescroll.onReachBottom(); |
|
||||
}, |
|
||||
// 当down的native: true时, 还需传递此方法进到子组件
|
|
||||
onPullDownRefresh(){ |
|
||||
let mescroll = this.getMescroll(this.tabIndex); |
|
||||
mescroll && mescroll.onPullDownRefresh(); |
|
||||
}, |
|
||||
methods:{ |
|
||||
// 根据下标获取对应子组件的mescroll
|
|
||||
getMescroll(i){ |
|
||||
if(!this.mescrollItems) this.mescrollItems = []; |
|
||||
if(!this.mescrollItems[i]) { |
|
||||
// v-for中的refs
|
|
||||
let vForItem = this.$refs["mescrollItem"]; |
|
||||
if(vForItem){ |
|
||||
this.mescrollItems[i] = vForItem[i] |
|
||||
}else{ |
|
||||
// 普通的refs,不可重复
|
|
||||
this.mescrollItems[i] = this.$refs["mescrollItem"+i]; |
|
||||
} |
|
||||
} |
|
||||
let item = this.mescrollItems[i] |
|
||||
return item ? item.mescroll : null |
|
||||
}, |
|
||||
// 切换tab,恢复滚动条位置
|
|
||||
tabChange(i){ |
|
||||
let mescroll = this.getMescroll(i); |
|
||||
if(mescroll){ |
|
||||
// 延时(比$nextTick靠谱一些),确保元素已渲染
|
|
||||
setTimeout(()=>{ |
|
||||
mescroll.scrollTo(mescroll.getScrollTop(),0) |
|
||||
},30) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default MescrollMoreMixin; |
|
||||
@ -1,102 +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 || 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 |
|
||||
}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 || 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的方法 (值改变才触发更新)
|
|
||||
setTimeout(()=>{ |
|
||||
if(this.downLoadType === 4 || this.downLoadType === 0){ |
|
||||
this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
|
|
||||
} |
|
||||
},320) |
|
||||
} |
|
||||
// 初始化wxs的数据
|
|
||||
this.wxsCall({type: 'setWxsProp'}) |
|
||||
// #endif
|
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default WxsMixin; |
|
||||
@ -1,92 +0,0 @@ |
|||||
// 使用renderjs直接操作window对象,实现动态控制app和h5的bounce
|
|
||||
// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止)
|
|
||||
// https://uniapp.dcloud.io/frame?id=renderjs
|
|
||||
|
|
||||
// 与wxs的me实例一致
|
|
||||
var me = {} |
|
||||
|
|
||||
// 初始化window对象的touch事件 (仅初始化一次)
|
|
||||
if(window && !window.$mescrollRenderInit){ |
|
||||
window.$mescrollRenderInit = true |
|
||||
|
|
||||
|
|
||||
window.addEventListener('touchstart', function(e){ |
|
||||
if (me.disabled()) return; |
|
||||
me.startPoint = me.getPoint(e); // 记录起点
|
|
||||
}, {passive: true}) |
|
||||
|
|
||||
|
|
||||
window.addEventListener('touchmove', function(e){ |
|
||||
if (me.disabled()) return; |
|
||||
if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce
|
|
||||
|
|
||||
var curPoint = me.getPoint(e); // 当前点
|
|
||||
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
|
|
||||
// 向下拉
|
|
||||
if (moveY > 0) { |
|
||||
// 可下拉的条件
|
|
||||
if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) { |
|
||||
|
|
||||
// 只有touch在mescroll的view上面,才禁止bounce
|
|
||||
var el = e.target; |
|
||||
var isMescrollTouch = false; |
|
||||
while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") { |
|
||||
var cls = el.classList; |
|
||||
if (cls && cls.contains('mescroll-render-touch')) { |
|
||||
isMescrollTouch = true |
|
||||
break; |
|
||||
} |
|
||||
el = el.parentNode; // 继续检查其父元素
|
|
||||
} |
|
||||
// 禁止bounce (不会对swiper和iOS侧滑返回造成影响)
|
|
||||
if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault(); |
|
||||
} |
|
||||
} |
|
||||
}, {passive: false}) |
|
||||
} |
|
||||
|
|
||||
/* 获取滚动条的位置 */ |
|
||||
me.getScrollTop = function() { |
|
||||
return me.scrollTop || 0 |
|
||||
} |
|
||||
|
|
||||
/* 是否禁用下拉刷新 */ |
|
||||
me.disabled = function(){ |
|
||||
return !me.optDown || !me.optDown.use || me.optDown.native |
|
||||
} |
|
||||
|
|
||||
/* 根据点击滑动事件获取第一个手指的坐标 */ |
|
||||
me.getPoint = function(e) { |
|
||||
if (!e) { |
|
||||
return {x: 0,y: 0} |
|
||||
} |
|
||||
if (e.touches && e.touches[0]) { |
|
||||
return {x: e.touches[0].pageX,y: e.touches[0].pageY} |
|
||||
} else if (e.changedTouches && e.changedTouches[0]) { |
|
||||
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY} |
|
||||
} else { |
|
||||
return {x: e.clientX,y: e.clientY} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 监听逻辑层数据的变化 (实时更新数据) |
|
||||
*/ |
|
||||
function propObserver(wxsProp) { |
|
||||
me.optDown = wxsProp.optDown |
|
||||
me.scrollTop = wxsProp.scrollTop |
|
||||
me.isDownScrolling = wxsProp.isDownScrolling |
|
||||
me.isUpScrolling = wxsProp.isUpScrolling |
|
||||
me.isUpBoth = wxsProp.isUpBoth |
|
||||
} |
|
||||
|
|
||||
/* 导出模块 */ |
|
||||
const renderBiz = { |
|
||||
data() { |
|
||||
return { |
|
||||
propObserver: propObserver, |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
export default renderBiz; |
|
||||
@ -1,268 +0,0 @@ |
|||||
// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响 |
|
||||
// https://uniapp.dcloud.io/frame?id=wxs |
|
||||
// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html |
|
||||
|
|
||||
// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致 |
|
||||
var me = {} |
|
||||
|
|
||||
// ------ 自定义下拉刷新动画 start ------ |
|
||||
|
|
||||
/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */ |
|
||||
me.onMoving = function (ins, rate, downHight){ |
|
||||
ins.requestAnimationFrame(function () { |
|
||||
ins.selectComponent('.mescroll-wxs-content').setStyle({ |
|
||||
'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题 |
|
||||
'transform': 'translateY(' + downHight + 'px)', |
|
||||
'transition': '' |
|
||||
}) |
|
||||
// 环形进度条 |
|
||||
var progress = ins.selectComponent('.mescroll-wxs-progress') |
|
||||
progress && progress.setStyle({transform: 'rotate(' + 360 * rate + 'deg)'}) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/* 显示下拉刷新进度 */ |
|
||||
me.showLoading = function (ins){ |
|
||||
me.downHight = me.optDown.offset |
|
||||
ins.requestAnimationFrame(function () { |
|
||||
ins.selectComponent('.mescroll-wxs-content').setStyle({ |
|
||||
'will-change': 'auto', |
|
||||
'transform': 'translateY(' + me.downHight + 'px)', |
|
||||
'transition': 'transform 300ms' |
|
||||
}) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/* 结束下拉 */ |
|
||||
me.endDownScroll = function (ins){ |
|
||||
me.downHight = 0; |
|
||||
me.isDownScrolling = false; |
|
||||
ins.requestAnimationFrame(function () { |
|
||||
ins.selectComponent('.mescroll-wxs-content').setStyle({ |
|
||||
'will-change': 'auto', |
|
||||
'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空) |
|
||||
'transition': 'transform 300ms' |
|
||||
}) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */ |
|
||||
me.clearTransform = function (ins){ |
|
||||
ins.requestAnimationFrame(function () { |
|
||||
ins.selectComponent('.mescroll-wxs-content').setStyle({ |
|
||||
'will-change': '', |
|
||||
'transform': '', |
|
||||
'transition': '' |
|
||||
}) |
|
||||
}) |
|
||||
} |
|
||||
|
|
||||
// ------ 自定义下拉刷新动画 end ------ |
|
||||
|
|
||||
/** |
|
||||
* 监听逻辑层数据的变化 (实时更新数据) |
|
||||
*/ |
|
||||
function propObserver(wxsProp) { |
|
||||
me.optDown = wxsProp.optDown |
|
||||
me.scrollTop = wxsProp.scrollTop |
|
||||
me.bodyHeight = wxsProp.bodyHeight |
|
||||
me.isDownScrolling = wxsProp.isDownScrolling |
|
||||
me.isUpScrolling = wxsProp.isUpScrolling |
|
||||
me.isUpBoth = wxsProp.isUpBoth |
|
||||
me.isScrollBody = wxsProp.isScrollBody |
|
||||
me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确 |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 监听逻辑层数据的变化 (调用wxs的方法) |
|
||||
*/ |
|
||||
function callObserver(callProp, oldValue, ins) { |
|
||||
if (me.disabled()) return; |
|
||||
if(callProp.callType){ |
|
||||
// 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style |
|
||||
if(callProp.callType === 'showLoading'){ |
|
||||
me.showLoading(ins) |
|
||||
}else if(callProp.callType === 'endDownScroll'){ |
|
||||
me.endDownScroll(ins) |
|
||||
}else if(callProp.callType === 'clearTransform'){ |
|
||||
me.clearTransform(ins) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* touch事件 |
|
||||
*/ |
|
||||
function touchstartEvent(e, ins) { |
|
||||
me.downHight = 0; // 下拉的距离 |
|
||||
me.startPoint = me.getPoint(e); // 记录起点 |
|
||||
me.startTop = me.getScrollTop(); // 记录此时的滚动条位置 |
|
||||
me.startAngle = 0; // 初始角度 |
|
||||
me.lastPoint = me.startPoint; // 重置上次move的点 |
|
||||
me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况) |
|
||||
me.inTouchend = false; // 标记不是touchend |
|
||||
|
|
||||
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步) |
|
||||
} |
|
||||
|
|
||||
function touchmoveEvent(e, ins) { |
|
||||
var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效) |
|
||||
|
|
||||
if (me.disabled()) return isPrevent; |
|
||||
|
|
||||
var scrollTop = me.getScrollTop(); // 当前滚动条的距离 |
|
||||
var curPoint = me.getPoint(e); // 当前点 |
|
||||
|
|
||||
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 |
|
||||
|
|
||||
// 向下拉 && 在顶部 |
|
||||
// mescroll-body,直接判定在顶部即可 |
|
||||
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove |
|
||||
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等 |
|
||||
if (moveY > 0 && ( |
|
||||
(me.isScrollBody && scrollTop <= 0) |
|
||||
|| |
|
||||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) ) |
|
||||
)) { |
|
||||
// 可下拉的条件 |
|
||||
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && |
|
||||
me.isUpBoth))) { |
|
||||
|
|
||||
// 下拉的角度是否在配置的范围内 |
|
||||
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90] |
|
||||
if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新 |
|
||||
|
|
||||
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发 |
|
||||
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) { |
|
||||
me.inTouchend = true; // 标记执行touchend |
|
||||
touchendEvent(e, ins); // 提前触发touchend |
|
||||
return isPrevent; |
|
||||
} |
|
||||
|
|
||||
isPrevent = false // 小程序是return false |
|
||||
|
|
||||
var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上) |
|
||||
|
|
||||
// 下拉距离 < 指定距离 |
|
||||
if (me.downHight < me.optDown.offset) { |
|
||||
if (me.movetype !== 1) { |
|
||||
me.movetype = 1; // 加入标记,保证只执行一次 |
|
||||
// me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次 |
|
||||
me.callMethod(ins, {type: 'setLoadType', downLoadType: 1}) |
|
||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来 |
|
||||
} |
|
||||
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小 |
|
||||
|
|
||||
// 指定距离 <= 下拉距离 |
|
||||
} else { |
|
||||
if (me.movetype !== 2) { |
|
||||
me.movetype = 2; // 加入标记,保证只执行一次 |
|
||||
// me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次 |
|
||||
me.callMethod(ins, {type: 'setLoadType', downLoadType: 2}) |
|
||||
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来 |
|
||||
} |
|
||||
if (diff > 0) { // 向下拉 |
|
||||
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小 |
|
||||
} else { // 向上收 |
|
||||
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度 |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
me.downHight = Math.round(me.downHight) // 取整 |
|
||||
var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值 |
|
||||
// me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行 |
|
||||
me.onMoving(ins, rate, me.downHight) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
me.lastPoint = curPoint; // 记录本次移动的点 |
|
||||
|
|
||||
return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效) |
|
||||
} |
|
||||
|
|
||||
function touchendEvent(e, ins) { |
|
||||
// 如果下拉区域高度已改变,则需重置回来 |
|
||||
if (me.isMoveDown) { |
|
||||
if (me.downHight >= me.optDown.offset) { |
|
||||
// 符合触发刷新的条件 |
|
||||
me.downHight = me.optDown.offset; // 更新下拉区域高度 |
|
||||
// me.triggerDownScroll(); |
|
||||
me.callMethod(ins, {type: 'triggerDownScroll'}) |
|
||||
} else { |
|
||||
// 不符合的话 则重置 |
|
||||
me.downHight = 0; |
|
||||
// me.optDown.endDownScroll && me.optDown.endDownScroll(me); |
|
||||
me.callMethod(ins, {type: 'endDownScroll'}) |
|
||||
} |
|
||||
me.movetype = 0; |
|
||||
me.isMoveDown = false; |
|
||||
} else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件 |
|
||||
var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉 |
|
||||
// 上滑 |
|
||||
if (isScrollUp) { |
|
||||
// 需检查滑动的角度 |
|
||||
var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90] |
|
||||
if (angle > 80) { |
|
||||
// 检查并触发上拉 |
|
||||
// me.triggerUpScroll(true); |
|
||||
me.callMethod(ins, {type: 'triggerUpScroll'}) |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步) |
|
||||
} |
|
||||
|
|
||||
/* 是否禁用下拉刷新 */ |
|
||||
me.disabled = function(){ |
|
||||
return !me.optDown || !me.optDown.use || me.optDown.native |
|
||||
} |
|
||||
|
|
||||
/* 根据点击滑动事件获取第一个手指的坐标 */ |
|
||||
me.getPoint = function(e) { |
|
||||
if (!e) { |
|
||||
return {x: 0,y: 0} |
|
||||
} |
|
||||
if (e.touches && e.touches[0]) { |
|
||||
return {x: e.touches[0].pageX,y: e.touches[0].pageY} |
|
||||
} else if (e.changedTouches && e.changedTouches[0]) { |
|
||||
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY} |
|
||||
} else { |
|
||||
return {x: e.clientX,y: e.clientY} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/* 计算两点之间的角度: 区间 [0,90]*/ |
|
||||
me.getAngle = function (p1, p2) { |
|
||||
var x = Math.abs(p1.x - p2.x); |
|
||||
var y = Math.abs(p1.y - p2.y); |
|
||||
var z = Math.sqrt(x * x + y * y); |
|
||||
var angle = 0; |
|
||||
if (z !== 0) { |
|
||||
angle = Math.asin(y / z) / Math.PI * 180; |
|
||||
} |
|
||||
return angle |
|
||||
} |
|
||||
|
|
||||
/* 获取滚动条的位置 */ |
|
||||
me.getScrollTop = function() { |
|
||||
return me.scrollTop || 0 |
|
||||
} |
|
||||
|
|
||||
/* 获取body的高度 */ |
|
||||
me.getBodyHeight = function() { |
|
||||
return me.bodyHeight || 0; |
|
||||
} |
|
||||
|
|
||||
/* 调用逻辑层的方法 */ |
|
||||
me.callMethod = function(ins, param) { |
|
||||
if(ins) ins.callMethod('wxsCall', param) |
|
||||
} |
|
||||
|
|
||||
/* 导出模块 */ |
|
||||
module.exports = { |
|
||||
propObserver: propObserver, |
|
||||
callObserver: callObserver, |
|
||||
touchstartEvent: touchstartEvent, |
|
||||
touchmoveEvent: touchmoveEvent, |
|
||||
touchendEvent: touchendEvent |
|
||||
} |
|
||||
@ -1,184 +0,0 @@ |
|||||
<template> |
|
||||
<view> |
|
||||
<mescroll-uni ref="mescrollRef" @init="mescrollInit" height="100%" top="0" :down="downOption" @down="downCallback" |
|
||||
:up="upOption" @up="upCallback"> |
|
||||
<!-- 数据列表 --> |
|
||||
<view> |
|
||||
|
|
||||
<block v-for="(item, index) in assetsFlow" :key="index"> |
|
||||
<view class="margin-top bg-white"> |
|
||||
<view class="flex justify-between align-start solid-bottom padding-tb-sm padding-lr" @tap="goDetails(item)"> |
|
||||
<!-- <image src="@/static/tu.png" mode="aspectFill" style="width: 150rpx; height: 150rpx;"></image> --> |
|
||||
<view class='cu-avatar xxl radius' :style="{'background-image':'url('+item.goods.cover+')'}"> |
|
||||
<view class='cu-tag badge padding' :style="{'background-color':item.state_text.bg_color,'color':item.state_text.color}">{{item.state_text.text}}</view> |
|
||||
</view> |
|
||||
<view class="flex-sub padding-left-sm"> |
|
||||
<view class="bref-box margin-top-xs"> |
|
||||
{{item.goods.name}} |
|
||||
</view> |
|
||||
<text class="block margin-top-sm text-gray text-sm">数量 <text class="margin-left margin-right-xs text-gray">x</text>{{item.number}}</text> |
|
||||
|
|
||||
<view class="flex justify-between margin-top-sm"> |
|
||||
<view class="text-red text-price text-lg"> |
|
||||
<amount :value="Number(item.selling_price || 0)" :is-round-up="false" :precision="2" :duration="800" transition></amount> |
|
||||
</view> |
|
||||
<view> |
|
||||
<button v-if="item.state==1" class="cu-btn bg-orange round margin-left-sm" @tap.stop="$routerGo('/pages/order/confirm-order?goods_id='+item.goods_id+'&goods_specs_id='+item.goods_specs_id)">立即付款</button> |
|
||||
<button v-if="item.state==2" class="cu-btn bg-green round margin-left-sm" @tap="$routerGo('/pages/order/order-details?orderid='+item.id)">立即使用</button> |
|
||||
<!-- <button v-if="item.state==2" class="cu-btn line-black round margin-left-sm" @tap="orderClick(item,'modalShowConfirm')">确认收货</button> --> |
|
||||
</view> |
|
||||
</view> |
|
||||
</view> |
|
||||
</view> |
|
||||
<view class="padding-lr padding-tb-sm order-bottom"> |
|
||||
<text class="cuIcon-fold bg-white order-ico"></text> |
|
||||
<view class="flex justify-between align-center"> |
|
||||
<view style="color: #777777;">{{item.created_at_text}}</view> |
|
||||
<view class="flex align-center justify-end"> |
|
||||
实付: |
|
||||
<view class="text-price text-red text-xl"> |
|
||||
<amount :value="Number(item.amount || 0)" :is-round-up="false" :precision="2" :duration="800" transition></amount> |
|
||||
</view> |
|
||||
</view> |
|
||||
</view> |
|
||||
</view> |
|
||||
</view> |
|
||||
</block> |
|
||||
</view> |
|
||||
|
|
||||
</mescroll-uni> |
|
||||
|
|
||||
<!-- 取消订单 --> |
|
||||
<my-uni-modal title="取消订单" content="您确定要取消该订单吗?" btnText="取消订单" :modalShow="modalShowCancel" @determine="cancelDetermine" |
|
||||
@hideModal="modalShowCancel=false" /> |
|
||||
|
|
||||
<!-- 删除订单 --> |
|
||||
<my-uni-modal title="删除订单" content="您确定要删除该订单吗?" btnText="删除订单" :modalShow="modalShowDel" @determine="delDetermine" |
|
||||
@hideModal="modalShowDel=false" /> |
|
||||
|
|
||||
<!-- 确认收货 --> |
|
||||
<my-uni-modal title="确认收货" content="请确认您已经收到商品在点击确认收货,以免造成损失." btnText="确认收货" :modalShow="modalShowConfirm" |
|
||||
@determine="confirmDetermine" @hideModal="modalShowConfirm=false" /> |
|
||||
</view> |
|
||||
</template> |
|
||||
|
|
||||
<script> |
|
||||
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js"; |
|
||||
import MescrollMoreItemMixin from "@/components/mescroll-uni/mixins/mescroll-more-item.js"; |
|
||||
export default { |
|
||||
mixins: [MescrollMixin, MescrollMoreItemMixin], // 注意此处还需使用MescrollMoreItemMixin (必须写在MescrollMixin后面) |
|
||||
data() { |
|
||||
return { |
|
||||
modalShowCancel: false, //取消订单 |
|
||||
modalShowDel: false, //删除订单 |
|
||||
modalShowConfirm: false, //确认收货 |
|
||||
|
|
||||
orderItem: {}, |
|
||||
|
|
||||
downOption: { |
|
||||
auto: false |
|
||||
}, |
|
||||
upOption: { |
|
||||
auto: false |
|
||||
}, |
|
||||
assetsFlow: [], |
|
||||
page: 1 |
|
||||
} |
|
||||
}, |
|
||||
props: { |
|
||||
i: Number, // 每个tab页的专属下标 (除了支付宝小程序必须在这里定义, 其他平台都可不用写, 因为已在MescrollMoreItemMixin定义) |
|
||||
index: { // 当前tab的下标 (除了支付宝小程序必须在这里定义, 其他平台都可不用写, 因为已在MescrollMoreItemMixin定义) |
|
||||
type: Number, |
|
||||
default () { |
|
||||
return 0 |
|
||||
} |
|
||||
}, |
|
||||
tabs: { // 为了请求数据,演示用,可根据自己的项目判断是否要传 |
|
||||
type: Array, |
|
||||
default () { |
|
||||
return [] |
|
||||
} |
|
||||
} |
|
||||
}, |
|
||||
methods: { |
|
||||
goDetails(item) { |
|
||||
if(item.state==1) { |
|
||||
this.$routerGo('/pages/order/unpay-details?orderid='+item.id) |
|
||||
}else { |
|
||||
this.$routerGo('/pages/order/order-details?orderid='+item.id) |
|
||||
} |
|
||||
|
|
||||
}, |
|
||||
getUserOrder(pagenum) { |
|
||||
this.$http(this.API.API_USERORDER, {state: this.tabs[this.index].type,page: pagenum,per_page: 20}).then(res => { |
|
||||
if(res.code == 0) { |
|
||||
if(pagenum == 1) this.assetsFlow = [] |
|
||||
this.mescroll.endSuccess(res.data.items.length, res.data.has_more_page); |
|
||||
|
|
||||
this.assetsFlow=this.assetsFlow.concat(res.data.items); |
|
||||
|
|
||||
console.log(this.assetsFlow) |
|
||||
} |
|
||||
|
|
||||
}).catch(err => { |
|
||||
this.mescroll.endErr(); |
|
||||
}); |
|
||||
}, |
|
||||
orderClick(item, type) { |
|
||||
this.orderItem = item |
|
||||
this[type] = true |
|
||||
}, |
|
||||
|
|
||||
//取消订单 |
|
||||
cancelDetermine() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
//删除订单 |
|
||||
delDetermine() { |
|
||||
|
|
||||
}, |
|
||||
|
|
||||
//确认收货 |
|
||||
confirmDetermine() { |
|
||||
this.$toast('已成功收货') |
|
||||
}, |
|
||||
|
|
||||
/*下拉刷新的回调 */ |
|
||||
downCallback() { |
|
||||
// 下拉刷新的回调,默认重置上拉加载列表为第一页 (自动执行 page.num=1, 再触发upCallback方法 ) |
|
||||
this.mescroll.resetUpScroll() |
|
||||
}, |
|
||||
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */ |
|
||||
upCallback(page) { |
|
||||
this.getUserOrder(page.num) |
|
||||
} |
|
||||
}, |
|
||||
created() { |
|
||||
this.getUserOrder() |
|
||||
} |
|
||||
} |
|
||||
</script> |
|
||||
|
|
||||
<style lang="scss" scoped> |
|
||||
.bref-box { |
|
||||
text-overflow: -o-ellipsis-lastline; |
|
||||
overflow: hidden; |
|
||||
text-overflow: ellipsis; |
|
||||
display: -webkit-box; |
|
||||
-webkit-line-clamp: 2; |
|
||||
-webkit-box-orient: vertical; |
|
||||
} |
|
||||
|
|
||||
.order-bottom { |
|
||||
position: relative; |
|
||||
|
|
||||
.order-ico { |
|
||||
position: absolute; |
|
||||
right: 50rpx; |
|
||||
top: -19rpx; |
|
||||
color: rgba(0, 0, 0, 0.1) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
</style> |
|
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue