├── app.js ├── app.json ├── app.wxss ├── components └── RefreshView │ ├── refresh-view.js │ ├── refresh-view.json │ ├── refresh-view.wxml │ └── refresh-view.wxss ├── pages ├── index │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss └── logs │ ├── logs.js │ ├── logs.json │ ├── logs.wxml │ └── logs.wxss ├── project.config.json └── utils └── util.js /app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | onLaunch: function () { 4 | // 展示本地存储能力 5 | var logs = wx.getStorageSync('logs') || [] 6 | logs.unshift(Date.now()) 7 | wx.setStorageSync('logs', logs) 8 | 9 | // 登录 10 | wx.login({ 11 | success: res => { 12 | // 发送 res.code 到后台换取 openId, sessionKey, unionId 13 | } 14 | }) 15 | // 获取用户信息 16 | wx.getSetting({ 17 | success: res => { 18 | if (res.authSetting['scope.userInfo']) { 19 | // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框 20 | wx.getUserInfo({ 21 | success: res => { 22 | // 可以将 res 发送给后台解码出 unionId 23 | this.globalData.userInfo = res.userInfo 24 | 25 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 26 | // 所以此处加入 callback 以防止这种情况 27 | if (this.userInfoReadyCallback) { 28 | this.userInfoReadyCallback(res) 29 | } 30 | } 31 | }) 32 | } 33 | } 34 | }) 35 | }, 36 | globalData: { 37 | userInfo: null 38 | } 39 | }) -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index", 4 | "pages/logs/logs" 5 | ], 6 | "window":{ 7 | "backgroundTextStyle":"light", 8 | "navigationBarBackgroundColor": "#fff", 9 | "navigationBarTitleText": "WeChat", 10 | "navigationBarTextStyle":"black" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 200rpx 0; 9 | box-sizing: border-box; 10 | } 11 | -------------------------------------------------------------------------------- /components/RefreshView/refresh-view.js: -------------------------------------------------------------------------------- 1 | // components/RefreshLayout/refresh-layout.js 2 | let lastY = 0 3 | const PULL_DEFAULT = -1 //默认 4 | const PULL_LT_HEIGHT = 1 //下拉小于高度 5 | const PULL_GT_HEIGHT = 2 //下拉大于高度 6 | const PULL_REFRESHING = 0 //刷新中 7 | let platform = 'ios', scale = 375/ wx.getSystemInfoSync().windowWidth*2 8 | Component({ 9 | /** 10 | * 组件的属性列表 11 | */ 12 | properties: { 13 | backgroundColor: { 14 | type: String, 15 | value: "#000" 16 | }, 17 | refreshHeight: { 18 | type: Number, 19 | value: 150 20 | }, 21 | textColor: { 22 | type: String, 23 | value: "white" 24 | } 25 | }, 26 | 27 | /** 28 | * 组件的初始数据 29 | */ 30 | data: { 31 | pullState: PULL_DEFAULT, // 刷新状态 -1:默认 1:开始下拉 2: 达到下拉最大距离 0: 正在刷新 32 | dynamicHeight: 0, //刷新布局动态高度 33 | refreshHeight: 150, //触发刷新的最小高度 34 | scrollTop: 0 35 | }, 36 | /*** 37 | * 不能使用setData 38 | */ 39 | created: function() { 40 | platform = wx.getSystemInfoSync().platform 41 | scale = wx.getSystemInfoSync().windowWidth / 375 *2 42 | }, 43 | attached: function() {}, 44 | ready: function() {}, 45 | moved: function() {}, 46 | detached: function() {}, 47 | /** 48 | * 组件的方法列表 49 | */ 50 | methods: { 51 | //自动刷新 52 | autoRefresh() { 53 | this._pullStateChange(PULL_REFRESHING, this.data.refreshHeight) 54 | //刷新事件 回调出去 55 | this.triggerEvent("onRefresh") 56 | }, 57 | //停止刷新 58 | stopPullRefresh() { 59 | // this._pullStateChange(PULL_DEFAULT, 0) 60 | this.setData({ 61 | pullState: PULL_DEFAULT, 62 | dynamicHeight: 0 63 | }, () => { 64 | wx.pageScrollTo({scrollTop: 0,duration: 0}) 65 | }) 66 | 67 | }, 68 | //是否正在刷新 69 | isRefreshing() { 70 | return PULL_REFRESHING == this.data.pullState 71 | }, 72 | //是否下拉状态 73 | isPullState() { 74 | return PULL_DEFAULT != this.data.pullState 75 | }, 76 | //页面触摸开始事件,必须在触摸开始方法中调用此方法 77 | handletouchstart: function(event) { 78 | lastY = event.touches[0].clientY 79 | }, 80 | //页面触摸移动事件,必须在触摸开始方法中调用此方法 81 | handletouchmove: function(event) { 82 | let pageY = event.touches[0].pageY 83 | let clientY = event.touches[0].clientY 84 | let offsetY = clientY - lastY 85 | if (this.data.scrollTop > 0 || offsetY < 0) return 86 | // if (0 == this.data.pullState) return 87 | let dynamicHeight = this.data.dynamicHeight + offsetY 88 | if (dynamicHeight > this.data.refreshHeight) { 89 | this._pullStateChange( (0 == this.data.pullState)?0:PULL_GT_HEIGHT, dynamicHeight) 90 | } else { 91 | dynamicHeight = dynamicHeight < 0 ? 0 : dynamicHeight //如果动态高度小于0处理 92 | this._pullStateChange((0 == this.data.pullState) ? 0 :PULL_LT_HEIGHT, dynamicHeight) 93 | } 94 | lastY = event.touches[0].clientY 95 | }, 96 | //页面触摸结束事件,必须在触摸开始方法中调用此方法 97 | handletouchend: function(event) { 98 | let refreshHeight = this.data.refreshHeight 99 | if (0 == this.data.pullState){ 100 | this._pullStateChange(PULL_REFRESHING, refreshHeight) 101 | return 102 | } 103 | let dynamicHeight = this.data.dynamicHeight 104 | if (this.data.scrollTop > 0 && PULL_DEFAULT != this.data.pullState) { 105 | // let top = this.data.scrollTop / wx.getSystemInfoSync().windowWidth * 20 106 | //2 * this.data.scrollTop 两倍表示px转rpx, 所以这里必须进行单位转换 107 | if (dynamicHeight - scale * this.data.scrollTop > refreshHeight) { 108 | this._pullStateChange(PULL_REFRESHING, refreshHeight) 109 | //刷新事件 回调出去 110 | this.triggerEvent("onRefresh") 111 | } else { 112 | this._pullStateChange(PULL_DEFAULT, 0) 113 | wx.pageScrollTo({scrollTop: 0,duration: 0}) 114 | } 115 | return 116 | } 117 | if (dynamicHeight >= this.data.refreshHeight) { 118 | this._pullStateChange(PULL_REFRESHING, refreshHeight) 119 | //刷新事件 回调出去 120 | this.triggerEvent("onRefresh") 121 | } else { 122 | this._pullStateChange(PULL_DEFAULT, 0) 123 | } 124 | }, 125 | //页面触摸取消事件,必须在触摸开始方法中调用此方法 126 | handletouchcancel: function(event) { 127 | this._pullStateChange(PULL_DEFAULT, 0) 128 | }, 129 | //页面滚动 130 | onPageScroll: function(event) { 131 | if (event.scrollTop > 0 && PULL_DEFAULT != this.data.pullState) { 132 | //2 * this.data.scrollTop 两倍表示px转rpx, 所以这里必须进行单位转换 133 | if (this.data.dynamicHeight - scale * event.scrollTop < this.data.refreshHeight) { 134 | this.setData({ 135 | pullState: PULL_LT_HEIGHT 136 | }) 137 | } else { 138 | this.setData({ 139 | pullState: PULL_GT_HEIGHT 140 | }) 141 | } 142 | } 143 | this.data.scrollTop = event.scrollTop 144 | }, 145 | //是否是安卓平台 146 | _isAndriod() { 147 | return 'ios' == platform 148 | }, 149 | //下拉状态监听 150 | _pullStateChange(state, dynamicHeight) { 151 | this.setData({pullState: state,dynamicHeight: dynamicHeight}) 152 | this.triggerEvent("onPullState") 153 | } 154 | } 155 | }) -------------------------------------------------------------------------------- /components/RefreshView/refresh-view.json: -------------------------------------------------------------------------------- 1 | { 2 | "component": true, 3 | "usingComponents": {} 4 | } -------------------------------------------------------------------------------- /components/RefreshView/refresh-view.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{pullState==1?'下拉刷新...':pullState==0?'正在刷新...':'松开刷新...'}} 6 | 7 | -------------------------------------------------------------------------------- /components/RefreshView/refresh-view.wxss: -------------------------------------------------------------------------------- 1 | /* components/RefreshLayout/refresh-layout.wxss */ 2 | 3 | .refresh-container { 4 | box-sizing: border-box; 5 | font-size: 28rpx; 6 | color: #999; 7 | background-color: #eee; 8 | display: flex; 9 | width: 100%; 10 | } 11 | .refresh-layout { 12 | vertical-align: center; 13 | width: 100%; 14 | text-align: center; 15 | align-self: flex-end; 16 | } 17 | 18 | .refresh-loading { 19 | margin: 0 5px; 20 | width: 30rpx; 21 | height: 30rpx; 22 | display: inline-block; 23 | vertical-align: middle; 24 | -webkit-animation: weuiLoading 1s steps(12, end) infinite; 25 | animation: weuiLoading 1s steps(12, end) infinite; 26 | background: transparent url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxMjAiIGhlaWdodD0iMTIwIiB2aWV3Qm94PSIwIDAgMTAwIDEwMCI+PHBhdGggZmlsbD0ibm9uZSIgZD0iTTAgMGgxMDB2MTAwSDB6Ii8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTlFOUU5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0idHJhbnNsYXRlKDAgLTMwKSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iIzk4OTY5NyIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgzMCAxMDUuOTggNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjOUI5OTlBIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDYwIDc1Ljk4IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0EzQTFBMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSg5MCA2NSA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNBQkE5QUEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoMTIwIDU4LjY2IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0IyQjJCMiIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgxNTAgNTQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjQkFCOEI5IiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKDE4MCA1MCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDMkMwQzEiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTE1MCA0NS45OCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNDQkNCQ0IiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTEyMCA0MS4zNCA2NSkiLz48cmVjdCB3aWR0aD0iNyIgaGVpZ2h0PSIyMCIgeD0iNDYuNSIgeT0iNDAiIGZpbGw9IiNEMkQyRDIiIHJ4PSI1IiByeT0iNSIgdHJhbnNmb3JtPSJyb3RhdGUoLTkwIDM1IDY1KSIvPjxyZWN0IHdpZHRoPSI3IiBoZWlnaHQ9IjIwIiB4PSI0Ni41IiB5PSI0MCIgZmlsbD0iI0RBREFEQSIgcng9IjUiIHJ5PSI1IiB0cmFuc2Zvcm09InJvdGF0ZSgtNjAgMjQuMDIgNjUpIi8+PHJlY3Qgd2lkdGg9IjciIGhlaWdodD0iMjAiIHg9IjQ2LjUiIHk9IjQwIiBmaWxsPSIjRTJFMkUyIiByeD0iNSIgcnk9IjUiIHRyYW5zZm9ybT0icm90YXRlKC0zMCAtNS45OCA2NSkiLz48L3N2Zz4=) no-repeat; 27 | background-size: 100%; 28 | } 29 | 30 | .refresh-tips { 31 | line-height: 30rpx; 32 | font-size: 26rpx; 33 | vertical-align: middle; 34 | margin: auto; 35 | } 36 | -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | const app = getApp() 4 | const path = '/praise/pages/praise/' 5 | 6 | Page({ 7 | data: { 8 | motto: 'Hello World', 9 | userInfo: {}, 10 | hasUserInfo: false, 11 | canIUse: wx.canIUse('button.open-type.getUserInfo') 12 | }, 13 | //事件处理函数 14 | bindViewTap: function() { 15 | wx.navigateTo({ 16 | url: path, 17 | }) 18 | }, 19 | onLoad: function() { 20 | // 刷新组件 21 | this.refreshView = this.selectComponent("#refreshView") 22 | // wx.navigateTo({ 23 | // url: path, 24 | // }) 25 | // return 26 | if (app.globalData.userInfo) { 27 | this.setData({ 28 | userInfo: app.globalData.userInfo, 29 | hasUserInfo: true 30 | }) 31 | wx.navigateTo({ 32 | url: path, 33 | }) 34 | } else if (this.data.canIUse) { 35 | // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回 36 | // 所以此处加入 callback 以防止这种情况 37 | app.userInfoReadyCallback = res => { 38 | this.setData({ 39 | userInfo: res.userInfo, 40 | hasUserInfo: true 41 | }) 42 | wx.navigateTo({ 43 | url: path, 44 | }) 45 | } 46 | } else { 47 | // 在没有 open-type=getUserInfo 版本的兼容处理 48 | wx.getUserInfo({ 49 | success: res => { 50 | app.globalData.userInfo = res.userInfo 51 | this.setData({ 52 | userInfo: res.userInfo, 53 | hasUserInfo: true 54 | }) 55 | wx.navigateTo({ 56 | url: path, 57 | }) 58 | } 59 | }) 60 | } 61 | }, 62 | getUserInfo: function(e) { 63 | console.log(e) 64 | app.globalData.userInfo = e.detail.userInfo 65 | this.setData({ 66 | userInfo: e.detail.userInfo, 67 | hasUserInfo: true 68 | }) 69 | wx.navigateTo({ 70 | url: path, 71 | }) 72 | }, 73 | //触摸开始 74 | handletouchstart: function (event) { 75 | this.refreshView.handletouchstart(event) 76 | }, 77 | //触摸移动 78 | handletouchmove: function (event) { 79 | this.refreshView.handletouchmove(event) 80 | }, 81 | //触摸结束 82 | handletouchend: function (event) { 83 | this.refreshView.handletouchend(event) 84 | }, 85 | //触摸取消 86 | handletouchcancel: function (event) { 87 | this.refreshView.handletouchcancel(event) 88 | }, 89 | //页面滚动 90 | onPageScroll: function (event) { 91 | this.refreshView.onPageScroll(event) 92 | }, 93 | onPullDownRefresh:function(){ 94 | setTimeout(() => { this.refreshView.stopPullRefresh()},5000) 95 | } 96 | }) -------------------------------------------------------------------------------- /pages/index/index.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarBackgroundColor": "#ff0000", 3 | "backgroundColor": "#ff0000", 4 | "navigationBarTextStyle": "white", 5 | "usingComponents": { 6 | "refresh-view": "/components/RefreshView/refresh-view" 7 | } 8 | } -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | {{userInfo.nickName}} 9 | 10 | 11 | 12 | {{motto}} 13 | 14 | 15 | -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | /**index.wxss**/ 2 | .userinfo { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | height: 100%; 7 | } 8 | 9 | .userinfo-avatar { 10 | width: 128rpx; 11 | height: 128rpx; 12 | margin: 20rpx; 13 | border-radius: 50%; 14 | } 15 | 16 | .userinfo-nickname { 17 | color: #aaa; 18 | } 19 | 20 | .usermotto { 21 | margin-top: 200px; 22 | } -------------------------------------------------------------------------------- /pages/logs/logs.js: -------------------------------------------------------------------------------- 1 | //logs.js 2 | const util = require('../../utils/util.js') 3 | 4 | Page({ 5 | data: { 6 | logs: [] 7 | }, 8 | onLoad: function () { 9 | this.setData({ 10 | logs: (wx.getStorageSync('logs') || []).map(log => { 11 | return util.formatTime(new Date(log)) 12 | }) 13 | }) 14 | } 15 | }) 16 | -------------------------------------------------------------------------------- /pages/logs/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "查看启动日志" 3 | } -------------------------------------------------------------------------------- /pages/logs/logs.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{index + 1}}. {{log}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /pages/logs/logs.wxss: -------------------------------------------------------------------------------- 1 | .log-list { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 40rpx; 5 | } 6 | .log-item { 7 | margin: 10rpx; 8 | } 9 | -------------------------------------------------------------------------------- /project.config.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": "项目配置文件。", 3 | "packOptions": { 4 | "ignore": [] 5 | }, 6 | "setting": { 7 | "urlCheck": true, 8 | "es6": true, 9 | "postcss": true, 10 | "minified": true, 11 | "newFeature": true 12 | }, 13 | "compileType": "miniprogram", 14 | "libVersion": "2.1.1", 15 | "appid": "wxe03dfd38667402cc", 16 | "projectname": "RefreshDemo", 17 | "isGameTourist": false, 18 | "condition": { 19 | "search": { 20 | "current": -1, 21 | "list": [] 22 | }, 23 | "conversation": { 24 | "current": -1, 25 | "list": [] 26 | }, 27 | "game": { 28 | "currentL": -1, 29 | "list": [] 30 | }, 31 | "miniprogram": { 32 | "current": -1, 33 | "list": [] 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | const formatTime = date => { 2 | const year = date.getFullYear() 3 | const month = date.getMonth() + 1 4 | const day = date.getDate() 5 | const hour = date.getHours() 6 | const minute = date.getMinutes() 7 | const second = date.getSeconds() 8 | 9 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 10 | } 11 | 12 | const formatNumber = n => { 13 | n = n.toString() 14 | return n[1] ? n : '0' + n 15 | } 16 | 17 | module.exports = { 18 | formatTime: formatTime 19 | } 20 | --------------------------------------------------------------------------------