From c08da91d54e1cf7e4bb9bcd46e9def89eb51b334 Mon Sep 17 00:00:00 2001 From: xpz2018 <107107461@qq.com> Date: Tue, 1 Jun 2021 09:08:23 +0800 Subject: [PATCH] no message --- components/animation-group/index.js | 383 ++++++++++++++++++++++ components/animation-group/index.json | 3 + components/animation-group/index.wxml | 5 + components/animation-group/index.wxss | 206 ++++++++++++ components/backdrop/index.js | 62 ++++ components/backdrop/index.json | 6 + components/backdrop/index.wxml | 1 + components/backdrop/index.wxss | 15 + components/helpers/arrayTreeFilter.js | 24 ++ components/helpers/baseComponent.js | 77 +++++ components/helpers/checkIPhoneX.js | 29 ++ components/helpers/classNames.js | 39 +++ components/helpers/colors.js | 27 ++ components/helpers/compareVersion.js | 27 ++ components/helpers/computedBehavior.js | 48 +++ components/helpers/createFieldsStore.js | 75 +++++ components/helpers/debounce.js | 56 ++++ components/helpers/eventsMixin.js | 53 +++ components/helpers/funcBehavior.js | 97 ++++++ components/helpers/gestures.js | 50 +++ components/helpers/isEmpty.js | 19 ++ components/helpers/mergeOptionsToData.js | 17 + components/helpers/relationsBehavior.js | 67 ++++ components/helpers/safeAreaBehavior.js | 46 +++ components/helpers/safeSetDataBehavior.js | 57 ++++ components/helpers/shallowEqual.js | 65 ++++ components/helpers/styleToCssString.js | 138 ++++++++ components/popover/index.js | 234 +++++++++++++ components/popover/index.json | 7 + components/popover/index.wxml | 17 + components/popover/index.wxss | 193 +++++++++++ pages/storage/order-info/index.js | 2 +- 32 files changed, 2144 insertions(+), 1 deletion(-) create mode 100644 components/animation-group/index.js create mode 100644 components/animation-group/index.json create mode 100644 components/animation-group/index.wxml create mode 100644 components/animation-group/index.wxss create mode 100644 components/backdrop/index.js create mode 100644 components/backdrop/index.json create mode 100644 components/backdrop/index.wxml create mode 100644 components/backdrop/index.wxss create mode 100644 components/helpers/arrayTreeFilter.js create mode 100644 components/helpers/baseComponent.js create mode 100644 components/helpers/checkIPhoneX.js create mode 100644 components/helpers/classNames.js create mode 100644 components/helpers/colors.js create mode 100644 components/helpers/compareVersion.js create mode 100644 components/helpers/computedBehavior.js create mode 100644 components/helpers/createFieldsStore.js create mode 100644 components/helpers/debounce.js create mode 100644 components/helpers/eventsMixin.js create mode 100644 components/helpers/funcBehavior.js create mode 100644 components/helpers/gestures.js create mode 100644 components/helpers/isEmpty.js create mode 100644 components/helpers/mergeOptionsToData.js create mode 100644 components/helpers/relationsBehavior.js create mode 100644 components/helpers/safeAreaBehavior.js create mode 100644 components/helpers/safeSetDataBehavior.js create mode 100644 components/helpers/shallowEqual.js create mode 100644 components/helpers/styleToCssString.js create mode 100644 components/popover/index.js create mode 100644 components/popover/index.json create mode 100644 components/popover/index.wxml create mode 100644 components/popover/index.wxss diff --git a/components/animation-group/index.js b/components/animation-group/index.js new file mode 100644 index 0000000..bbfdc01 --- /dev/null +++ b/components/animation-group/index.js @@ -0,0 +1,383 @@ +import baseComponent from '../helpers/baseComponent' +import styleToCssString from '../helpers/styleToCssString' + +const ENTER = 'enter' +const ENTERING = 'entering' +const ENTERED = 'entered' +const EXIT = 'exit' +const EXITING = 'exiting' +const EXITED = 'exited' +const UNMOUNTED = 'unmounted' + +const TRANSITION = 'transition' +const ANIMATION = 'animation' + +const TIMEOUT = 1000 / 60 + +const defaultClassNames = { + enter: '', // 进入过渡的开始状态,在过渡过程完成之后移除 + enterActive: '', // 进入过渡的结束状态,在过渡过程完成之后移除 + enterDone: '', // 进入过渡的完成状态 + exit: '', // 离开过渡的开始状态,在过渡过程完成之后移除 + exitActive: '', // 离开过渡的结束状态,在过渡过程完成之后移除 + exitDone: '', // 离开过渡的完成状态 +} + +baseComponent({ + properties: { + // 触发组件进入或离开过渡的状态 + in: { + type: Boolean, + value: false, + observer(newVal) { + if (this.data.isMounting) { + this.updated(newVal) + } + }, + }, + // 过渡的类名 + classNames: { + type: null, + value: defaultClassNames, + }, + // 过渡持续时间 + duration: { + type: null, + value: null, + }, + // 过渡动效的类型 + type: { + type: String, + value: TRANSITION, + }, + // 首次挂载时是否触发进入过渡 + appear: { + type: Boolean, + value: false, + }, + // 是否启用进入过渡 + enter: { + type: Boolean, + value: true, + }, + // 是否启用离开过渡 + exit: { + type: Boolean, + value: true, + }, + // 首次进入过渡时是否懒挂载组件 + mountOnEnter: { + type: Boolean, + value: true, + }, + // 离开过渡完成时是否卸载组件 + unmountOnExit: { + type: Boolean, + value: true, + }, + // 自定义类名 + wrapCls: { + type: String, + value: '', + }, + // 自定义样式 + wrapStyle: { + type: [String, Object], + value: '', + observer(newVal) { + this.setData({ + extStyle: styleToCssString(newVal), + }) + }, + }, + disableScroll: { + type: Boolean, + value: false, + }, + }, + data: { + animateCss: '', // 动画样式 + animateStatus: EXITED, // 动画状态,可选值 entering、entered、exiting、exited + isMounting: false, // 是否首次挂载 + extStyle: '', // 组件样式 + }, + methods: { + /** + * 监听过渡或动画的回调函数 + */ + addEventListener() { + const { animateStatus } = this.data + const { enter, exit } = this.getTimeouts() + + if (animateStatus === ENTERING && !enter && this.data.enter) { + this.performEntered() + } + + if (animateStatus === EXITING && !exit && this.data.exit) { + this.performExited() + } + }, + /** + * 会在 WXSS transition 或 wx.createAnimation 动画结束后触发 + */ + onTransitionEnd() { + if (this.data.type === TRANSITION) { + this.addEventListener() + } + }, + /** + * 会在一个 WXSS animation 动画完成时触发 + */ + onAnimationEnd() { + if (this.data.type === ANIMATION) { + this.addEventListener() + } + }, + /** + * 更新组件状态 + * @param {String} nextStatus 下一状态,ENTERING 或 EXITING + * @param {Boolean} mounting 是否首次挂载 + */ + updateStatus(nextStatus, mounting = false) { + if (nextStatus !== null) { + this.cancelNextCallback() + this.isAppearing = mounting + + if (nextStatus === ENTERING) { + this.performEnter() + } else { + this.performExit() + } + } + }, + /** + * 进入过渡 + */ + performEnter() { + const { className, activeClassName } = this.getClassNames(ENTER) + const { enter } = this.getTimeouts() + const enterParams = { + animateStatus: ENTER, + animateCss: className, + } + const enteringParams = { + animateStatus: ENTERING, + animateCss: `${className} ${activeClassName}`, + } + + // 若已禁用进入过渡,则更新状态至 ENTERED + if (!this.isAppearing && !this.data.enter) { + return this.performEntered() + } + + // 第一阶段:设置进入过渡的开始状态,并触发 ENTER 事件 + // 第二阶段:延迟一帧后,设置进入过渡的结束状态,并触发 ENTERING 事件 + // 第三阶段:若已设置过渡的持续时间,则延迟指定时间后触发进入过渡完成 performEntered,否则等待触发 onTransitionEnd 或 onAnimationEnd + this.safeSetData(enterParams, () => { + this.triggerEvent('change', { animateStatus: ENTER }) + this.triggerEvent(ENTER, { isAppearing: this.isAppearing }) + + // 由于有些时候不能正确的触发动画完成的回调,具体原因未知 + // 所以采用延迟一帧的方式来确保可以触发回调 + this.delayHandler(TIMEOUT, () => { + this.safeSetData(enteringParams, () => { + this.triggerEvent('change', { animateStatus: ENTERING }) + this.triggerEvent(ENTERING, { isAppearing: this.isAppearing }) + + if (enter) { + this.delayHandler(enter, this.performEntered) + } + }) + }) + }) + }, + /** + * 进入过渡完成 + */ + performEntered() { + const { doneClassName } = this.getClassNames(ENTER) + const enteredParams = { + animateStatus: ENTERED, + animateCss: doneClassName, + } + + // 第三阶段:设置进入过渡的完成状态,并触发 ENTERED 事件 + this.safeSetData(enteredParams, () => { + this.triggerEvent('change', { animateStatus: ENTERED }) + this.triggerEvent(ENTERED, { isAppearing: this.isAppearing }) + }) + }, + /** + * 离开过渡 + */ + performExit() { + const { className, activeClassName } = this.getClassNames(EXIT) + const { exit } = this.getTimeouts() + const exitParams = { + animateStatus: EXIT, + animateCss: className, + } + const exitingParams = { + animateStatus: EXITING, + animateCss: `${className} ${activeClassName}`, + } + + // 若已禁用离开过渡,则更新状态至 EXITED + if (!this.data.exit) { + return this.performExited() + } + + // 第一阶段:设置离开过渡的开始状态,并触发 EXIT 事件 + // 第二阶段:延迟一帧后,设置离开过渡的结束状态,并触发 EXITING 事件 + // 第三阶段:若已设置过渡的持续时间,则延迟指定时间后触发离开过渡完成 performExited,否则等待触发 onTransitionEnd 或 onAnimationEnd + this.safeSetData(exitParams, () => { + this.triggerEvent('change', { animateStatus: EXIT }) + this.triggerEvent(EXIT) + + this.delayHandler(TIMEOUT, () => { + this.safeSetData(exitingParams, () => { + this.triggerEvent('change', { animateStatus: EXITING }) + this.triggerEvent(EXITING) + + if (exit) { + this.delayHandler(exit, this.performExited) + } + }) + }) + }) + }, + /** + * 离开过渡完成 + */ + performExited() { + const { doneClassName } = this.getClassNames(EXIT) + const exitedParams = { + animateStatus: EXITED, + animateCss: doneClassName, + } + + // 第三阶段:设置离开过渡的完成状态,并触发 EXITED 事件 + this.safeSetData(exitedParams, () => { + this.triggerEvent('change', { animateStatus: EXITED }) + this.triggerEvent(EXITED) + + // 判断离开过渡完成时是否卸载组件 + if (this.data.unmountOnExit) { + this.setData({ animateStatus: UNMOUNTED }, () => { + this.triggerEvent('change', { animateStatus: UNMOUNTED }) + }) + } + }) + }, + /** + * 获取指定状态下的类名 + * @param {String} type 过渡类型,enter 或 exit + */ + getClassNames(type) { + const { classNames } = this.data + const className = typeof classNames !== 'string' ? classNames[type] : `${classNames}-${type}` + const activeClassName = typeof classNames !== 'string' ? classNames[`${type}Active`] : `${classNames}-${type}-active` + const doneClassName = typeof classNames !== 'string' ? classNames[`${type}Done`] : `${classNames}-${type}-done` + + return { + className, + activeClassName, + doneClassName, + } + }, + /** + * 获取过渡持续时间 + */ + getTimeouts() { + const { duration } = this.data + + if (duration !== null && typeof duration === 'object') { + return { + enter: duration.enter, + exit: duration.exit, + } + } else if (typeof duration === 'number') { + return { + enter: duration, + exit: duration, + } + } + + return {} + }, + /** + * 属性值 in 被更改时的响应函数 + * @param {Boolean} newVal 触发组件进入或离开过渡的状态 + */ + updated(newVal) { + let { animateStatus } = this.pendingData || this.data + let nextStatus = null + + if (newVal) { + if (animateStatus === UNMOUNTED) { + animateStatus = EXITED + this.setData({ animateStatus: EXITED }, () => { + this.triggerEvent('change', { animateStatus: EXITED }) + }) + } + if (animateStatus !== ENTER && animateStatus !== ENTERING && animateStatus !== ENTERED) { + nextStatus = ENTERING + } + } else { + if (animateStatus === ENTER || animateStatus === ENTERING || animateStatus === ENTERED) { + nextStatus = EXITING + } + } + + this.updateStatus(nextStatus) + }, + /** + * 延迟一段时间触发回调 + * @param {Number} timeout 延迟时间 + * @param {Function} handler 回调函数 + */ + delayHandler(timeout, handler) { + if (timeout) { + this.setNextCallback(handler) + setTimeout(this.nextCallback, timeout) + } + }, + /** + * 点击事件 + */ + onTap() { + this.triggerEvent('click') + }, + /** + * 阻止移动触摸 + */ + noop() { + }, + }, + attached() { + let animateStatus = null + let appearStatus = null + + if (this.data.in) { + if (this.data.appear) { + animateStatus = EXITED + appearStatus = ENTERING + } else { + animateStatus = ENTERED + } + } else { + if (this.data.unmountOnExit || this.data.mountOnEnter) { + animateStatus = UNMOUNTED + } else { + animateStatus = EXITED + } + } + + // 由于小程序组件首次挂载时 observer 事件总是优先于 attached 事件 + // 所以使用 isMounting 来强制优先触发 attached 事件 + this.safeSetData({ animateStatus, isMounting: true }, () => { + this.triggerEvent('change', { animateStatus }) + this.updateStatus(appearStatus, true) + }) + }, +}) diff --git a/components/animation-group/index.json b/components/animation-group/index.json new file mode 100644 index 0000000..fba482a --- /dev/null +++ b/components/animation-group/index.json @@ -0,0 +1,3 @@ +{ + "component": true +} \ No newline at end of file diff --git a/components/animation-group/index.wxml b/components/animation-group/index.wxml new file mode 100644 index 0000000..7a68452 --- /dev/null +++ b/components/animation-group/index.wxml @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/components/animation-group/index.wxss b/components/animation-group/index.wxss new file mode 100644 index 0000000..ac89862 --- /dev/null +++ b/components/animation-group/index.wxss @@ -0,0 +1,206 @@ +.wux-animate--fadeIn-enter { + transition: opacity .3s; + opacity: 0 +} +.wux-animate--fadeIn-enter-active, +.wux-animate--fadeIn-enter-done { + opacity: 1 +} +.wux-animate--fadeIn-exit { + transition: opacity .3s; + opacity: 1 +} +.wux-animate--fadeIn-exit-active, +.wux-animate--fadeIn-exit-done { + opacity: 0 +} +.wux-animate--fadeInDown-enter { + transition: opacity .3s,transform .3s; + opacity: 0; + transform: translate3d(0,-100%,0) +} +.wux-animate--fadeInDown-enter-active, +.wux-animate--fadeInDown-enter-done { + opacity: 1; + transform: none +} +.wux-animate--fadeInDown-exit { + transition: opacity .3s,transform .3s; + opacity: 1; + transform: none +} +.wux-animate--fadeInDown-exit-active, +.wux-animate--fadeInDown-exit-done { + opacity: 0; + transform: translate3d(0,-100%,0) +} +.wux-animate--fadeInLeft-enter { + transition: opacity .3s,transform .3s; + opacity: 0; + transform: translate3d(-100%,0,0) +} +.wux-animate--fadeInLeft-enter-active, +.wux-animate--fadeInLeft-enter-done { + opacity: 1; + transform: none +} +.wux-animate--fadeInLeft-exit { + transition: opacity .3s,transform .3s; + opacity: 1; + transform: none +} +.wux-animate--fadeInLeft-exit-active, +.wux-animate--fadeInLeft-exit-done { + opacity: 0; + transform: translate3d(-100%,0,0) +} +.wux-animate--fadeInRight-enter { + transition: opacity .3s,transform .3s; + opacity: 0; + transform: translate3d(100%,0,0) +} +.wux-animate--fadeInRight-enter-active, +.wux-animate--fadeInRight-enter-done { + opacity: 1; + transform: none +} +.wux-animate--fadeInRight-exit { + transition: opacity .3s,transform .3s; + opacity: 1; + transform: none +} +.wux-animate--fadeInRight-exit-active, +.wux-animate--fadeInRight-exit-done { + opacity: 0; + transform: translate3d(100%,0,0) +} +.wux-animate--fadeInUp-enter { + transition: opacity .3s,transform .3s; + opacity: 0; + transform: translate3d(0,100%,0) +} +.wux-animate--fadeInUp-enter-active, +.wux-animate--fadeInUp-enter-done { + opacity: 1; + transform: none +} +.wux-animate--fadeInUp-exit { + transition: opacity .3s,transform .3s; + opacity: 1; + transform: none +} +.wux-animate--fadeInUp-exit-active, +.wux-animate--fadeInUp-exit-done { + opacity: 0; + transform: translate3d(0,100%,0) +} +.wux-animate--slideInUp-enter { + transition: transform .3s; + transform: translate3d(0,100%,0); + visibility: visible +} +.wux-animate--slideInUp-enter-active, +.wux-animate--slideInUp-enter-done { + transform: translateZ(0) +} +.wux-animate--slideInUp-exit { + transition: transform .3s; + transform: translateZ(0) +} +.wux-animate--slideInUp-exit-active, +.wux-animate--slideInUp-exit-done { + transform: translate3d(0,100%,0); + visibility: visible +} +.wux-animate--slideInDown-enter { + transition: transform .3s; + transform: translate3d(0,-100%,0); + visibility: visible +} +.wux-animate--slideInDown-enter-active, +.wux-animate--slideInDown-enter-done { + transform: translateZ(0) +} +.wux-animate--slideInDown-exit { + transition: transform .3s; + transform: translateZ(0) +} +.wux-animate--slideInDown-exit-active, +.wux-animate--slideInDown-exit-done { + transform: translate3d(0,-100%,0); + visibility: visible +} +.wux-animate--slideInLeft-enter { + transition: transform .3s; + transform: translate3d(-100%,0,0); + visibility: visible +} +.wux-animate--slideInLeft-enter-active, +.wux-animate--slideInLeft-enter-done { + transform: translateZ(0) +} +.wux-animate--slideInLeft-exit { + transition: transform .3s; + transform: translateZ(0) +} +.wux-animate--slideInLeft-exit-active, +.wux-animate--slideInLeft-exit-done { + transform: translate3d(-100%,0,0); + visibility: visible +} +.wux-animate--slideInRight-enter { + transition: transform .3s; + transform: translate3d(100%,0,0); + visibility: visible +} +.wux-animate--slideInRight-enter-active, +.wux-animate--slideInRight-enter-done { + transform: none +} +.wux-animate--slideInRight-exit { + transition: transform .3s; + transform: none +} +.wux-animate--slideInRight-exit-active, +.wux-animate--slideInRight-exit-done { + transform: translate3d(100%,0,0); + visibility: visible +} +.wux-animate--zoom-enter { + transition: all .3s cubic-bezier(.215,.61,.355,1); + opacity: .01; + transform: scale(.75) +} +.wux-animate--zoom-enter-active, +.wux-animate--zoom-enter-done { + opacity: 1; + transform: none +} +.wux-animate--zoom-exit { + transition: all .25s linear; + transform: none +} +.wux-animate--zoom-exit-active, +.wux-animate--zoom-exit-done { + opacity: .01; + transform: scale(.75) +} +.wux-animate--punch-enter { + transition: all .3s cubic-bezier(.215,.61,.355,1); + opacity: .01; + transform: scale(1.35) +} +.wux-animate--punch-enter-active, +.wux-animate--punch-enter-done { + opacity: 1; + transform: none +} +.wux-animate--punch-exit { + transition: all .25s linear; + transform: none +} +.wux-animate--punch-exit-active, +.wux-animate--punch-exit-done { + opacity: .01; + transform: scale(1.35) +} \ No newline at end of file diff --git a/components/backdrop/index.js b/components/backdrop/index.js new file mode 100644 index 0000000..cf43b99 --- /dev/null +++ b/components/backdrop/index.js @@ -0,0 +1,62 @@ +import baseComponent from '../helpers/baseComponent' + +baseComponent({ + properties: { + prefixCls: { + type: String, + value: 'wux-backdrop', + }, + transparent: { + type: Boolean, + value: false, + }, + zIndex: { + type: Number, + value: 1000, + }, + classNames: { + type: null, + value: 'wux-animate--fadeIn', + }, + }, + computed: { + classes: ['prefixCls, transparent', function(prefixCls, transparent) { + const wrap = transparent ? `${prefixCls}--transparent` : prefixCls + + return { + wrap, + } + }], + }, + methods: { + /** + * 保持锁定 + */ + retain() { + if (typeof this.backdropHolds !== 'number' || !this.backdropHolds) { + this.backdropHolds = 0 + } + + this.backdropHolds = this.backdropHolds + 1 + + if (this.backdropHolds === 1) { + this.setData({ in: true }) + } + }, + /** + * 释放锁定 + */ + release() { + if (this.backdropHolds === 1) { + this.setData({ in: false }) + } + this.backdropHolds = Math.max(0, this.backdropHolds - 1) + }, + /** + * 点击事件 + */ + onClick() { + this.triggerEvent('click') + }, + }, +}) diff --git a/components/backdrop/index.json b/components/backdrop/index.json new file mode 100644 index 0000000..9b9e7ab --- /dev/null +++ b/components/backdrop/index.json @@ -0,0 +1,6 @@ +{ + "component": true, + "usingComponents": { + "wux-animation-group": "../animation-group/index" + } +} \ No newline at end of file diff --git a/components/backdrop/index.wxml b/components/backdrop/index.wxml new file mode 100644 index 0000000..d6ee398 --- /dev/null +++ b/components/backdrop/index.wxml @@ -0,0 +1 @@ + diff --git a/components/backdrop/index.wxss b/components/backdrop/index.wxss new file mode 100644 index 0000000..be44d1b --- /dev/null +++ b/components/backdrop/index.wxss @@ -0,0 +1,15 @@ +.wux-backdrop { + background: rgba(0,0,0,.4) +} +.wux-backdrop, +.wux-backdrop--transparent { + position: fixed; + z-index: 1000; + top: 0; + right: 0; + left: 0; + bottom: 0 +} +.wux-backdrop--transparent { + background: 0 0 +} \ No newline at end of file diff --git a/components/helpers/arrayTreeFilter.js b/components/helpers/arrayTreeFilter.js new file mode 100644 index 0000000..94736eb --- /dev/null +++ b/components/helpers/arrayTreeFilter.js @@ -0,0 +1,24 @@ +/** + * https://github.com/afc163/array-tree-filter + */ +function arrayTreeFilter(data, filterFn, options) { + options = options || {} + options.childrenKeyName = options.childrenKeyName || 'children' + let children = data || [] + const result = [] + let level = 0 + do { + const foundItem = children.filter(function(item) { + return filterFn(item, level) + })[0] + if (!foundItem) { + break + } + result.push(foundItem) + children = foundItem[options.childrenKeyName] || [] + level += 1 + } while (children.length > 0) + return result +} + +export default arrayTreeFilter \ No newline at end of file diff --git a/components/helpers/baseComponent.js b/components/helpers/baseComponent.js new file mode 100644 index 0000000..7c5d66d --- /dev/null +++ b/components/helpers/baseComponent.js @@ -0,0 +1,77 @@ +import computedBehavior from './computedBehavior' +import relationsBehavior from './relationsBehavior' +import safeAreaBehavior from './safeAreaBehavior' +import safeSetDataBehavior from './safeSetDataBehavior' +import funcBehavior from './funcBehavior' +import compareVersion from './compareVersion' + +const { platform, SDKVersion } = wx.getSystemInfoSync() +const libVersion = '2.6.6' + +// check SDKVersion +if (platform === 'devtools' && compareVersion(SDKVersion, libVersion) < 0) { + if (wx && wx.showModal) { + wx.showModal({ + title: '提示', + content: `当前基础库版本(${SDKVersion})过低,无法使用 Wux Weapp 组件库,请更新基础库版本 >=${libVersion} 后重试。`, + }) + } +} + +const baseComponent = (options = {}) => { + // add default externalClasses + options.externalClasses = [ + 'wux-class', + 'wux-hover-class', + ...(options.externalClasses = options.externalClasses || []), + ] + + // add default behaviors + options.behaviors = [ + relationsBehavior, + safeSetDataBehavior, + ...(options.behaviors = options.behaviors || []), + computedBehavior, // make sure it's triggered + ] + + // use safeArea + if (options.useSafeArea) { + options.behaviors = [...options.behaviors, safeAreaBehavior] + delete options.useSafeArea + } + + // use func + if (options.useFunc) { + options.behaviors = [...options.behaviors, funcBehavior] + delete options.useFunc + } + + // use field + if (options.useField) { + options.behaviors = [...options.behaviors, 'wx://form-field'] + delete options.useField + } + + // use export + if (options.useExport) { + options.behaviors = [...options.behaviors, 'wx://component-export'] + options.methods = { + export () { + return this + }, + ...options.methods, + } + delete options.useExport + } + + // add default options + options.options = { + multipleSlots: true, + addGlobalClass: true, + ...options.options, + } + + return Component(options) +} + +export default baseComponent diff --git a/components/helpers/checkIPhoneX.js b/components/helpers/checkIPhoneX.js new file mode 100644 index 0000000..5d65c52 --- /dev/null +++ b/components/helpers/checkIPhoneX.js @@ -0,0 +1,29 @@ +/** + * 获取系统信息 + */ + +let systemInfo = null + +export const getSystemInfo = (isForce) => { + if (!systemInfo || isForce) { + try { + systemInfo = wx.getSystemInfoSync() + } catch(e) { /* Ignore */ } + } + + return systemInfo +} + +// iPhoneX 竖屏安全区域 +export const safeAreaInset = { + top: 88, // StatusBar & NavBar + left: 0, + right: 0, + bottom: 34, // Home Indicator +} + +const isIPhoneX = ({ model, platform }) => { + return /iPhone X/.test(model) && platform === 'ios' +} + +export const checkIPhoneX = (isForce) => isIPhoneX(getSystemInfo(isForce)) diff --git a/components/helpers/classNames.js b/components/helpers/classNames.js new file mode 100644 index 0000000..e2eb2d4 --- /dev/null +++ b/components/helpers/classNames.js @@ -0,0 +1,39 @@ +/*! + Copyright (c) 2018 Jed Watson. + Licensed under the MIT License (MIT), see + http://jedwatson.github.io/classnames +*/ +/* global define */ +'use strict'; + +var hasOwn = {}.hasOwnProperty; + +function classNames() { + var classes = []; + + for (var i = 0; i < arguments.length; i++) { + var arg = arguments[i]; + if (!arg) continue; + + var argType = typeof arg; + + if (argType === 'string' || argType === 'number') { + classes.push(arg); + } else if (Array.isArray(arg) && arg.length) { + var inner = classNames.apply(null, arg); + if (inner) { + classes.push(inner); + } + } else if (argType === 'object') { + for (var key in arg) { + if (hasOwn.call(arg, key) && arg[key]) { + classes.push(key); + } + } + } + } + + return classes.join(' '); +} + +export default classNames \ No newline at end of file diff --git a/components/helpers/colors.js b/components/helpers/colors.js new file mode 100644 index 0000000..fc4b9d8 --- /dev/null +++ b/components/helpers/colors.js @@ -0,0 +1,27 @@ +/** + * Don't modify this file! + * Colors generated by themes! + */ + +/* eslint-disable */ + +export const colors = { + 'light': '#ddd', + 'stable': '#b2b2b2', + 'positive': '#387ef5', + 'calm': '#11c1f3', + 'balanced': '#33cd5f', + 'energized': '#ffc900', + 'assertive': '#ef473a', + 'royal': '#886aea', + 'dark': '#444', +} + +export const isPresetColor = (color) => { + if (!color) { + return false + } + return colors[color] ? colors[color] : color +} + +/* eslint-enable */ diff --git a/components/helpers/compareVersion.js b/components/helpers/compareVersion.js new file mode 100644 index 0000000..47a2d59 --- /dev/null +++ b/components/helpers/compareVersion.js @@ -0,0 +1,27 @@ +function compareVersion(v1, v2) { + const $v1 = v1.split('.') + const $v2 = v2.split('.') + const len = Math.max($v1.length, $v2.length) + + while ($v1.length < len) { + $v1.push('0') + } + while ($v2.length < len) { + $v2.push('0') + } + + for (let i = 0; i < len; i++) { + const num1 = parseInt($v1[i]) + const num2 = parseInt($v2[i]) + + if (num1 > num2) { + return 1 + } else if (num1 < num2) { + return -1 + } + } + + return 0 +} + +export default compareVersion diff --git a/components/helpers/computedBehavior.js b/components/helpers/computedBehavior.js new file mode 100644 index 0000000..8650193 --- /dev/null +++ b/components/helpers/computedBehavior.js @@ -0,0 +1,48 @@ +import isEmpty from './isEmpty' +import shallowEqual from './shallowEqual' + +const ALL_DATA_KEY = '**' + +const trim = (str = '') => str.replace(/\s/g, '') + +export default Behavior({ + lifetimes: { + attached() { + this.initComputed() + }, + }, + definitionFilter(defFields) { + const { computed = {} } = defFields + const observers = Object.keys(computed).reduce((acc, name) => { + const [field, getter] = Array.isArray(computed[name]) ? computed[name] : [ALL_DATA_KEY, computed[name]] + return { + ...acc, + [field]: function(...args) { + if (typeof getter === 'function') { + const newValue = getter.apply(this, args) + const oldValue = this.data[name] + if (!isEmpty(newValue) && !shallowEqual(newValue, oldValue)) { + this.setData({ [name]: newValue }) + } + } + }, + } + }, {}) + + Object.assign(defFields.observers = (defFields.observers || {}), observers) + Object.assign(defFields.methods = (defFields.methods || {}), { + initComputed: function(data = {}, isForce = false) { + if (!this.runInitComputed || isForce) { + this.runInitComputed = false + const context = this + const result = { ...this.data, ...data } + Object.keys(observers).forEach((key) => { + const values = trim(key).split(',').reduce((acc, name) => ([...acc, result[name]]), []) + observers[key].apply(context, values) + }) + this.runInitComputed = true + } + }, + }) + }, +}) diff --git a/components/helpers/createFieldsStore.js b/components/helpers/createFieldsStore.js new file mode 100644 index 0000000..9dbb3ee --- /dev/null +++ b/components/helpers/createFieldsStore.js @@ -0,0 +1,75 @@ +class FieldsStore { + constructor(fields = {}) { + this.fields = fields + } + + setFields(fields) { + Object.assign(this.fields, fields) + } + + updateFields(fields) { + this.fields = fields + } + + clearField(name) { + delete this.fields[name] + } + + getValueFromFields(name, fields) { + const field = fields[name] + if (field && 'value' in field) { + return field.value + } + return field.initialValue + } + + getAllFieldsName() { + const { fields } = this + return fields ? Object.keys(fields) : [] + } + + getField(name) { + return { + ...this.fields[name], + name, + } + } + + getFieldValuePropValue(fieldOption) { + const { name, valuePropName } = fieldOption + const field = this.getField(name) + const fieldValue = 'value' in field ? field.value : field.initialValue + + return { + [valuePropName]: fieldValue, + } + } + + getFieldValue(name) { + return this.getValueFromFields(name, this.fields) + } + + getFieldsValue(names) { + const fields = names || this.getAllFieldsName() + return fields.reduce((acc, name) => { + acc[name] = this.getFieldValue(name) + return acc + }, {}) + } + + resetFields(ns) { + const { fields } = this + const names = ns || this.getAllFieldsName() + return names.reduce((acc, name) => { + const field = fields[name] + if (field) { + acc[name] = field.initialValue + } + return acc + }, {}) + } +} + +export default function createFieldsStore(fields) { + return new FieldsStore(fields) +} diff --git a/components/helpers/debounce.js b/components/helpers/debounce.js new file mode 100644 index 0000000..207e486 --- /dev/null +++ b/components/helpers/debounce.js @@ -0,0 +1,56 @@ +export default function debounce(func, wait, immediate) { + let timeout, + args, + context, + timestamp, + result + + function later() { + const last = +(new Date()) - timestamp + if (last < wait && last >= 0) { + timeout = setTimeout(later, wait - last) + } else { + timeout = undefined + if (!immediate) { + result = func.apply(context, args) + if (!timeout) { + context = undefined + args = undefined + } + } + } + } + + function debounced() { + context = this + args = arguments + timestamp = +(new Date()) + + const callNow = immediate && !timeout + if (!timeout) { + timeout = setTimeout(later, wait) + } + + if (callNow) { + result = func.apply(context, args) + context = undefined + args = undefined + } + + return result + } + + function cancel() { + if (timeout !== undefined) { + clearTimeout(timeout) + timeout = undefined + } + + context = undefined + args = undefined + } + + debounced.cancel = cancel + + return debounced +} \ No newline at end of file diff --git a/components/helpers/eventsMixin.js b/components/helpers/eventsMixin.js new file mode 100644 index 0000000..f690335 --- /dev/null +++ b/components/helpers/eventsMixin.js @@ -0,0 +1,53 @@ +const defaultEvents = { + onChange() {}, +} + +export default function eventsMixin(params = { defaultEvents }) { + return Behavior({ + lifetimes: { + created () { + this._oriTriggerEvent = this.triggerEvent + this.triggerEvent = this._triggerEvent + }, + }, + properties: { + events: { + type: Object, + value: defaultEvents, + }, + }, + data: { + inputEvents: defaultEvents, + }, + definitionFilter(defFields) { + // set default data + Object.assign(defFields.data = (defFields.data || {}), { + inputEvents: Object.assign({}, defaultEvents, defFields.inputEvents), + }) + + // set default methods + Object.assign(defFields.methods = (defFields.methods || {}), { + _triggerEvent(name, params, runCallbacks = true, option) { + const { inputEvents } = this.data + const method = `on${name[0].toUpperCase()}${name.slice(1)}` + const func = inputEvents[method] + + if (runCallbacks && typeof func === 'function') { + func.call(this, params) + } + + this._oriTriggerEvent(name, params, option) + }, + }) + + // set default observers + Object.assign(defFields.observers = (defFields.observers || {}), { + events(newVal) { + this.setData({ + inputEvents: Object.assign({}, defaultEvents, this.data.inputEvents, newVal), + }) + }, + }) + }, + }) +} diff --git a/components/helpers/funcBehavior.js b/components/helpers/funcBehavior.js new file mode 100644 index 0000000..948075c --- /dev/null +++ b/components/helpers/funcBehavior.js @@ -0,0 +1,97 @@ +/** + * 过滤对象的函数属性 + * @param {Object} opts + */ +const mergeOptionsToData = (opts = {}) => { + const options = Object.assign({}, opts) + + for (const key in options) { + if (options.hasOwnProperty(key) && typeof options[key] === 'function') { + delete options[key] + } + } + + return options +} + +/** + * Simple bind, faster than native + * + * @param {Function} fn + * @param {Object} ctx + * @return {Function} + */ +const bind = (fn, ctx) => { + return (...args) => { + return args.length ? fn.apply(ctx, args) : fn.call(ctx) + } +} + +/** + * Object assign + */ +const assign = (...args) => Object.assign({}, ...args) + +export default Behavior({ + definitionFilter(defFields) { + defFields.data = mergeOptionsToData(defFields.data) + defFields.data.in = false + defFields.data.visible = false + }, + methods: { + /** + * 过滤对象的函数属性 + * @param {Object} opts + */ + $$mergeOptionsToData: mergeOptionsToData, + /** + * 合并参数并绑定方法 + * + * @param {Object} opts 参数对象 + * @param {Object} fns 方法挂载的属性 + */ + $$mergeOptionsAndBindMethods (opts = {}, fns = this.fns) { + const options = Object.assign({}, opts) + + for (const key in options) { + if (options.hasOwnProperty(key) && typeof options[key] === 'function') { + fns[key] = bind(options[key], this) + delete options[key] + } + } + + return options + }, + /** + * Promise setData + * @param {Array} args 参数对象 + */ + $$setData (...args) { + const params = assign({}, ...args) + + return new Promise((resolve) => { + this.setData(params, resolve) + }) + }, + /** + * 延迟指定时间执行回调函数 + * @param {Function} callback 回调函数 + * @param {Number} timeout 延迟时间 + */ + $$requestAnimationFrame (callback = () => {}, timeout = 1000 / 60) { + return new Promise((resolve) => setTimeout(resolve, timeout)).then(callback) + }, + }, + /** + * 组件生命周期函数,在组件实例进入页面节点树时执行 + */ + created () { + this.fns = {} + }, + /** + * 组件生命周期函数,在组件实例被从页面节点树移除时执行 + */ + detached () { + this.fns = {} + }, +}) diff --git a/components/helpers/gestures.js b/components/helpers/gestures.js new file mode 100644 index 0000000..99a4a40 --- /dev/null +++ b/components/helpers/gestures.js @@ -0,0 +1,50 @@ +/** + * 获取触摸点位置信息 + */ +export const getTouchPoints = (nativeEvent, index = 0) => { + const touches = nativeEvent.touches + const changedTouches = nativeEvent.changedTouches + const hasTouches = touches && touches.length > 0 + const hasChangedTouches = changedTouches && changedTouches.length > 0 + const points = !hasTouches && hasChangedTouches ? changedTouches[index] : hasTouches ? touches[index] : nativeEvent + + return { + x: points.pageX, + y: points.pageY, + } +} + +/** + * 获取触摸点个数 + */ +export const getPointsNumber = (e) => e.touches && e.touches.length || e.changedTouches && e.changedTouches.length + +/** + * 判断是否为同一点 + */ +export const isEqualPoints = (p1, p2) => p1.x === p2.x && p1.y === p2.y + +/** + * 判断是否为相近的两点 + */ +export const isNearbyPoints = (p1, p2, DOUBLE_TAP_RADIUS = 25) => { + const xMove = Math.abs(p1.x - p2.x) + const yMove = Math.abs(p1.y - p2.y) + return xMove < DOUBLE_TAP_RADIUS & yMove < DOUBLE_TAP_RADIUS +} + +/** + * 获取两点之间的距离 + */ +export const getPointsDistance = (p1, p2) => { + const xMove = Math.abs(p1.x - p2.x) + const yMove = Math.abs(p1.y - p2.y) + return Math.sqrt(xMove * xMove + yMove * yMove) +} + +/** + * 获取触摸移动方向 + */ +export const getSwipeDirection = (x1, x2, y1, y2) => { + return Math.abs(x1 - x2) >= Math.abs(y1 - y2) ? (x1 - x2 > 0 ? 'Left' : 'Right') : (y1 - y2 > 0 ? 'Up' : 'Down') +} diff --git a/components/helpers/isEmpty.js b/components/helpers/isEmpty.js new file mode 100644 index 0000000..56fc4f8 --- /dev/null +++ b/components/helpers/isEmpty.js @@ -0,0 +1,19 @@ +/** + * Checks if a value is empty. + */ +function isEmpty(value) { + if (Array.isArray(value)) { + return value.length === 0 + } else if (typeof value === 'object') { + if (value) { + for (const _ in value) { + return false + } + } + return true + } else { + return !value + } +} + +export default isEmpty \ No newline at end of file diff --git a/components/helpers/mergeOptionsToData.js b/components/helpers/mergeOptionsToData.js new file mode 100644 index 0000000..47f5acc --- /dev/null +++ b/components/helpers/mergeOptionsToData.js @@ -0,0 +1,17 @@ +/** + * 过滤对象的函数属性 + * @param {Object} opts + */ +const mergeOptionsToData = (opts = {}) => { + const options = Object.assign({}, opts) + + for (const key in options) { + if (options.hasOwnProperty(key) && typeof options[key] === 'function') { + delete options[key] + } + } + + return options +} + +export default mergeOptionsToData diff --git a/components/helpers/relationsBehavior.js b/components/helpers/relationsBehavior.js new file mode 100644 index 0000000..2f05c35 --- /dev/null +++ b/components/helpers/relationsBehavior.js @@ -0,0 +1,67 @@ +import isEmpty from './isEmpty' +import debounce from './debounce' + +/** + * bind func to obj + */ +function bindFunc(obj, method, observer) { + const oldFn = obj[method] + obj[method] = function(target) { + if (observer) { + observer.call(this, target, { + [method]: true, + }) + } + if (oldFn) { + oldFn.call(this, target) + } + } +} + +// default methods +const methods = ['linked', 'linkChanged', 'unlinked'] + +// extra props +const extProps = ['observer'] + +export default Behavior({ + lifetimes: { + created() { + this._debounce = null + }, + detached() { + if (this._debounce && this._debounce.cancel) { + this._debounce.cancel() + } + }, + }, + definitionFilter(defFields) { + const { relations } = defFields + + if (!isEmpty(relations)) { + for (const key in relations) { + const relation = relations[key] + + // bind func + methods.forEach((method) => bindFunc(relation, method, relation.observer)) + + // delete extProps + extProps.forEach((prop) => delete relation[prop]) + } + } + + Object.assign(defFields.methods = (defFields.methods || {}), { + getRelationsName: function(types = ['parent', 'child', 'ancestor', 'descendant']) { + return Object.keys(relations || {}).map((key) => { + if (relations[key] && types.includes(relations[key].type)) { + return key + } + return null + }).filter((v) => !!v) + }, + debounce: function(func, wait = 0, immediate = false) { + return (this._debounce = this._debounce || debounce(func.bind(this), wait, immediate)).call(this) + }, + }) + }, +}) diff --git a/components/helpers/safeAreaBehavior.js b/components/helpers/safeAreaBehavior.js new file mode 100644 index 0000000..c81fad0 --- /dev/null +++ b/components/helpers/safeAreaBehavior.js @@ -0,0 +1,46 @@ +import { getSystemInfo, checkIPhoneX } from './checkIPhoneX' + +const defaultSafeArea = { + top: false, + bottom: false, +} + +const setSafeArea = (params) => { + if (typeof params === 'boolean') { + return Object.assign({}, defaultSafeArea, { + top: params, + bottom: params, + }) + } else if (params !== null && typeof params === 'object') { + return Object.assign({}, defaultSafeArea) + } else if (typeof params === 'string') { + return Object.assign({}, defaultSafeArea, { + [params]: true, + }) + } + return defaultSafeArea +} + +export default Behavior({ + properties: { + safeArea: { + type: [Boolean, String, Object], + value: false, + }, + }, + observers: { + safeArea(newVal) { + this.setData({ safeAreaConfig: setSafeArea(newVal) }) + }, + }, + definitionFilter(defFields) { + const { statusBarHeight } = getSystemInfo() || {} + const isIPhoneX = checkIPhoneX() + + Object.assign(defFields.data = (defFields.data || {}), { + safeAreaConfig: defaultSafeArea, + statusBarHeight, + isIPhoneX, + }) + }, +}) diff --git a/components/helpers/safeSetDataBehavior.js b/components/helpers/safeSetDataBehavior.js new file mode 100644 index 0000000..f00f0ae --- /dev/null +++ b/components/helpers/safeSetDataBehavior.js @@ -0,0 +1,57 @@ +export default Behavior({ + lifetimes: { + created() { + this.nextCallback = null + }, + detached() { + this.cancelNextCallback() + }, + }, + methods: { + /** + * safeSetData + * @param {Object} nextData 数据对象 + * @param {Function} callback 回调函数 + */ + safeSetData(nextData, callback) { + this.pendingData = Object.assign({}, this.data, nextData) + callback = this.setNextCallback(callback) + + this.setData(nextData, () => { + this.pendingData = null + callback() + }) + }, + /** + * 设置下一回调函数 + * @param {Function} callback 回调函数 + */ + setNextCallback(callback) { + let active = true + + this.nextCallback = (event) => { + if (active) { + active = false + this.nextCallback = null + + callback.call(this, event) + } + } + + this.nextCallback.cancel = () => { + active = false + } + + return this.nextCallback + }, + /** + * 取消下一回调函数 + */ + cancelNextCallback() { + if (this.nextCallback !== null) { + this.nextCallback.cancel() + this.nextCallback = null + } + }, + }, +}) \ No newline at end of file diff --git a/components/helpers/shallowEqual.js b/components/helpers/shallowEqual.js new file mode 100644 index 0000000..8aece72 --- /dev/null +++ b/components/helpers/shallowEqual.js @@ -0,0 +1,65 @@ +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @typechecks + * + */ + +/*eslint-disable no-self-compare */ + +'use strict'; + +var hasOwnProperty = Object.prototype.hasOwnProperty; + +/** + * inlined Object.is polyfill to avoid requiring consumers ship their own + * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is + */ +function is(x, y) { + // SameValue algorithm + if (x === y) { + // Steps 1-5, 7-10 + // Steps 6.b-6.e: +0 != -0 + // Added the nonzero y check to make Flow happy, but it is redundant + return x !== 0 || y !== 0 || 1 / x === 1 / y; + } else { + // Step 6.a: NaN == NaN + return x !== x && y !== y; + } +} + +/** + * Performs equality by iterating through keys on an object and returning false + * when any key has values which are not strictly equal between the arguments. + * Returns true when the values of all keys are strictly equal. + */ +function shallowEqual(objA, objB) { + if (is(objA, objB)) { + return true; + } + + if (typeof objA !== 'object' || objA === null || typeof objB !== 'object' || objB === null) { + return false; + } + + var keysA = Object.keys(objA); + var keysB = Object.keys(objB); + + if (keysA.length !== keysB.length) { + return false; + } + + // Test for A's keys different from B. + for (var i = 0; i < keysA.length; i++) { + if (!hasOwnProperty.call(objB, keysA[i]) || !is(objA[keysA[i]], objB[keysA[i]])) { + return false; + } + } + + return true; +} + +export default shallowEqual \ No newline at end of file diff --git a/components/helpers/styleToCssString.js b/components/helpers/styleToCssString.js new file mode 100644 index 0000000..1665cf7 --- /dev/null +++ b/components/helpers/styleToCssString.js @@ -0,0 +1,138 @@ +'use strict'; + +/** + * CSS properties which accept numbers but are not in units of "px". + */ +var isUnitlessNumber = { + boxFlex: true, + boxFlexGroup: true, + columnCount: true, + flex: true, + flexGrow: true, + flexPositive: true, + flexShrink: true, + flexNegative: true, + fontWeight: true, + lineClamp: true, + lineHeight: true, + opacity: true, + order: true, + orphans: true, + widows: true, + zIndex: true, + zoom: true, + + // SVG-related properties + fillOpacity: true, + strokeDashoffset: true, + strokeOpacity: true, + strokeWidth: true +}; + +/** + * @param {string} prefix vendor-specific prefix, eg: Webkit + * @param {string} key style name, eg: transitionDuration + * @return {string} style name prefixed with `prefix`, properly camelCased, eg: + * WebkitTransitionDuration + */ +function prefixKey(prefix, key) { + return prefix + key.charAt(0).toUpperCase() + key.substring(1); +} + +/** + * Support style names that may come passed in prefixed by adding permutations + * of vendor prefixes. + */ +var prefixes = ['Webkit', 'ms', 'Moz', 'O']; + +// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an +// infinite loop, because it iterates over the newly added props too. +Object.keys(isUnitlessNumber).forEach(function(prop) { + prefixes.forEach(function(prefix) { + isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop]; + }); +}); + +var msPattern = /^ms-/; + +var _uppercasePattern = /([A-Z])/g; + +/** + * Hyphenates a camelcased string, for example: + * + * > hyphenate('backgroundColor') + * < "background-color" + * + * For CSS style names, use `hyphenateStyleName` instead which works properly + * with all vendor prefixes, including `ms`. + * + * @param {string} string + * @return {string} + */ +function hyphenate(string) { + return string.replace(_uppercasePattern, '-$1').toLowerCase(); +} + +/** + * Hyphenates a camelcased CSS property name, for example: + * + * > hyphenateStyleName('backgroundColor') + * < "background-color" + * > hyphenateStyleName('MozTransition') + * < "-moz-transition" + * > hyphenateStyleName('msTransition') + * < "-ms-transition" + * + * As Modernizr suggests (http://modernizr.com/docs/#prefixed), an `ms` prefix + * is converted to `-ms-`. + * + * @param {string} string + * @return {string} + */ +function hyphenateStyleName(string) { + return hyphenate(string).replace(msPattern, '-ms-'); +} + +var isArray = Array.isArray; +var keys = Object.keys; + +var counter = 1; +// Follows syntax at https://developer.mozilla.org/en-US/docs/Web/CSS/content, +// including multiple space separated values. +var unquotedContentValueRegex = /^(normal|none|(\b(url\([^)]*\)|chapter_counter|attr\([^)]*\)|(no-)?(open|close)-quote|inherit)((\b\s*)|$|\s+))+)$/; + +function buildRule(key, value) { + if (!isUnitlessNumber[key] && typeof value === 'number') { + value = '' + value + 'px'; + } else if (key === 'content' && !unquotedContentValueRegex.test(value)) { + value = "'" + value.replace(/'/g, "\\'") + "'"; + } + + return hyphenateStyleName(key) + ': ' + value + '; '; +} + +function styleToCssString(rules) { + var result = '' + if (typeof rules === 'string') { + return rules + } + if (!rules || keys(rules).length === 0) { + return result; + } + var styleKeys = keys(rules); + for (var j = 0, l = styleKeys.length; j < l; j++) { + var styleKey = styleKeys[j]; + var value = rules[styleKey]; + + if (isArray(value)) { + for (var i = 0, len = value.length; i < len; i++) { + result += buildRule(styleKey, value[i]); + } + } else { + result += buildRule(styleKey, value); + } + } + return result; +} + +export default styleToCssString \ No newline at end of file diff --git a/components/popover/index.js b/components/popover/index.js new file mode 100644 index 0000000..9c79ff2 --- /dev/null +++ b/components/popover/index.js @@ -0,0 +1,234 @@ +import baseComponent from '../helpers/baseComponent' +import classNames from '../helpers/classNames' +import styleToCssString from '../helpers/styleToCssString' + +const getPlacements = ([a, s, b] = rects, placement = 'top') => { + switch (placement) { + case 'topLeft': + return { + top: s.scrollTop + a.top - b.height - 4, + left: s.scrollLeft + a.left, + } + case 'top': + return { + top: s.scrollTop + a.top - b.height - 4, + left: s.scrollLeft + a.left + (a.width - b.width) / 2, + } + case 'topRight': + return { + top: s.scrollTop + a.top - b.height - 4, + left: s.scrollLeft + a.left + a.width - b.width, + } + case 'rightTop': + return { + top: s.scrollTop + a.top, + left: s.scrollLeft + a.left + a.width + 4, + } + case 'right': + return { + top: s.scrollTop + a.top + (a.height - b.height) / 2, + left: s.scrollLeft + a.left + a.width + 4, + } + case 'rightBottom': + return { + top: s.scrollTop + a.top + a.height - b.height, + left: s.scrollLeft + a.left + a.width + 4, + } + case 'bottomRight': + return { + top: s.scrollTop + a.top + a.height + 4, + left: s.scrollLeft + a.left + a.width - b.width, + } + case 'bottom': + return { + top: s.scrollTop + a.top + a.height + 4, + left: s.scrollLeft + a.left + (a.width - b.width) / 2, + } + case 'bottomLeft': + return { + top: s.scrollTop + a.top + a.height + 4, + left: s.scrollLeft + a.left, + } + case 'leftBottom': + return { + top: s.scrollTop + a.top + a.height - b.height, + left: s.scrollLeft + a.left - b.width - 4, + } + case 'left': + return { + top: s.scrollTop + a.top + (a.height - b.height) / 2, + left: s.scrollLeft + a.left - b.width - 4, + } + case 'leftTop': + return { + top: s.scrollTop + a.top, + left: s.scrollLeft + a.left - b.width - 4, + } + default: + return { + left: 0, + top: 0, + } + } +} + +baseComponent({ + properties: { + prefixCls: { + type: String, + value: 'wux-popover', + }, + classNames: { + type: null, + value: 'wux-animate--fadeIn', + }, + theme: { + type: String, + value: 'light', + }, + title: { + type: String, + value: '', + }, + content: { + type: String, + value: '', + }, + placement: { + type: String, + value: 'top', + }, + trigger: { + type: String, + value: 'click', + }, + bodyStyle: { + type: [String, Object], + value: '', + observer(newVal) { + this.setData({ + extStyle: styleToCssString(newVal), + }) + }, + }, + defaultVisible: { + type: Boolean, + value: false, + }, + visible: { + type: Boolean, + value: false, + observer(newVal) { + if (this.data.controlled) { + this.updated(newVal) + } + }, + }, + controlled: { + type: Boolean, + value: false, + }, + mask: { + type: Boolean, + value: false, + }, + maskClosable: { + type: Boolean, + value: true, + }, + }, + data: { + extStyle: '', + popoverStyle: '', + popoverVisible: false, + }, + computed: { + classes: ['prefixCls, theme, placement', function(prefixCls, theme, placement) { + const wrap = classNames(prefixCls, { + [`${prefixCls}--theme-${theme}`]: theme, + [`${prefixCls}--placement-${placement}`]: placement, + }) + const content = `${prefixCls}__content` + const arrow = `${prefixCls}__arrow` + const inner = `${prefixCls}__inner` + const title = `${prefixCls}__title` + const innerContent = `${prefixCls}__inner-content` + const element = `${prefixCls}__element` + + return { + wrap, + content, + arrow, + inner, + title, + innerContent, + element, + } + }], + }, + methods: { + updated(popoverVisible) { + if (this.data.popoverVisible !== popoverVisible) { + this.setData({ popoverVisible }) + this.setBackdropVisible(popoverVisible) + } + }, + getPopoverStyle() { + const { prefixCls, placement } = this.data + const query = wx.createSelectorQuery().in(this) + query.select(`.${prefixCls}__element`).boundingClientRect() + query.selectViewport().scrollOffset() + query.select(`.${prefixCls}`).boundingClientRect() + query.exec((rects) => { + if (rects.filter((n) => !n).length) return + + const placements = getPlacements(rects, placement) + const popoverStyle = styleToCssString(placements) + + this.setData({ + popoverStyle, + }) + }) + }, + /** + * 当组件进入过渡的开始状态时,设置气泡框位置信息 + */ + onEnter() { + this.getPopoverStyle() + }, + onChange() { + const { popoverVisible, controlled } = this.data + const nextVisible = !popoverVisible + + if (!controlled) { + this.updated(nextVisible) + } + + this.triggerEvent('change', { visible: nextVisible }) + }, + onClick() { + if (this.data.trigger === 'click') { + this.onChange() + } + }, + setBackdropVisible(visible) { + if (this.data.mask && this.wuxBackdrop) { + this.wuxBackdrop[visible ? 'retain' : 'release']() + } + }, + onMaskClick() { + const { maskClosable, popoverVisible } = this.data + if (maskClosable && popoverVisible) { + this.onChange() + } + }, + }, + ready() { + const { defaultVisible, visible, controlled } = this.data + const popoverVisible = controlled ? visible : defaultVisible + if (this.data.mask) { + this.wuxBackdrop = this.selectComponent('#wux-backdrop') + } + this.updated(popoverVisible) + }, +}) diff --git a/components/popover/index.json b/components/popover/index.json new file mode 100644 index 0000000..716bdf5 --- /dev/null +++ b/components/popover/index.json @@ -0,0 +1,7 @@ +{ + "component": true, + "usingComponents": { + "wux-animation-group": "../animation-group/index", + "wux-backdrop": "../backdrop/index" + } +} diff --git a/components/popover/index.wxml b/components/popover/index.wxml new file mode 100644 index 0000000..c1dfa43 --- /dev/null +++ b/components/popover/index.wxml @@ -0,0 +1,17 @@ + + + + + + + {{ title }} + + {{ content }} + + + + + + + + diff --git a/components/popover/index.wxss b/components/popover/index.wxss new file mode 100644 index 0000000..24f765a --- /dev/null +++ b/components/popover/index.wxss @@ -0,0 +1,193 @@ +.wux-popover { + font-family: Monospaced Number,Chinese Quote,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,PingFang SC,Hiragino Sans GB,Microsoft YaHei,Helvetica Neue,Helvetica,Arial,sans-serif; + font-size: 28rpx; + line-height: 1.5; + color: rgba(0,0,0,.65); + box-sizing: border-box; + margin: 0; + padding: 0; + list-style: none; + position: absolute; + top: 0; + left: 0; + z-index: 1030; + cursor: auto; + -webkit-user-select: text; + -moz-user-select: text; + -ms-user-select: text; + user-select: text; + white-space: normal; + font-weight: 400; + text-align: left +} +.wux-popover::after { + content: ""; + position: absolute; + background: rgba(255,255,255,.01) +} +.wux-popover__container { + position: absolute; + top: 0; + left: 0; + width: 100% +} +.wux-popover__element { + display: inline-block; + line-height: 1 +} +.wux-popover--theme-dark .wux-popover__inner { + background-color: #333 +} +.wux-popover--theme-dark.wux-popover--placement-top .wux-popover__arrow, +.wux-popover--theme-dark.wux-popover--placement-topLeft .wux-popover__arrow, +.wux-popover--theme-dark.wux-popover--placement-topRight .wux-popover__arrow { + border-top-color: #333 +} +.wux-popover--theme-dark.wux-popover--placement-right .wux-popover__arrow, +.wux-popover--theme-dark.wux-popover--placement-rightBottom .wux-popover__arrow, +.wux-popover--theme-dark.wux-popover--placement-rightTop .wux-popover__arrow { + border-right-color: #333 +} +.wux-popover--theme-dark.wux-popover--placement-bottom .wux-popover__arrow, +.wux-popover--theme-dark.wux-popover--placement-bottomLeft .wux-popover__arrow, +.wux-popover--theme-dark.wux-popover--placement-bottomRight .wux-popover__arrow { + border-bottom-color: #333 +} +.wux-popover--theme-dark.wux-popover--placement-left .wux-popover__arrow, +.wux-popover--theme-dark.wux-popover--placement-leftBottom .wux-popover__arrow, +.wux-popover--theme-dark.wux-popover--placement-leftTop .wux-popover__arrow { + border-left-color: #333 +} +.wux-popover--theme-dark .wux-popover__inner, +.wux-popover--theme-dark .wux-popover__title { + color: #fff +} +.wux-popover--placement-top, +.wux-popover--placement-topLeft, +.wux-popover--placement-topRight { + padding-bottom: 10px +} +.wux-popover--placement-right, +.wux-popover--placement-rightBottom, +.wux-popover--placement-rightTop { + padding-left: 10px +} +.wux-popover--placement-bottom, +.wux-popover--placement-bottomLeft, +.wux-popover--placement-bottomRight { + padding-top: 10px +} +.wux-popover--placement-left, +.wux-popover--placement-leftBottom, +.wux-popover--placement-leftTop { + padding-right: 10px +} +.wux-popover__inner { + background-color: #fff; + background-clip: padding-box; + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0,0,0,.15); + color: rgba(0,0,0,.65) +} +.wux-popover__title { + position: relative; + min-width: 120px; + margin: 0; + padding: 5px 16px 4px; + min-height: 32px; + box-sizing: border-box; + color: rgba(0,0,0,.85); + font-weight: 500 +} +.wux-popover__title::after { + content: " "; + position: absolute; + left: 0; + bottom: 0; + right: 0; + height: 1PX; + border-bottom: 1PX solid #d9d9d9; + color: #d9d9d9; + transform-origin: 0 100%; + transform: scaleY(.5) +} +.wux-popover__inner-content { + padding: 24rpx 32rpx +} +.wux-popover__arrow { + width: 0; + height: 0; + position: absolute; + display: block; + border-color: transparent; + border-style: solid; + border-width: 16.97056274rpx +} +.wux-popover--placement-top .wux-popover__arrow, +.wux-popover--placement-topLeft .wux-popover__arrow, +.wux-popover--placement-topRight .wux-popover__arrow { + bottom: 8rpx; + border-bottom-width: 0; + border-top-color: #fff +} +.wux-popover--placement-top .wux-popover__arrow { + left: 50%; + transform: translateX(-50%) +} +.wux-popover--placement-topLeft .wux-popover__arrow { + left: 32rpx +} +.wux-popover--placement-topRight .wux-popover__arrow { + right: 32rpx +} +.wux-popover--placement-right .wux-popover__arrow, +.wux-popover--placement-rightBottom .wux-popover__arrow, +.wux-popover--placement-rightTop .wux-popover__arrow { + left: 8rpx; + border-left-width: 0; + border-right-color: #fff +} +.wux-popover--placement-right .wux-popover__arrow { + top: 50%; + transform: translateY(-50%) +} +.wux-popover--placement-rightTop .wux-popover__arrow { + top: 24rpx +} +.wux-popover--placement-rightBottom .wux-popover__arrow { + bottom: 24rpx +} +.wux-popover--placement-bottom .wux-popover__arrow, +.wux-popover--placement-bottomLeft .wux-popover__arrow, +.wux-popover--placement-bottomRight .wux-popover__arrow { + top: 8rpx; + border-top-width: 0; + border-bottom-color: #fff +} +.wux-popover--placement-bottom .wux-popover__arrow { + left: 50%; + transform: translateX(-50%) +} +.wux-popover--placement-bottomLeft .wux-popover__arrow { + left: 32rpx +} +.wux-popover--placement-bottomRight .wux-popover__arrow { + right: 32rpx +} +.wux-popover--placement-left .wux-popover__arrow, +.wux-popover--placement-leftBottom .wux-popover__arrow, +.wux-popover--placement-leftTop .wux-popover__arrow { + right: 8rpx; + border-right-width: 0; + border-left-color: #fff +} +.wux-popover--placement-left .wux-popover__arrow { + top: 50%; + transform: translateY(-50%) +} +.wux-popover--placement-leftTop .wux-popover__arrow { + top: 24rpx +} +.wux-popover--placement-leftBottom .wux-popover__arrow { + bottom: 24rpx +} \ No newline at end of file diff --git a/pages/storage/order-info/index.js b/pages/storage/order-info/index.js index 412cf43..9277f05 100644 --- a/pages/storage/order-info/index.js +++ b/pages/storage/order-info/index.js @@ -90,7 +90,7 @@ Scene({ }, printOrderInfo: function(){ wx.showLoading({ title: '正在处理', mask: true }) - printOrder({id: this.data.form.id, printType: 3 }).then(result => { + printOrder({id: this.data.orderInfo.id, printType: 3 }).then(result => { wx.hideLoading() util.showToast('即将打印,请稍后') }).catch(err => {