Browse Source

下拉刷新的组件

master
xpz2018 6 years ago
parent
commit
16ef990815
29 changed files with 473 additions and 308 deletions
  1. 59
      components/_common/color.js
  2. 17
      components/_common/data-hook.js
  3. 9
      components/_common/extra-props.js
  4. 12
      components/_common/is-iphonex.js
  5. 24
      components/_common/jan-component.js
  6. 21
      components/_common/mixin-component.js
  7. 49
      components/_common/open-type.js
  8. 36
      components/_common/scroll-view.js
  9. 4
      components/_common/type-of.js
  10. 16
      components/_common/utils.wxs
  11. 56
      components/refresh-view/index.js
  12. 4
      components/refresh-view/index.json
  13. 9
      components/refresh-view/index.wxml
  14. 158
      components/refresh-view/index.wxs
  15. 68
      components/refresh-view/index.wxss
  16. BIN
      components/refresher/.DS_Store
  17. 116
      components/refresher/index.js
  18. 5
      components/refresher/index.json
  19. 10
      components/refresher/index.wxml
  20. 33
      components/refresher/index.wxss
  21. 2
      pages/mall/index/index.json
  22. 2
      pages/mall/search-list/index.json
  23. 2
      pages/mall/shops/index.json
  24. 13
      pages/mall/shops/index.wxml
  25. 2
      pages/message/index.json
  26. 33
      pages/refresh/refresh.js
  27. 5
      pages/refresh/refresh.json
  28. 6
      pages/refresh/refresh.wxml
  29. 10
      pages/refresh/refresh.wxss

59
components/_common/color.js

@ -0,0 +1,59 @@
const hex2Rgb = function(color) {
let reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
color = color.toLowerCase()
if (color.length == 9) color = color.slice(0, 7)
if (reg.test(color)) {
if (color.length === 4) {
let colorNew = "#"
for (let i = 1; i < 4; i += 1) {
colorNew += color.slice(i, i + 1).concat(color.slice(i, i + 1))
}
color = colorNew
}
let colorChange = []
for (let i = 1; i < 7; i += 2) {
colorChange.push(parseInt("0x" + color.slice(i, i + 2)))
}
return colorChange
} else {
if (color.includes("rgb")) {
color = color
.replace(/(rgba|rgb)/, "")
.replace("(", "")
.replace(")", "")
if (color.includes(",")) {
return color
.split(",")
.map(item => item * 1)
.slice(0, 3)
} else {
return [0, 0, 0]
}
} else {
return [0, 0, 0]
}
}
}
const isTextDeepColor = function(backgroundRgbArr = [255, 255, 255]) {
if (
!(
backgroundRgbArr instanceof Array &&
backgroundRgbArr.length === 3 &&
backgroundRgbArr.every(item => item >= 0 && item <= 255)
)
)
return false
const grayLevel =
backgroundRgbArr[0] * 0.299 +
backgroundRgbArr[1] * 0.587 +
backgroundRgbArr[2] * 0.114
return grayLevel >= 192
}
module.exports = {
hex2Rgb,
isTextDeepColor
}

17
components/_common/data-hook.js

@ -0,0 +1,17 @@
const dataHook = function(dataList = [], hook) {
if (
!dataList instanceof Array ||
!dataList.length ||
!dataList.every(item => typeof item == "string")
)
return {}
if (!hook instanceof Function) hook = () => void 0
let obj = {
observers: {}
},
key = dataList.join(",")
obj["observers"][key] = hook
return obj
}
module.exports = dataHook

9
components/_common/extra-props.js

@ -0,0 +1,9 @@
const extraProps = function(props, extra) {
if (typeof extra !== "object") return props
for (let key in props) {
if (extra[key]) props[key] = extra[key]
}
return props
}
module.exports = extraProps

12
components/_common/is-iphonex.js

@ -0,0 +1,12 @@
function isIphoneX() {
var systemInfo = wx.getSystemInfoSync()
return !!(
systemInfo &&
systemInfo.model &&
systemInfo.model.indexOf("iPhone X") > -1
)
}
module.exports = {
isIphoneX: isIphoneX
}

24
components/_common/jan-component.js

@ -0,0 +1,24 @@
const mixinComponent = require("./mixin-component")
const janComponent = function(options = {}) {
options = mixinComponent(
{
options: {
addGlobalClass: true
},
externalClasses: ["custom-class", "customClass"],
properties: {
customClass: String
},
data: {
_class: "",
_style: ""
},
methods: {}
},
options
)
return options
}
module.exports = janComponent

21
components/_common/mixin-component.js

@ -0,0 +1,21 @@
const typeOf = require("./type-of")
const mixinComponent = function(origin = {}, next = {}) {
if (typeOf(origin) !== "object") origin = {}
if (typeOf(next) !== "object") next = {}
for (let key in next) {
if (typeOf(next[key]) === "object") {
if (typeOf(origin[key]) === "object") {
origin[key] = mixinComponent(origin[key], next[key])
} else {
origin[key] = next[key]
}
} else {
if (typeOf(origin[key]) == "undefined") origin[key] = next[key]
}
}
return origin
}
module.exports = mixinComponent

49
components/_common/open-type.js

@ -0,0 +1,49 @@
const mapOpenType = function() {
return {
properties: {
openType: {
type: String,
value: ""
},
id: String,
lang: {
type: String,
value: "en"
},
businessId: Number,
sessionFrom: String,
sendMessageTitle: String,
sendMessagePath: String,
sendMessageImg: String,
showMessageCard: Boolean,
appParameter: String,
ariaLabel: String
},
methods: {
$emit(type, e) {
this.triggerEvent(type, e)
},
bindGetUserInfo(event) {
this.$emit("getuserinfo", event.detail)
},
bindContact(event) {
this.$emit("contact", event.detail)
},
bindGetPhoneNumber(event) {
this.$emit("getphonenumber", event.detail)
},
bindError(event) {
this.$emit("error", event.detail)
},
bindLaunchApp(event) {
this.$emit("launchapp", event.detail)
},
bindOpenSetting(event) {
this.$emit("opensetting", event.detail)
}
}
}
}
module.exports = mapOpenType

36
components/_common/scroll-view.js

@ -0,0 +1,36 @@
const mapScrollView = function() {
return {
properties: {
scrollX: Boolean,
scrollY: {
type: Boolean,
value: true
},
upperThreshold: {
type: Number,
value: 50
},
owerThreshold: {
type: Number,
value: 50
},
scrollTop: Number,
scrollLeft: Number,
scrollIntoView: String,
scrollWithAnimation: Boolean,
enableBackToTop: {
type: Boolean,
value: true
},
enableFlex: Boolean,
scrollAnchoring: Boolean
},
methods: {
$emit(type, e) {
this.triggerEvent(type, e)
}
}
}
}
module.exports = mapScrollView

4
components/_common/type-of.js

@ -0,0 +1,4 @@
const typeOf = obj =>
/\[object (.+?)\]/g.exec(Object.prototype.toString.call(obj))[1].toLowerCase()
module.exports = typeOf

16
components/_common/utils.wxs

@ -0,0 +1,16 @@
function addUnit(value) {
if (value == null) {
return ""
}
if (typeof value === "string" && value.indexOf("var") > -1) {
return value
}
if (value >= 0) return value + "px"
return value
}
module.exports = {
addUnit: addUnit
}

56
components/refresh-view/index.js

@ -1,56 +0,0 @@
// 使用的时候,用本组件包裹可以触发下拉刷新的内容。enablePullDownRefresh需要设置为false。
Component({
properties: {
refreshed: { // 必选,通知本组件收起
type: Boolean,
value: false,
},
refreshing: { // 可选,通知本组件直接进入refreshing状态
type: Boolean,
value: false,
},
distMax: { // 可选,可以下拉的最大高度,回弹的高度为最大高度的75%
type: Number,
value: 45,
},
color: { // 可选,圆弧颜色
type: String,
value: "#000",
},
backgroundColor: { // 可选,背景颜色
type: String,
value: "#fff",
},
type: {
type: Number,
value: 0
}
},
data: {
reachTop: false,
},
methods: {
initObserver() {
this.observer = this.createIntersectionObserver()
this.observer.relativeToViewport().observe(".intersection-dot", (res) => {
if (res.intersectionRatio > 0) {
this.setData({ reachTop: true, })
} else {
this.setData({ reachTop: false,})
}
})
},
clearObserver() {
if (this.observer) {
this.observer.disconnect()
this.observer = null
}
},
},
ready() {
this.initObserver()
},
detached() {
this.clearObserver()
},
})

4
components/refresh-view/index.json

@ -1,4 +0,0 @@
{
"component": true,
"usingComponents": {}
}

9
components/refresh-view/index.wxml

@ -1,9 +0,0 @@
<wxs module="refresher" src="./index.wxs"></wxs>
<view class="refresh-wrap" style="{{backgroundColor ? 'background-color:' + backgroundColor + ';' : ''}}">
<view class="refresh" style="{{color ? 'border-color:' + color + ';' : ''}}"></view>
</view>
<view class="wrap" change:refreshed="{{refresher.onRefreshed}}" refreshed="{{refreshed}}" change:refreshing="{{refresher.onRefreshing}}" refreshing="{{refreshing}}" data-dist-max="{{distMax}}" data-type="{{type}}" data-reach-top="{{reachTop}}" bind:touchstart="{{refresher.touchStart}}"
bind:touchmove="{{refresher.touchMove}}" bind:touchend="{{refresher.touchEnd}}">
<view class="intersection-dot"></view>
<slot></slot>
</view>

158
components/refresh-view/index.wxs

@ -1,158 +0,0 @@
// 文档:https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
/* eslint-disable */
function calcResistance(distExtra, distMax) {
return Math.min(1, distExtra / distMax / 1.2) * Math.min(distMax, distExtra)
}
function touchStart(e, ins) {
var dataset = e.instance.getDataset()
var state = e.instance.getState()
state.distMax = dataset.distMax
state.type = dataset.type
state.distThreshold = state.distMax * 0.75
state.reachTop = dataset.reachTop
state.status = state.status || "pending" // pending、pulling、releasing、refreshing
if (state.status === "pending" && state.reachTop) {
state.startPulling = true
state.pullStartX = e.touches[0].clientX
state.pullStartY = e.touches[0].clientY
state.distance = 0
state.component = state.component || ins.selectComponent(".refresh-wrap")
state.component.removeClass("transition")
state.component.removeClass("animation")
state.component.removeClass("fadeout")
state.component.setStyle({
top: (state.distance * 2 - 20) + "rpx",
opacity: 0,
transform: "rotate(0)",
"-webkit-transform": "rotate(0)"
})
}
}
function touchMove(e, ins) {
var state = e.instance.getState()
if (!state.startPulling || state.status === "refreshing") {
return true
}
state.pullMoveX = e.touches[0].clientX
state.pullMoveY = e.touches[0].clientY
if (state.status === "pending") {
state.status = "pulling"
}
var distExtraX = state.pullMoveX - state.pullStartX
var distExtraY = state.pullMoveY - state.pullStartY
if (distExtraY > 0 && Math.abs(distExtraY) >= Math.abs(distExtraX)) {
state.distance = calcResistance(distExtraY, state.distMax)
state.component.setStyle({
top: setop(state.distance, state.type),
opacity: Math.min(1, state.distance / state.distThreshold),
transform: "rotate(" + (245 * state.distance / state.distMax) + "deg)",
"-webkit-transform": "rotate(" + (245 * state.distance / state.distMax) + "deg)"
})
if (state.status === "pulling" && state.distance > state.distThreshold) {
state.status = "releasing"
}
if (state.status === "releasing" && state.distance < state.distThreshold) {
state.status = "pulling"
}
return false
}
return true
}
function touchEnd(e, ins) {
var state = e.instance.getState()
if (!state.startPulling || state.status === "refreshing") {
return
}
state.startPulling = false
if (state.status === "releasing" && state.distance > state.distThreshold) {
state.status = "refreshing"
state.distance = state.distThreshold
state.component.addClass("animation")
state.component.setStyle({
top: setop(state.distance, state.type),
opacity: Math.min(1, state.distance / state.distThreshold),
})
ins.triggerEvent("refresh")
} else {
reset(state)
}
}
function reset(state) {
state.status = "pending"
state.startPulling = false
state.distance = 0
state.component.addClass("transition")
state.component.setStyle({
top: setop(state.distance, state.type),
opacity: 0,
transform: "rotate(0)",
"-webkit-transform": "rotate(0)"
})
}
function onRefreshed(newValue, oldValue, ins, itemIns) {
if (newValue) {
var state = itemIns.getState()
state.status = "pending"
state.startPulling = false
var component = state.component || ins.selectComponent(".refresh-wrap")
component.removeClass("animation")
component.addClass("fadeout")
component.setStyle({
top: (state.distance * 2 - (state.type? 12 : 30)) + "rpx",
opacity: 0,
transform: "scale(0)",
"-webkit-transform": "rotate(0)"
})
}
}
function setop(distance, type){
var top = (distance * 2 + (type == 0 ? 120 : -30)) + "rpx"
return top
}
function onRefreshing(newValue, oldValue, ins, itemIns) {
if (newValue) {
var dataset = itemIns.getDataset()
var state = itemIns.getState()
state.distMax = state.distMax || dataset.distMax
state.distThreshold = state.distThreshold || state.distMax * 0.75
state.status = "refreshing"
state.distance = state.distThreshold
state.startPulling = false
state.component = state.component || ins.selectComponent(".refresh-wrap")
state.component.removeClass("transition")
state.component.removeClass("fadeout")
state.component.addClass("animation")
state.component.setStyle({
top: setop(state.distance, state.type),
opacity: 1,
transition: "none",
})
// ins.triggerEvent("refresh")
}
}
module.exports = {
touchStart: touchStart,
touchMove: touchMove,
touchEnd: touchEnd,
onRefreshed: onRefreshed,
onRefreshing: onRefreshing,
}
/* eslint-enable */

68
components/refresh-view/index.wxss

@ -1,68 +0,0 @@
@keyframes rolling {
0% {
transform: rotate(135deg);
}
100% {
transform: rotate(495deg);
}
}
@keyframes fadeout {
0% {
opacity: 1;
transform: rotate(135deg) scale(1);
}
100% {
opacity: 0;
transform: rotate(135deg) scale(0);
}
}
.refresh-wrap {
position: fixed;
width: 42px;
height: 42px;
left: 50%;
margin-left: -21px;
top: 50px;
z-index: 88;
background-color: #fff;
border-radius: 50%;
box-shadow: 0 0 6px 1px #ccc;
opacity: 0;
display: flex;
align-items: center;
justify-content: center;
}
.refresh-wrap.transition {
transition: top 0.3s ease-in-out, opacity 0.3s ease-in-out, transform 0.3s ease-in-out;
}
.refresh-wrap.animation {
transition: top 0.3s ease-in-out, opacity 0.3s ease-in-out;
animation: rolling 0.6s linear infinite;
}
.refresh-wrap.fadeout {
animation: fadeout 0.3s ease-in-out;
}
.refresh-wrap .refresh {
box-sizing: border-box;
width: 24px;
height: 24px;
border-radius: 50%;
border: 3px solid #000;
border-top-color: transparent !important;
}
.refresh-wrap.fadeout .refresh {
opacity: 0;
}
.wrap {
width: 100%;
height: 100%;
position: relative;
}
.wrap .intersection-dot {
position: absolute;
width: 10px;
height: 10px;
left: 0;
top: 0;
visibility: hidden;
}

BIN
components/refresher/.DS_Store

116
components/refresher/index.js

@ -0,0 +1,116 @@
/**
* 组件refresher
* 版本v0.0.1
* 维护人Meeken
*/
const janComponent = require("../_common/jan-component")
const mixinComponent = require("../_common/mixin-component")
const mapScrollView = require("../_common/scroll-view")
/* 使用 janComponent 初始化组件配置 */
let options = janComponent({
options: {
multipleSlots: true // 在组件定义时的选项中启用多slot支持
},
properties: {
refresherTextStyle: {
type: String,
value: "black"
},
refresherBackground: {
type: String,
value: "inherit"
},
triggered: {
type: Boolean,
value: false
},
threshold: {
type: Number,
value: 50
},
restoredText: {
type: String,
value: "下拉刷新"
},
pullingText: {
type: String,
value: "松开刷新"
},
refreshingText: {
type: String,
value: "正在刷新"
},
useSlotStyle: Boolean
},
data: {
_state: "下拉刷新",
_stateCode: 0,
_process: 0
}
})
options = mixinComponent(options, mapScrollView())
options = mixinComponent(options, {
methods: {
on2Upper(e) {
this.$emit("scrolltoupper", e)
},
on2Lower(e) {
this.$emit("scrolltolower", e)
},
onScroll(e) {
this.$emit("scroll", e)
},
onPulling(e) {
const dy = e.detail.dy,
threshold = this.properties.threshold
this.setData({
_process: dy / threshold,
_stateCode: 1,
_state: dy < threshold ?
this.properties.restoredText :
this.properties.pullingText
})
this.$emit("pulling", e)
},
onRefreshed(e) {
this.setData({
_state: this.properties.refreshingText,
_stateCode: 2,
_process: 1
})
this.$emit("refresh", e)
},
onRestored(e) {
this.setData({
_state: "",
_stateCode: 0,
_process: 0
})
this.$emit("restore", e)
},
onAbort(e) {
this.setData({
_state: "",
_stateCode: 0,
_process: 0
})
this.$emit("abort", e)
}
},
attached() {}
})
Component(options)

5
components/refresher/index.json

@ -0,0 +1,5 @@
{
"component": true,
"usingComponents": {
}
}

10
components/refresher/index.wxml

@ -0,0 +1,10 @@
<scroll-view class="jan-refresher {{ customClass }}" scroll-y enable-back-to-top refresher-enabled refresher-threshold="{{ threshold }}" refresher-default-style="{{ !useSlotStyle ? refresherTextStyle : 'null' }}" refresher-background="{{ refresherBackground }}" refresher-triggered="{{ triggered }}" bindscrolltoupper="on2Upper" bindscrolltolower="on2Lower" bindscroll="onScroll" bindrefresherpulling="onPulling" bindrefresherrefresh="onRefreshed" bindrefresherrestore="onRestored" bindrefresherabort="onAbort" scroll-x="{{ scrollX }}" scroll-y="{{ scrollY }}" upper-threshold="{{ upperThreshold }}" lower-threshold="{{ lowerThreshold }}" scroll-top="{{ scrollTop }}" scroll-left="{{ scrollLeft }}" scroll-into-view="{{ scrollIntoView }}" scroll-with-animation="{{ scrollWithAnimation }}" enable-back-to-top="{{ enableBackToTop }}" enable-flex="{{ enableFlex }}" scroll-anchoring="{{ scrollAnchoring }}">
<view wx:if="{{ useSlotStyle }}" slot="refresher" class="jan-refresh--node">
<view wx:if="{{ _state }}" class="jan-refresher--icon {{ _stateCode === 2 ? 'refreshing' : '' }}" style="font-weight: 600 !important;margin-right: 16rpx;transform: rotate({{ (_process > 1 ? 1 : _process) * 360 }}deg);margin-top:8rpx">
<text class="cuIcon-refresh text-black"></text>
</view>
<view>{{ _state }}</view>
</view>
<slot></slot>
<view class="jan-refresher--safety-block" />
</scroll-view>

33
components/refresher/index.wxss

@ -0,0 +1,33 @@
.jan-refresh--node {
width: 100%;
height: 50px;
background-color: var(--bg-color);
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.jan-refresher--safety-block {
width: 2rpx;
height: 2rpx;
bottom: -2rpx;
position: absolute;
}
.jan-refresher--icon.refreshing {
transition: all 0.3s;
animation: rotate 0.5s linear infinite;
}
@keyframes rotate {
0% {
transform: rotate(0);
}
50% {
transform: rotate(180deg);
}
100% {
transform: rotate(360deg);
}
}

2
pages/mall/index/index.json

@ -1,7 +1,7 @@
{
"component": true,
"usingComponents": {
"refresh-view": "/components/refresh-view/index",
"refresh-view": "/components/refresher/index",
"wux-button": "/components/button/index",
"wux-tab": "/components/tab/index",
"wux-divider": "/components/divider/index"

2
pages/mall/search-list/index.json

@ -1,6 +1,6 @@
{
"usingComponents": {
"refresh-view": "/components/refresh-view/index",
"refresh-view": "/components/refresher/index",
"wux-button": "/components/button/index",
"wux-tab": "/components/tab/index",
"wux-image": "/components/image/index"

2
pages/mall/shops/index.json

@ -5,7 +5,7 @@
"wux-skeleton-paragraph": "/components/skeleton-paragraph/index",
"wux-row": "/components/row/index",
"wux-col": "/components/col/index",
"refresh-view": "/components/refresh-view/index",
"refresh-view": "/components/refresher/index",
"wux-button": "/components/button/index",
"wux-tab": "/components/tab/index",
"wux-image": "/components/image/index",

13
pages/mall/shops/index.wxml

@ -3,12 +3,6 @@
<view slot="content" style="color:black;font-size:18px">原纸商城</view>
</cu-custom>
<!-- <refresh-view refreshing="false" refreshed="{{!requesting}}" bind:refresh="onRefresh"> -->
<!-- <view class="flex flex-column flex-center" wx:if="{{loading}}" style="height:{{height}}rpx">
<view class="load-spinner text-gray" />
<view class="text-empty" style="margin-top:48rpx">加载中...</view>
</view> -->
<view wx:if="{{loading}}" class="page__bd page__bd_spacing">
<view class="sub-title"></view>
<wux-skeleton active>
@ -79,6 +73,7 @@
<image class="img-empty" src="/assets/image/list_empty.png"></image>
<view class="text-empty">暂无数据</view>
</view>
<view wx:else>
<view class="cu-bar bg-white search fixed" style="top:{{CustomBar}}px;">
<view class="search-form" style="border-radius: 12rpx" bindtap="searchList">
@ -86,7 +81,8 @@
<input type="text" disabled="true" placeholder="请输入要搜索的商品" confirm-type="search"></input>
</view>
</view>
<scroll-view scroll-y style="height:{{height}}rpx;margin-top: 100rpx;padding: 0rpx 24rpx" bindscrolltolower="fetchTaskList">
<view style="height:100rpx;width:100%"></view>
<scroll-view scroll-y style="height:{{height}}rpx;padding: 0rpx 24rpx" bindscrolltolower="fetchTaskList">
<image src="https://image.weilanwl.com/img/4x3-1.jpg" class="swiper-image" mode="aspectFill" lazy-load="{{true}}" style="margin-top:24rpx"></image>
<view wx:if="{{cheapList.length}}">
<view class="flex nav-li">
@ -153,5 +149,4 @@
</view>
</wux-divider>
</scroll-view>
</view>
<!-- </refresh-view> -->
</view>

2
pages/message/index.json

@ -1,7 +1,7 @@
{
"component": true,
"usingComponents": {
"refresh-view": "/components/refresh-view/index",
"refresh-view": "/components/refresher/index",
"wux-button": "/components/button/index"
}
}

33
pages/refresh/refresh.js

@ -0,0 +1,33 @@
// refresh.js
Page({
/**
* 页面的初始数据
*/
data: {
refresherState: false
},
onRefresh(e) {
this.setData({
refresherState: true
})
setTimeout(() => {
this.setData({
refresherState: false
})
}, 3000)
},
onLoad() {
setTimeout(() => {
this.setData({
refresherState: true
})
}, 3000)
},
onPulling(e) {
console.log(e)
}
})

5
pages/refresh/refresh.json

@ -0,0 +1,5 @@
{
"usingComponents": {
"refresh-view": "/components/refresher/index"
}
}

6
pages/refresh/refresh.wxml

@ -0,0 +1,6 @@
<view style="height:150rpx;width:100%">
</view>
<refresh-view bind:refresh="onRefresh" triggered="{{ refresherState }}">
<view class="test-block">师德师风</view>
</refresh-view>

10
pages/refresh/refresh.wxss

@ -0,0 +1,10 @@
.test-block {
width: 100vw;
height: 500px;
background: pink;
}
/*
.yoo {
width: 100vw;
height: 400px;
} */
Loading…
Cancel
Save