Browse Source

很多功能

master
1549469775 3 years ago
parent
commit
4d1aa7d1b9
  1. 23
      .hbuilderx/launch.json
  2. 22
      api/index.js
  3. 21
      api/request/error.js
  4. 77
      api/request/index.js
  5. 50
      api/request/upload.js
  6. 17
      assets/style/common.scss
  7. 48
      components/Cell/Cell.vue
  8. 22
      components/CellBox/CellBox.vue
  9. 55
      components/niu-tree/anim.vue
  10. 105
      components/niu-tree/niu-tree.vue
  11. 4
      main.js
  12. 15
      manifest.json
  13. 86
      mixins/mescroll-ui/mixin.js
  14. 46
      mock/index - 副本.js
  15. 45
      mock/index.js
  16. 46
      mock/mock - 副本.js
  17. 3
      mock/mock.js
  18. 35
      mock/util.js
  19. 25
      pages.json
  20. 22
      pages/Sub/Pro/SelectTime/SelectTime.vue
  21. 549
      pages/Sub/Pro/SelectTime/TimeSelect.vue
  22. 74
      pages/Sub/Pro/SelectTime/TimeType.vue
  23. 222
      pages/Sub/Pro/SelectTime/day.vue
  24. 111
      pages/Sub/Pro/SelectTime/util.js
  25. 124
      pages/Sub/Socket/Socket.scss
  26. 129
      pages/Sub/Socket/Socket.vue
  27. 497
      pages/Sub/Socket/events.js
  28. 162
      pages/Sub/Socket/socket - 副本.js
  29. 170
      pages/Sub/Socket/socket.js
  30. 81
      pages/Sub/Tabs/Tabs.vue
  31. 241
      pages/Sub/Tabs/cell.vue
  32. 19
      pages/about/about.vue
  33. 99
      pages/index/index.vue
  34. 1
      static/dir.svg
  35. 1
      static/file.svg
  36. 6
      uni_modules/mescroll-uni/changelog.md
  37. 19
      uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css
  38. 400
      uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue
  39. 47
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css
  40. 39
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue
  41. 360
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue
  42. 49
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js
  43. 437
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue
  44. 44
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css
  45. 53
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue
  46. 32
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css
  47. 40
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue
  48. 380
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue
  49. 64
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js
  50. 462
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue
  51. 116
      uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue
  52. 55
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css
  53. 47
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue
  54. 83
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue
  55. 47
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css
  56. 39
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue
  57. 15
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js
  58. 57
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js
  59. 64
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js
  60. 36
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css
  61. 799
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.js
  62. 477
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue
  63. 47
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js
  64. 66
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js
  65. 74
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js
  66. 109
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/mixins.js
  67. 92
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/renderjs.js
  68. 268
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs
  69. 80
      uni_modules/mescroll-uni/package.json
  70. 45
      uni_modules/mescroll-uni/readme.md
  71. 3
      uni_modules/niu-ui/assets/script/util.js
  72. 145
      uni_modules/niu-ui/assets/style/common.scss
  73. 112
      uni_modules/niu-ui/assets/style/global.scss
  74. 32
      uni_modules/niu-ui/components/niu-button/niu-button.vue
  75. 5
      uni_modules/niu-ui/components/niu-card/niu-card.vue
  76. 125
      uni_modules/niu-ui/components/niu-grid-item/niu-grid-item.vue
  77. 55
      uni_modules/niu-ui/components/niu-grid/niu-grid.vue
  78. 141
      uni_modules/niu-ui/components/niu-image/niu-image.vue
  79. 2
      uni_modules/niu-ui/components/niu-navbar/niu-navbar.vue
  80. 14
      uni_modules/niu-ui/components/niu-page/niu-page.vue
  81. 82
      uni_modules/niu-ui/components/niu-tabbar/niu-tabbar.vue
  82. 315
      uni_modules/niu-ui/components/niu-tabs/niu-tabs.vue
  83. 66
      uni_modules/niu-ui/components/niu-upload/addicon.vue
  84. 116
      uni_modules/niu-ui/components/niu-upload/niu-upload.vue
  85. 159
      uni_modules/niu-ui/components/niu-year/niu-year.vue
  86. 15
      uni_modules/niu-ui/components/niu-year/util.js
  87. 20
      uni_modules/niu-ui/extensions/toast.js
  88. 14
      uni_modules/niu-ui/index.js
  89. 2255
      uni_modules/niu-ui/plugins/fuse.js
  90. 14
      uni_modules/niu-ui/util/index.js

23
.hbuilderx/launch.json

@ -2,10 +2,23 @@
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"type": "uniCloud",
"default": {
"launchtype": "local"
}
}
"app-plus" :
{
"launchtype" : "local"
},
"default" :
{
"launchtype" : "local"
},
"h5" :
{
"launchtype" : "local"
},
"mp-weixin" :
{
"launchtype" : "local"
},
"type" : "uniCloud"
}
]
}

22
api/index.js

@ -0,0 +1,22 @@
import {
http,
uploadOne,
uploadImage
} from '@/api/request/index.js';
import {
url_config
} from '@/config/index.js'
const api = {
send(opts = {}){
const {url, method='GET',data={},auth = 'auth',loading = false} = opts
return http(loading)(url,method,data,auth);
},
sendOne(url, method='GET',data={},loading = false,auth = 'auth'){
return http(loading)(url,method,data,auth);
},
uploadPath: () => (url_config + "/oss/upload"),
uploadFile: (path, msg) => uploadImage("/oss/upload", path, msg),
uploadOne: (msg) => uploadOne("/oss/upload", msg),
homeInfo: (data, show = false, loadingText) => http(show, loadingText)(`/ums/api/v1/home/pageInfo`, 'GET', data),
}

21
api/request/error.js

@ -0,0 +1,21 @@
export default function checkError(code, res) {
let result = null
switch (code) {
case 401:
case 202:
let text = ''
if (res) {
text = res && res.data ? res.data.message : '请重新登录'
}
break;
default:
uni.showToast({
icon: 'none',
title: res.data ? res.data.message ? res.data.message : res.data.error ? res.data.error :
'请求失败' : '请求失败'
})
break;
}
return result;
}

77
api/request/index.js

@ -0,0 +1,77 @@
import urlConfig from '@/config/index.js'
import checkError from "./error.js"
import { uploadOne, uploadImage} from "./upload.js"
const {
url_config
} = urlConfig;
export {
uploadOne,
uploadImage
}
export default {
request: http(),
// 带加载框的请求
_request: http(true)
}
function isUrl(url) {
return /^((https|http|ftp|rtsp|mms)?:\/\/)[^\s]+/.test(url)
}
export function http(showLoading, loadingText="正在加载中") {
return function(url, method = 'GET', data = {}, type = 'form') {
let realData = data;
let realUrl = url
var header = {};
if (type == 'json') {
header['Content-Type'] = 'application/json';
} else if (type == 'form') {
header['Content-Type'] = 'application/x-www-form-urlencoded';
}
return new Promise((resolve, reject) => {
if (showLoading) {
uni.showLoading({
title: loadingText,
mask: true
})
}
uni.request({
url: isUrl(realUrl) ? realUrl : (url_config + realUrl),
method,
dataType: 'json',
header,
data: realData,
success: (res) => {
if (showLoading) {
uni.hideLoading();
}
if (!(res.statusCode === 200)) {
checkError(res.statusCode, res);
reject(res)
return
}
if (res.data.code == 200) {
resolve(res.data);
} else {
checkError(res.data.code, res);
reject(res.data)
}
},
fail(err) {
if (showLoading) {
uni.hideLoading();
}
uni.showToast({
icon: 'none',
title: err.errMsg
})
checkError(-1, err);
reject(err)
}
});
})
}
}

50
api/request/upload.js

@ -0,0 +1,50 @@
export function uploadOne(action, msg) {
let that = this;
return new Promise((resolve, reject) => {
uni.chooseImage({
count: 1,
async success(res) {
if (res.tempFilePaths && res.tempFilePaths[0]) {
let file = res.tempFilePaths[0];
let urls = await uploadImage(action, file, msg);
resolve(JSON.parse(urls));
} else {
reject()
}
},
fail(err) {
reject(err)
}
})
})
}
export function uploadImage(action, path, msg = "上传中") {
uni.showLoading({
title: msg
})
return new Promise((resolve, reject) => {
uni.uploadFile({
url: url_config + action,
filePath: path,
fileType: "image",
dataType: 'json',
name: 'file',
header: {},
success: (uploadFileRes) => {
uni.hideLoading();
resolve(uploadFileRes.data)
},
fail: (err) => {
uni.hideLoading();
uni.showToast({
icon: 'none',
title: '上传失败'
})
reject(err)
}
});
})
}

17
assets/style/common.scss

@ -1,13 +1,14 @@
page {
background-color: #FFF8F8;
// background-color: #FFF8F8;
background-color: #F1F1F1;
// min-height: 100%;
height: 100%;
font-size: 28rpx;
line-height: 1.5; //
line-height: 1.2; //
color: #000;
}
page>.tabpage{
page .tabpage{
// 怪异盒模型
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
@ -21,17 +22,17 @@ page {
margin-bottom: 0;
/* #endif */
// 触发BFC
overflow-x: hidden;
overflow-y: scroll;
// overflow-x: hidden;
// overflow-y: scroll;
min-height: 100%;
}
page>.page{
page .page{
// 怪异盒模型
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
// 触发BFC
overflow-x: hidden;
overflow-y: scroll;
// overflow-x: hidden;
// overflow-y: scroll;
min-height: 100%;
}

48
components/Cell/Cell.vue

@ -0,0 +1,48 @@
<template>
<view class="cell component" v-if="$slots.default || title" @click="(e)=>$emit('click',e)">
<view class="cell-button a-ov" @click="toPage()">
<slot>
<text>{{title}}</text>
<text class="cell-button__sub">{{content}}</text>
</slot>
</view>
</view>
</template>
<script>
export default {
name:"Cell",
props: [ 'title', 'content', 'path' ],
data() {
return {
};
},
methods: {
toPage() {
uni.navigateTo({
url: this.path
})
}
},
}
</script>
<style lang="scss" scoped>
.cell.component{
.cell-button{
margin: 10rpx 20rpx;
border-radius: 10rpx;
padding: 20rpx 20rpx;
font-size: 28rpx;
box-shadow: 0px 1px 4px 0px rgba(255, 81, 40, 0.1);
background-color: white;
color: #333333;
}
.cell-button__sub{
font-size: 20rpx;
margin-left: 5rpx;
color: #aaaaaa;
}
}
</style>

22
components/CellBox/CellBox.vue

@ -0,0 +1,22 @@
<template>
<view class="cell-box component">
<slot></slot>
</view>
</template>
<script>
export default {
name:"CellBox",
data() {
return {
};
}
}
</script>
<style lang="scss" scoped>
.cell-box.component{
margin: 20rpx;
}
</style>

55
components/niu-tree/anim.vue

@ -0,0 +1,55 @@
<template>
<view class="content" v-show="value">
<slot></slot>
</view>
</template>
<script>
export default {
props: {
value: {
type: Boolean,
default: false
},
},
watch: {
value: {
//
handler(newVal, oldVal) {
this.$nextTick(async()=>{
console.log(await this.size())
})
},
//
// immediate: true
}
},
methods: {
async size() {
let res = await this.query(".content", this)
return res.height
},
query(selector, that) {
return new Promise(resolve => {
let query = uni.createSelectorQuery()
.in(that)
.select(selector);
query.boundingClientRect(res => {
resolve(res)
}).exec();
})
}
},
}
</script>
<style lang="scss" scoped>
.content {
max-height: 0;
overflow: hidden;
transition: max-height .5s linear;
&.close{
}
}
</style>

105
components/niu-tree/niu-tree.vue

@ -0,0 +1,105 @@
<template>
<view class="niu-tree component">
<view class="niu-tree__item" v-for="(item,index) in list" :key="index">
<view class="niu-tree__title" @click="clickFile(item,index)">
<niu-image right="10rpx" :src="item.isFloder?'/static/dir.svg':'/static/file.svg'" height="35rpx"
width="35rpx" mode=""></niu-image>
<view class="niu-tree__title__text">{{item.title}}</view>
</view>
<view class="niu-tree__sub" :class="[item.isOpen?'':'close']">
<niu-tree :list="item.children"></niu-tree>
</view>
</view>
</view>
</template>
<script>
import anim from "./anim.vue"
export default {
name: "niu-tree",
components: {
anim
},
props: {
list: {
type: Array,
default: () => [{
title: "文件夹1",
isFloder: true,
isOpen: true,
children: [{
title: "文件夹2",
isFloder: true,
isOpen: true,
children: [{
title: "文件夹3",
isFloder: false,
isOpen: false,
children: [
]
}]
}]
}, {
title: "asdsad",
isFloder: true,
isOpen: false,
children: [{
title: "asd",
isFloder: true,
isOpen: false,
children: [{
title: "asdasdas",
isFloder: false,
isOpen: false,
children: [
]
}]
}]
}]
},
},
data() {
return {
};
},
methods: {
clickFile(item) {
item.isOpen = !item.isOpen
}
},
}
</script>
<style lang="scss" scoped>
.niu-tree.component {
line-height: 1;
font-size: 24rpx;
.niu-tree__title {
padding: 10rpx 60rpx;
display: flex;
align-items: center;
&__text {
flex: 1;
width: 0;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
.niu-tree__sub {
margin-left: 20rpx;
height: auto;
overflow: hidden;
transition: height .5s linear;
&.close{
height: 0;
}
}
}
</style>

4
main.js

@ -1,6 +1,6 @@
import Vue from 'vue'
import niuUI from '@/uni_modules/niu-ui'
import mock from '@/mock'
// import mock from '@/mock'.
import App from './App'
Vue.config.productionTip = false
@ -8,7 +8,7 @@ Vue.config.productionTip = false
App.mpType = 'app'
Vue.use(niuUI)
Vue.use(mock)
// Vue.use(mock)
const app = new Vue({
...App

15
manifest.json

@ -8,7 +8,7 @@
/* 5+App */
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
@ -43,7 +43,9 @@
/* ios */
"ios" : {},
/* SDK */
"sdkConfigs" : {}
"sdkConfigs" : {
"ad" : {}
}
}
},
/* */
@ -65,7 +67,12 @@
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics": {
"enable": false
"h5" : {
"devServer" : {
"disableHostCheck" : true
}
},
"uniStatistics" : {
"enable" : false
}
}

86
mixins/mescroll-ui/mixin.js

@ -1,86 +0,0 @@
const allConfig = {};
const allFunc = {};
/**
* 自动下拉刷新
*/
allConfig.common = {
downOption: {
auto: true,
use: true
},
upOption: {
page: {
size: 10 // 每页数据的数量,默认10
},
auto: false,
toTop: {
src: 'https://shidaizhu.oss-cn-shenzhen.aliyuncs.com/app_static/images/backtop.png',
bottom: "20%",
duration: 300,
zIndex: 9990,
right: 50,
safearea: false,
width: 96,
radius: "50%",
left: null
},
empty: {
use: true,
// icon: require('@/static/zichan_empty.png'),
tip: '暂无数据',
// btnText: '发布项目需求',
fixed: false,
top: '40%',
zIndex: 9
},
mescroll: null,
noMoreSize: 5,
textNoMore: '-- 暂无更多数据 --'
}, // 上拉加载的常用配置
};
allFunc.common = {
computed:{
$mTop(){
return this.$n.state.$Top
},
},
onPageScroll(e) {
// if(e.scrollTop<=40){
// this.mescroll.lockDownScroll( false )
// }
// if(e.scrollTop>40){
// this.mescroll.lockDownScroll( true )
// }
},
mounted(){
},
methods: {
mescrollInit(mescroll) {
this.mescroll = mescroll;
},
// mescroll组件初始化的回调,可获取到mescroll对象
downCallback(mescroll) {
this.mescroll.resetUpScroll(false)
},
upCallback(mescroll) {
},
}
}
export default function(name,func="common") {
const config = allConfig[name] ? allConfig[name] : {}
const myFunc = allFunc[func] ? allFunc[func] : {}
return {
data() {
return {
downOption: {}, // 下拉刷新的配置
upOption: {}, // 上拉加载的常用配置
...config,
}
},
...myFunc
}
}

46
mock/index - 副本.js

@ -0,0 +1,46 @@
/**
* Mock数据
*/
this.$u.api.queryElectricityInfoReport
export default {
}
const oldRequest = uni.request;
const opts = {
url: '',
data: '',
header: {},
method: '',
timeout: '',
dataType: '',
responseType: '',
sslVerify: '',
withCredentials: '',
firstIpv4: '',
success: '',
fail: '',
complete: '',
}
uni.request = async (opts) => {
console.log(1231);
const data = await oldRequest({
url: "https://baidu.com",
method: "GET"
})
let [error, res] = data;
console.log(error);
console.log(res);
}
export default {
install(Vue, opts) {
}
}

45
mock/index.js

@ -1,38 +1,17 @@
/**
* Mock数据
*/
const oldRequest = uni.request;
const opts = {
url: '',
data: '',
header: {},
method: '',
timeout: '',
dataType: '',
responseType: '',
sslVerify: '',
withCredentials: '',
firstIpv4: '',
success: '',
fail: '',
complete: '',
}
uni.request = async (opts) => {
console.log(1231);
const data = await oldRequest({
url: "https://baidu.com",
method: "GET"
})
let [error, res] = data;
console.log(error);
console.log(res);
import http from './util';
import * as mockData from './mock';
const home = {
aa: "haha"
}
export default {
install(Vue, opts) {
const api = {
home: ()=> http({res: mockData['home'], isSuccess:true})
};
export default{
install(Vue,opts){
if(Vue.prototype.$n){
Vue.prototype.$n.api = Object.assign(Vue.prototype.$n.api, api)
}
}
}

46
mock/mock - 副本.js

@ -0,0 +1,46 @@
/**
* Mock数据
*/
// this.$u.api.queryElectricityInfoReport
export default {
}
const oldRequest = uni.request;
const opts = {
url: '',
data: '',
header: {},
method: '',
timeout: '',
dataType: '',
responseType: '',
sslVerify: '',
withCredentials: '',
firstIpv4: '',
success: '',
fail: '',
complete: '',
}
uni.request = async (opts) => {
console.log(1231);
const data = await oldRequest({
url: "https://baidu.com",
method: "GET"
})
let [error, res] = data;
console.log(error);
console.log(res);
}
export default {
install(Vue, opts) {
}
}

3
mock/mock.js

@ -0,0 +1,3 @@
export const home = {
aa: "sadsa"
}

35
mock/util.js

@ -0,0 +1,35 @@
const defaultOpts = {
message: "",
res: {},
err: false,
get isSuccess() {
return !![0,1][~~(Math.random()*2)]
},
get duration() {
return ~~(Math.random()*3000+500)
}
}
export default function http(opts) {
let realOpts = {}
Object.keys(defaultOpts).forEach(v => {
if (opts&&opts[v] != undefined) {
realOpts[v] = opts[v]
} else {
realOpts[v] = defaultOpts[v]
}
})
console.log('请等待'+realOpts.duration/1000+'s')
return new Promise((resolve, reject) => {
setTimeout(() => {
if(realOpts.err){
reject(new Error("diy message"));
}
if(realOpts.isSuccess){
resolve({code: 1, message:realOpts.message, data: realOpts.res})
}else{
reject({code: 0,message:realOpts.message, data: realOpts.res})
}
}, realOpts.duration)
})
}

25
pages.json

@ -7,10 +7,31 @@
"navigationBarTitleText": "uni-app"
}
}
,{
"path" : "pages/about/about",
,{
"path" : "pages/Sub/Tabs/Tabs",
"style" :
{
"navigationStyle": "custom",
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/Sub/Socket/Socket",
"style" :
{
"navigationStyle": "custom",
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}
}
,{
"path" : "pages/Sub/Pro/SelectTime/SelectTime",
"style" :
{
"navigationStyle": "custom",
"navigationBarTitleText": "",
"enablePullDownRefresh": false
}

22
pages/Sub/Pro/SelectTime/SelectTime.vue

@ -0,0 +1,22 @@
<template>
<niu-page inStyle="background: white;">
<niu-navbar bg="#FE504F" fixed color="white">
选择日期
</niu-navbar>
<TimeSelect></TimeSelect>
</niu-page>
</template>
<script>
import TimeSelect from "./TimeSelect.vue"
export default {
components: {
TimeSelect
}
}
</script>
<style lang="scss" scoped>
</style>

549
pages/Sub/Pro/SelectTime/TimeSelect.vue

@ -0,0 +1,549 @@
<template>
<view class="">
<view class="card" v-if="timeType">
<view class="card-title">
时间类型
</view>
<view class="timetype">
<view class="timetype_item" :class="[defaultTimeType==0?'active':'']" @click="changeTimeType('年')">
</view>
<view class="timetype_item" :class="[defaultTimeType==1?'active':'']" @click="changeTimeType('月')">
</view>
<view class="timetype_item" :class="[defaultTimeType==2?'active':'']" @click="changeTimeType('日')">
</view>
<!-- <time-type height="300rpx" :value="defaultTimeType" @change="changeTimeType" lineHeight="60rpx">
</time-type> -->
</view>
</view>
<view class="card">
<view class="card-title">
时间选择
<text style="font-size: 24rpx;color: #FA6200;" v-if="defaultTimeType!=0">关联之后起止时间联动,间隔不超过30天</text>
<text style="font-size: 24rpx;color: #FA6200;" v-if="defaultTimeType==0">关联之后起止时间联动,间隔不超过365天</text>
</view>
<view class="card-content">
<view class="time left" :class="[chooseOne==0?'active':'']" @click="clickChoose(0)">
{{startTimeFormat}}
</view>
<view class="label"></view>
<view class="time right" :class="[chooseOne==1?'active':'']" @click="clickChoose(1)">
{{endTimeFormat}}
</view>
</view>
<view style="position: relative;height:20rpx;top: -20rpx;">
<view
style="font-size: 24rpx;color: red;position: absolute;top: 0;left: 0;right: 0;transform: scale(.8);transform-origin: left;">
{{validate}}
</view>
</view>
<view style="display: flex;align-items: center;">
<template>
<view @click="selectToday()" class="btn">
今天
</view>
<view v-if="defaultTimeType!=0" @click="selectOneMonth()" class="btn">
近30天
</view>
<view v-if="defaultTimeType==0" @click="selectOneYear()" class="btn">
近1年
</view>
<view @click="clickBind" class="btn" :class="[isBind?'active':'']">
关联起止时间
</view>
</template>
<view style="text-align: right;flex: 1;width: 0;">
<image style="width: 28rpx;height: 28rpx;"
src="https://www.enesoon-saas-back-test.cn/company/static/icon-clear@2x.png" @tap="clearDate" />
</view>
</view>
<view>
<day :value="defaultTime" @change="changeTime" height="400rpx" ref="day" :current="true"
:type="timeType"></day>
</view>
</view>
<view class="card">
<view class="card-title">
年份选择
</view>
<view class="card-content">
<scroll-view scroll-x>
<view class="year-list">
<view class="year-item" @click="delYearList(item,index)" v-for="(item,index) in sortYearList"
:key="index">
{{item}}
<u-icon name="close-circle-fill" color="#999" size="16rpx" top="-10rpx"></u-icon>
</view>
</view>
</scroll-view>
</view>
<scroll-view scroll-y style="height: 240rpx;">
<view class="year-choose-list">
<view class="year-choose-item" @click="addYearList(item,index)"
:class="[yearList.includes(+item)?'highlight':'', selectYearData==item?'disabled':'']"
v-for="(item,index) in yearAllList" :key="index">
{{item}}
</view>
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import day from "./day.vue"
import {
dateTimeFormat
} from "./util.js"
import TimeType from "./TimeType.vue"
let intevalTime = 30 * 24 * 60 * 60 * 1000; // 30
export default{
components: {
day,
TimeType
},
data() {
return {
yearAllList: [2021, 2020, 2019, 2018],
yearList: [],
isBind: true,
startTime: '',
endTime: '',
chooseOne: -1,
defaultTime: null,
timeType: "year,month,day",
defaultTimeType: 1
};
},
created() {
this.changeTimeType('月')
this.chooseOne = 0
let day = new Date().getTime()
let dayD = new Date(day)
this.endTime = dayD
let dayc = new Date(this.endTime).getTime() - intevalTime
let dayA = new Date(dayc)
dayA.setHours(0,0,0)
this.startTime = dayA
this.defaultTime = this.startTime
this.bindDay()
},
computed: {
validate() {
if(!this.endTime || !this.startTime){
return ""
}
if (this.endTime.getTime() - this.startTime.getTime() < 0) {
return "开始时间不能大于结束时间"
}
if (this.endTime.getTime() - this.startTime.getTime() > intevalTime) {
if(this.defaultTimeType!=0){
return "时间间隔不能大于30天"
}else{
return "时间间隔不能大于365天"
}
}
return ''
},
startTimeFormat() {
if (!this.startTime) {
return "开始时间"
}
// yyyy-MM-dd HH:mm:ss
if (this.defaultTimeType == 0) {
return dateTimeFormat(this.startTime, 'yyyy-MM')
}
if (this.defaultTimeType == 1) {
return dateTimeFormat(this.startTime, 'yyyy-MM-dd')
}
if (this.defaultTimeType == 2) {
return dateTimeFormat(this.startTime, 'yyyy-MM-dd')
}
return dateTimeFormat(this.startTime, 'yyyy-MM-dd')
},
endTimeFormat() {
if (!this.endTime) {
return "结束时间"
}
// yyyy-MM-dd HH:mm:ss
if (this.defaultTimeType == 0) {
return dateTimeFormat(this.endTime, 'yyyy-MM')
}
if (this.defaultTimeType == 1) {
return dateTimeFormat(this.endTime, 'yyyy-MM-dd')
}
if (this.defaultTimeType == 2) {
return dateTimeFormat(this.endTime, 'yyyy-MM-dd')
}
return dateTimeFormat(this.endTime, 'yyyy-MM-dd')
},
selectYearData() {
let year = -1
if (this.startTime && this.endTime) {
let Ayear = +dateTimeFormat(this.startTime, "yyyy")
let Byear = +dateTimeFormat(this.endTime, "yyyy")
if (Ayear == Byear) {
year = Ayear
}
}
return year
},
sortYearList() {
return this.yearList.sort(function(a, b) {
return +b - (+a)
})
},
},
methods: {
clearDate() {
this.startTime = ""
this.endTime = ""
this.yearList = [];
},
delYearList(item) {
// if (!this.year) {
// return
// }
if (item == this.selectYearData) {
uni.showToast({
icon: "none",
title: "该年份不能修改"
})
return
}
let index = this.yearList.indexOf(item)
this.yearList.splice(index, 1)
this.buildDate()
},
addYearList(item) {
// if (!this.year) {
// return
// }
if (item == this.selectYearData) {
uni.showToast({
icon: "none",
title: "该年份不能修改"
})
return
}
let index = this.yearList.indexOf(item)
if (index == -1) {
if (this.startTime && this.endTime) {
let Ayear = +dateTimeFormat(this.startTime, "yyyy")
let Byear = +dateTimeFormat(this.endTime, "yyyy")
if (Ayear != Byear) {
uni.showToast({
icon: "none",
title: "跨年不能选择"
})
return
}
if (this.yearList.length == 3) {
uni.showToast({
icon: "none",
title: "只能选择三个年份"
})
return
}
this.yearList.push(item)
this.buildDate()
} else {
uni.showToast({
icon: "none",
title: "请完善时间范围"
})
}
} else {
this.yearList.splice(index, 1)
this.buildDate()
}
},
clickChoose(type) {
if (type == 0) {
if (this.chooseOne != 0) {
this.chooseOne = 0;
if (this.startTime) {
this.defaultTime = this.startTime
} else {
this.defaultTime = new Date()
}
} else {
this.chooseOne = -1;
}
}
if (type == 1) {
if (this.chooseOne != 1) {
this.chooseOne = 1;
if (this.endTime) {
this.defaultTime = this.endTime
} else {
this.defaultTime = new Date()
}
} else {
this.chooseOne = -1;
}
}
},
changeTimeType(type) {
switch (type) {
case "年":
this.defaultTimeType = 0
intevalTime = 365 * 24 * 60 * 60 * 1000;
this.timeType = 'year,month'
break;
case "月":
intevalTime = 30 * 24 * 60 * 60 * 1000;
this.defaultTimeType = 1
this.timeType = 'year,month,day'
break;
case '日':
intevalTime = 30 * 24 * 60 * 60 * 1000;
this.defaultTimeType = 2
this.timeType = "year,month,day,hour,minute,second"
break;
case 'only-year':
intevalTime = 30 * 24 * 60 * 60 * 1000;
this.defaultTimeType = 3
this.timeType = "year"
break;
default:
this.defaultTimeType = 1
this.timeType = 'year,month,day'
break;
}
},
bindDay() {
if (this.chooseOne == 0 || this.chooseOne == -1) {
if (this.isBind && ((this.startTime && this.endTime && this.isBigger30Day(this.startTime.getTime(),
this.endTime
.getTime())) || (!this.endTime && this.startTime))) {
let day = new Date(dateTimeFormat(this.startTime, "yyyy/MM/dd HH:mm:ss")).getTime() + intevalTime
this.endTime = new Date(day)
}
}
if (this.chooseOne == 1) {
if (this.isBind && ((this.startTime && this.endTime && this.isBigger30Day(this.startTime.getTime(),
this.endTime
.getTime())) || (!this.startTime && this.endTime))) {
let day = new Date(dateTimeFormat(this.endTime, "yyyy/MM/dd HH:mm:ss")).getTime() - intevalTime
this.startTime = new Date(day)
}
}
if (this.startTime && this.endTime) {
let Ayear = +dateTimeFormat(this.startTime, "yyyy")
let Byear = +dateTimeFormat(this.endTime, "yyyy")
if (Ayear == Byear) {
this.yearList = [Ayear]
} else {
this.yearList = []
}
}
this.buildDate()
},
buildDate() {
let format = "yyyy-MM-dd HH:mm:ss"
if(this.defaultTimeType==0){
format = "yyyy-MM"
}
if(this.defaultTimeType==1){
format = "yyyy-MM-dd"
}
// console.log("");
// console.log(dateTimeFormat(this.startTime, format));
// console.log(dateTimeFormat(this.endTime, format));
// console.log(this.yearList);
// console.log(this.defaultTimeType);
if (this.startTime && this.endTime) {
if (this.endTime.getTime() - this.startTime.getTime() < 0) {
uni.showToast({
icon: "none",
title: "开始时间不能大于结束时间"
})
}
if(this.endTime.getTime() - this.startTime.getTime() > intevalTime){
uni.showToast({
icon: "none",
title: "时间间隔大于30天"
})
}
}
},
isBigger30Day(time1, time2) {
return (time2 - time1 > intevalTime) || (time2 - time1 < 0)
},
changeTime(time) {
if (this.chooseOne == 0) {
this.startTime = time
}
if (this.chooseOne == 1) {
this.endTime = time
}
this.bindDay()
if (this.chooseOne == -1) {
uni.showToast({
icon: "none",
title: "请先选中一项"
})
}
},
selectOneYear() {
this.selectToday(1)
this.select(0)
},
selectOneMonth() {
this.selectToday(1)
this.select(0)
},
fixedTime(time,chooseOne){
time.setHours(0,0,0)
return time
},
select(chooseOne) {
if (chooseOne == 0 && this.endTime) {
let day = new Date(dateTimeFormat(this.endTime, "yyyy/MM/dd HH:mm:ss")).getTime() - intevalTime
this.defaultTime = new Date(day)
this.startTime = new Date(day)
// this.$refs.day.setTime(new Date(day))
}
if (chooseOne == 1 && this.startTime) {
let day = new Date(dateTimeFormat(this.startTime, "yyyy/MM/dd HH:mm:ss")).getTime() + intevalTime
this.defaultTime = new Date(day)
this.endTime = new Date(day)
}
this.bindDay()
},
selectToday(chooseOne) {
if (this.chooseOne == -1) {
uni.showToast({
icon: "none",
title: "请先选中一项"
})
return
}
if (this.chooseOne == 0 || chooseOne===0) {
this.startTime = new Date()
}
if (this.chooseOne == 1 || chooseOne===1) {
this.endTime = new Date()
}
this.bindDay()
},
clickBind() {
this.isBind = !this.isBind;
this.bindDay()
}
}
}
</script>
<style lang="scss" scoped>
.card {
margin: 20rpx;
.card-title {
font-size: 32rpx;
font-weight: 500;
color: #333333;
}
.card-content {
margin: 20rpx 0;
line-height: 60rpx;
text-align: center;
font-size: 28rpx;
font-weight: 400;
color: #999999;
display: flex;
align-items: center;
.time {
width: 260rpx;
border-bottom: 1px solid #CECECE;
&.active {
color: #22AB39;
}
}
.label {
color: #666666;
flex: 1;
}
}
}
.btn {
border: 1px solid #C0C0C0;
color: #333333;
padding: 4rpx 15rpx;
border-radius: 10rpx;
color: #000;
+.btn {
margin-left: 10rpx;
}
&.active {
border-color: #22AB39;
color: #22AB39;
}
}
.year-choose-list {
text-align: center;
line-height: 60rpx;
.year-choose-item {
margin: 10rpx 0;
color: #999;
&.highlight {
color: #333;
background: #F5F5F5;
}
&.disabled {
color: #333;
background: #F5F5F5;
}
}
}
.year-list {
white-space: nowrap;
text-align: left;
color: #22AB39;
line-height: 60rpx;
height: 60rpx;
.year-item {
display: inline-block;
padding: 0 20rpx;
}
}
.timetype{
display: flex;
justify-content: space-around;
margin: 20rpx 0;
.timetype_item{
line-height: 1;
padding: 8rpx 42rpx;
border-radius: 4rpx;
font-size: 28rpx;
font-weight: 400;
color: #37363B;
&.active{
background-color: #22AB39;
color: white;
}
}
}
</style>

74
pages/Sub/Pro/SelectTime/TimeType.vue

@ -0,0 +1,74 @@
<template>
<view class="year component">
<picker-view :value="active" :style="[{height:height}]" :indicator-style="`height: ${lineHeight};`" @change="bindChange" class="picker-view">
<picker-view-column>
<view class="item" v-for="(item,index) in list" :key="index">{{item}}</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
export default{
props: {
value: {
type: Number,
default: 0
},
height: {
type: String,
default: "600rpx"
},
lineHeight: {
type: String,
default: "60rpx"
},
},
watch:{
value:{
handler(val,oldVal){
if(this.curIndex == val) return
this.active = [val]
this.curIndex = val
if(val>-1){
this.emitMsg(val)
}
},
deep: true,
immediate: true,
}
},
data() {
return {
curIndex: 0,
active: [],
list: ["年","月","日"],
}
},
methods: {
bindChange(e) {
const val = e.detail.value
this.curIndex = val
this.emitMsg(val)
},
emitMsg(index){
this.$emit("change",this.list[index])
}
},
}
</script>
<style scoped lang="scss">
.year.component {
.picker-view{
background: #f5f5f5;
}
.item {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
}
</style>

222
pages/Sub/Pro/SelectTime/day.vue

@ -0,0 +1,222 @@
<template>
<view class="day component">
<picker-view :value="active" :style="[{height:height}]" :indicator-style="`height: ${lineHeight};`"
@change="bindChange" class="picker-view">
<!-- 使用v-show会有问题 -->
<picker-view-column v-if="typeList.includes('year')">
<view class="item" :style="[{height:lineHeight}]" v-for="(item,index) in yearList" :key="index">
{{item}}
</view>
</picker-view-column>
<picker-view-column v-if="typeList.includes('month')">
<view class="item" :style="[{height:lineHeight}]" v-for="(item,index) in monthList" :key="index">
{{item}}
</view>
</picker-view-column>
<picker-view-column v-if="typeList.includes('day')">
<view class="item" :style="[{height:lineHeight}]" v-for="(item,index) in dayList" :key="index">
{{item}}
</view>
</picker-view-column>
<picker-view-column v-if="typeList.includes('hour')">
<view class="item" :style="[{height:lineHeight}]" v-for="(item,index) in hourList" :key="index">
{{item}}
</view>
</picker-view-column>
<picker-view-column v-if="typeList.includes('minute')">
<view class="item" :style="[{height:lineHeight}]" v-for="(item,index) in minuteList" :key="index">
{{item}}
</view>
</picker-view-column>
<picker-view-column v-if="typeList.includes('second')">
<view class="item" :style="[{height:lineHeight}]" v-for="(item,index) in secondList" :key="index">
{{item}}
</view>
</picker-view-column>
</picker-view>
</view>
</template>
<script>
import {
buildYearData,
buildMonthData,
buildDayData,
buildHourData,
buildMinuteData,
buildSecondData,
bigerZero
} from "./util.js"
export default {
props: {
type: {
type: String,
default: "year,month,day,hour,minute,second"
},
filter: {
type: Boolean,
default: false
},
value: {
type: Date,
default: () => new Date()
},
height: {
type: String,
default: "600rpx"
},
lineHeight: {
type: String,
default: "60rpx"
},
year1: {
type: Number | Array,
default: new Date().getFullYear()
},
interval: {
type: Array,
default: () => [-3, 1]
},
current: {
type: Boolean,
default: true
},
rightnow: {
type: Boolean,
default: false
}
},
data() {
return {
time: null,
active: [0, 0, 0, 0, 0, 0],
yearList: [],
monthList: [],
dayList: [],
hourList: [],
minuteList: [],
secondList: [],
}
},
created() {
},
watch: {
value: {
handler(val, oldVal) {
this.setDefault(val)
},
deep: true,
immediate: false,
},
typeList: {
handler(val, oldVal) {
this.$nextTick(() => {
this.emitChange()
})
},
deep: true,
immediate: false,
}
},
computed: {
typeList() {
return this.type.split(",")
}
},
mounted() {
this.setDefault(this.value)
if (this.rightnow) {
this.emitChange()
}
},
methods: {
setTime(time) {
this.setDefault(time)
this.$nextTick(() => {
this.emitChange()
})
},
setDefault(time) {
this.time = time;
if (typeof this.year1 == "number") {
this.buildInternalYearData()
} else if (Array.isArray(this.year1)) {
this.yearList = this.year1
}
let array = this.buildActiveItem()
this.buildInternalData(array)
this.$nextTick(() => {
this.active = array
})
},
buildActiveItem() {
let array = [0, 0, 0, 0, 0, 0];
let curTime = this.time;
array[0] = bigerZero(curTime.getFullYear() - this.yearList[0])
array[1] = bigerZero(curTime.getMonth())
array[2] = bigerZero(curTime.getDate() - 1)
array[3] = bigerZero(curTime.getHours())
array[4] = bigerZero(curTime.getMinutes())
array[5] = bigerZero(curTime.getSeconds())
return array
},
buildInternalYearData() {
if (typeof this.year1 == "number") {
this.yearList = buildYearData(this.year1, this.interval);
} else if (Array.isArray(this.year1)) {
this.yearList = this.year1
}
},
buildInternalData(active) {
this.monthList = buildMonthData(this.yearList[active[0]])
this.dayList = buildDayData(this.yearList[active[0]], this.monthList[active[1]])
this.hourList = buildHourData()
this.minuteList = buildMinuteData()
this.secondList = buildSecondData()
},
bindChange: function(e) {
const val = e.detail.value
this.active = val;
this.buildInternalYearData()
this.buildInternalData(this.active)
this.emitChange()
},
emitChange() {
let result = []
let year = this.yearList[this.active[0] >= this.yearList.length ? this.yearList.length - 1 : this.active[
0]]
let month = this.monthList[this.active[1] >= this.monthList.length ? this.monthList.length - 1 : this
.active[1]]
let day = this.dayList[this.active[2] >= this.dayList.length ? this.dayList.length - 1 : this.active[2]]
let hour = this.hourList[this.active[3] >= this.hourList.length ? this.hourList.length - 1 : this.active[
3]]
let minute = this.minuteList[this.active[4] >= this.minuteList.length ? this.minuteList.length - 1 : this
.active[4]]
let second = this.secondList[this.active[5] >= this.secondList.length ? this.secondList.length - 1 : this
.active[5]]
result = [year, month, day, hour, minute, second]
let timeStr = result[0] + "/" + result[1] + "/" + result[2] + " " + result[3] + ":" + result[4] + ":" +
result[5]
let time = new Date(timeStr)
this.$emit("change", time)
}
}
}
</script>
<style lang="scss" scoped>
.day.component {
.picker-view {
background: #f5f5f5;
}
.item {
display: flex;
align-items: center;
justify-content: center;
text-align: center;
}
}
</style>

111
pages/Sub/Pro/SelectTime/util.js

@ -0,0 +1,111 @@
export function bigerZero(num){
if(num===undefined){
return 0
}
return num<0?0:num
}
export function buildYearData(year, long) {
let minYear = long[0];
let maxYear = long[1];
if (minYear > maxYear) {
console.log("区间错误");
return
}
let len = maxYear - minYear
let yearList = createArray(len + 1, year + minYear)
return yearList;
}
export function buildHourData(){
return createArray(24);
}
export function buildMinuteData(){
return createArray(60);
}
export function buildSecondData(){
return createArray(60);
}
export function buildMonthData(year) {
return createArray(12, 1);
}
export function buildDayData(year, month) {
let is31Day = [1, 3, 5, 7, 8, 10, 12].indexOf(month) != -1
let leapYear = isLeapYear(year);
if (month !== 2) {
return createArray(is31Day ? 31 : 30, 1);
} else if (leapYear && month === 2) {
return createArray(29, 1);
} else if (!leapYear && month === 2) {
return createArray(28, 1);
}
return []
}
function createArray(length, num = 0) {
return [...Array(length)].map((v, i) => (num + i))
}
/**
* 是否是闰年
* @param {Object} year 年份
*/
function isLeapYear(year) {
if (year % 100 != 0 && year % 4 == 0 || year % 400 == 0) {
return true
}
return false
}
export function dateTimeFormat(date, fmt = 'yyyy-MM-dd HH:mm:ss') {
if (!date) {
return ''
}
if (typeof date === 'string') {
date = date.replace('T', ' ').replace('Z', '');
date = new Date(date.replace(/-/g, '/'))
}
if (typeof date === 'number') {
date = new Date(date)
}
var o = {
'M+': date.getMonth() + 1,
'd+': date.getDate(),
'h+': date.getHours() % 12 === 0 ? 12 : date.getHours() % 12,
'H+': date.getHours(),
'm+': date.getMinutes(),
's+': date.getSeconds(),
'q+': Math.floor((date.getMonth() + 3) / 3),
'S': date.getMilliseconds()
}
var week = {
'0': '\u65e5',
'1': '\u4e00',
'2': '\u4e8c',
'3': '\u4e09',
'4': '\u56db',
'5': '\u4e94',
'6': '\u516d'
}
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (date.getFullYear() + '').substr(4 - RegExp.$1.length))
}
if (/(E+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, ((RegExp.$1.length > 1) ? (RegExp.$1.length > 2 ? '\u661f\u671f' : '\u5468') :
'') +
week[date.getDay() + ''])
}
for (var k in o) {
if (new RegExp('(' + k + ')').test(fmt)) {
fmt = fmt.replace(RegExp.$1, (RegExp.$1.length === 1) ? (o[k]) : (('00' + o[k]).substr(('' + o[k])
.length)))
}
}
return fmt
}

124
pages/Sub/Socket/Socket.scss

@ -0,0 +1,124 @@
.input-box{
display: flex;
align-items: center;
margin: 20rpx;
.input{
flex: 1;
background-color: white;
border-top-left-radius: 10rpx;
border-bottom-left-radius: 10rpx;
height: 70rpx;
line-height: 70rpx;
padding: 0 20rpx;
}
.button{
height: 70rpx;
line-height: 70rpx;
color: white;
font-size: 28rpx;
padding: 0 20rpx;
background-color: #31AB45;
&:last-child{
border-top-right-radius: 10rpx;
border-bottom-right-radius: 10rpx;
}
&.success{
background-color: #f29100;
}
&.del{
background-color: #dd6161;
}
}
}
.talkbox{
.msg{
text-align: center;
margin-bottom: 10rpx;
.text{
background-color: #c8c9cc;
color: white;
display: inline-block;
line-height: 1;
padding: 8rpx 15rpx;
border-radius: 6rpx;
font-size: 24rpx;
}
}
.other{
.talk__info{
margin-right: 10rpx;
}
.talk__content{
display: flex;
justify-content: flex-start;
.text{
background-color: #39B54A;
&::before{
right: 100%;
border-top: 10rpx solid transparent;
border-bottom: 10rpx solid transparent;
border-left: 10rpx solid transparent;
border-right: 10rpx solid #39B54A;
}
}
.error{
margin-left: 10rpx;
}
}
}
.my{
.talk__info{
margin-left: 20rpx;
}
.talk__content{
display: flex;
justify-content: flex-end;
.text{
background-color: #3277FF;
&::before{
left: 100%;
border-top: 10rpx solid transparent;
border-bottom: 10rpx solid transparent;
border-right: 10rpx solid transparent;
border-left: 10rpx solid #3277FF;
}
}
.error{
margin-right: 10rpx;
}
}
}
.talk{
display: flex;
align-items: flex-start;
margin: 0 10rpx 10rpx 10rpx;
.talk__content{
flex: 1;
width: 0;
margin-left: 15rpx;
display: flex;
align-items: center;
.text{
display: inline-block;
border-radius: 8rpx;
color: white;
line-height: 1.3;
padding: 10rpx;
position: relative;
word-break: break-all;
&::before{
content: " ";
display: block;
position: absolute;
}
}
.error{
width: 20rpx;
height: 20rpx;
background-color: #dd6161;
border-radius: 50%;
}
}
}
}

129
pages/Sub/Socket/Socket.vue

@ -0,0 +1,129 @@
<template>
<niu-page>
<niu-navbar fixed color="white" bg="#39b54a">
Socket
</niu-navbar>
<view class="content">
<view class="input-box">
<input type="text" v-model="loginName" :disabled="isLogin" class="input" placeholder="请输入名字">
<view class="button success" @click="connect">连接</view>
<view class="button del" @click="disconnect">断开</view>
</view>
<view class="input-box">
<input type="text" v-model="msg" class="input" placeholder="请输入消息">
<view class="button" @click="submit">提交</view>
</view>
<view class="talkbox" v-for="(item, index) in msgList" :key="index">
<view v-if="item.ev==='msg'" class="msg">
<view class="text">
{{item.data}}
</view>
</view>
<view class="other talk" v-if="item.ev==='text'&&item.user&&item.user.id!=loginName">
<view class="talk__info">
</view>
<view class="talk__content" @click="clickSend(item,index)">
<view class="text">
{{item.data}}
</view>
<view class="error" v-if="item._success==2"></view>
</view>
</view>
<view class="my talk" v-if="item.ev==='text'&&item.user&&item.user.id==loginName">
<view class="talk__content" @click="clickSend(item,index)">
<view class="error" v-if="item._success==2"></view>
<view class="text">
{{item.data}}
</view>
</view>
<view class="talk__info">
</view>
</view>
</view>
</view>
</niu-page>
</template>
<script>
import socket, {
Msg
} from "./socket.js"
export default {
data() {
return {
isLogin: false,
loginName: '',
msg: '',
msgList: []
};
},
onLoad() {
socket.event.on("onOpen", this.onOpen)
socket.event.on("onClose", this.onClose)
socket.event.on("onMessage", this.onMessage)
},
beforeDestroy() {
socket.event.off("onMessage", this.onMessage)
socket.event.off("onOpen", this.onOpen)
socket.close()
},
methods: {
async onClose() {
this.isLogin = false
},
async onOpen() {
this.isLogin = true
socket.send(Msg.create("login", this.loginName, 0))
/**
* 自动发送未成功的消息
*/
// let list = this.msgList.filter(v=>v._success!==1).reverse();
// this.msgList = this.msgList.filter(v=>v._success==1);
// let len = list.length;
// for (var i = len -1; i >= 0; i--) {
// const msg = list[i]
// if(msg&&msg._success!==1){
// await socket.send({...msg, _success: 0})
// }
// }
},
onMessage(data) {
console.log("onMessage", data);
if (data.ev === "text") {
this.msgList.push(data)
}
if (data.ev === "msg") {
this.msgList.push(data)
}
},
async clickSend(msg, i) {
if (msg._success == 2) {
this.msgList.splice(i, 1);
try {
await socket.send(Msg.clone(msg, 0))
} catch (e) {
console.log(e);
}
}
},
connect() {
if(!this.loginName) return
socket.initConnect()
},
disconnect() {
socket.close()
},
async submit() {
if (this.msg == "") return
await socket.send(Msg.create("text", this.msg, 0))
this.msg = ""
},
},
}
</script>
<style lang="scss" scoped>
@import "./Socket.scss";
</style>

497
pages/Sub/Socket/events.js

@ -0,0 +1,497 @@
// Copyright Joyent, Inc. and other Node contributors.
//
// Permission is hereby granted, free of charge, to any person obtaining a
// copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to permit
// persons to whom the Software is furnished to do so, subject to the
// following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
// USE OR OTHER DEALINGS IN THE SOFTWARE.
'use strict';
var R = typeof Reflect === 'object' ? Reflect : null
var ReflectApply = R && typeof R.apply === 'function'
? R.apply
: function ReflectApply(target, receiver, args) {
return Function.prototype.apply.call(target, receiver, args);
}
var ReflectOwnKeys
if (R && typeof R.ownKeys === 'function') {
ReflectOwnKeys = R.ownKeys
} else if (Object.getOwnPropertySymbols) {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target)
.concat(Object.getOwnPropertySymbols(target));
};
} else {
ReflectOwnKeys = function ReflectOwnKeys(target) {
return Object.getOwnPropertyNames(target);
};
}
function ProcessEmitWarning(warning) {
if (console && console.warn) console.warn(warning);
}
var NumberIsNaN = Number.isNaN || function NumberIsNaN(value) {
return value !== value;
}
function EventEmitter() {
EventEmitter.init.call(this);
}
module.exports = EventEmitter;
module.exports.once = once;
// Backwards-compat with node 0.10.x
EventEmitter.EventEmitter = EventEmitter;
EventEmitter.prototype._events = undefined;
EventEmitter.prototype._eventsCount = 0;
EventEmitter.prototype._maxListeners = undefined;
// By default EventEmitters will print a warning if more than 10 listeners are
// added to it. This is a useful default which helps finding memory leaks.
var defaultMaxListeners = 10;
function checkListener(listener) {
if (typeof listener !== 'function') {
throw new TypeError('The "listener" argument must be of type Function. Received type ' + typeof listener);
}
}
Object.defineProperty(EventEmitter, 'defaultMaxListeners', {
enumerable: true,
get: function() {
return defaultMaxListeners;
},
set: function(arg) {
if (typeof arg !== 'number' || arg < 0 || NumberIsNaN(arg)) {
throw new RangeError('The value of "defaultMaxListeners" is out of range. It must be a non-negative number. Received ' + arg + '.');
}
defaultMaxListeners = arg;
}
});
EventEmitter.init = function() {
if (this._events === undefined ||
this._events === Object.getPrototypeOf(this)._events) {
this._events = Object.create(null);
this._eventsCount = 0;
}
this._maxListeners = this._maxListeners || undefined;
};
// Obviously not all Emitters should be limited to 10. This function allows
// that to be increased. Set to zero for unlimited.
EventEmitter.prototype.setMaxListeners = function setMaxListeners(n) {
if (typeof n !== 'number' || n < 0 || NumberIsNaN(n)) {
throw new RangeError('The value of "n" is out of range. It must be a non-negative number. Received ' + n + '.');
}
this._maxListeners = n;
return this;
};
function _getMaxListeners(that) {
if (that._maxListeners === undefined)
return EventEmitter.defaultMaxListeners;
return that._maxListeners;
}
EventEmitter.prototype.getMaxListeners = function getMaxListeners() {
return _getMaxListeners(this);
};
EventEmitter.prototype.emit = function emit(type) {
var args = [];
for (var i = 1; i < arguments.length; i++) args.push(arguments[i]);
var doError = (type === 'error');
var events = this._events;
if (events !== undefined)
doError = (doError && events.error === undefined);
else if (!doError)
return false;
// If there is no 'error' event listener then throw.
if (doError) {
var er;
if (args.length > 0)
er = args[0];
if (er instanceof Error) {
// Note: The comments on the `throw` lines are intentional, they show
// up in Node's output if this results in an unhandled exception.
throw er; // Unhandled 'error' event
}
// At least give some kind of context to the user
var err = new Error('Unhandled error.' + (er ? ' (' + er.message + ')' : ''));
err.context = er;
throw err; // Unhandled 'error' event
}
var handler = events[type];
if (handler === undefined)
return false;
if (typeof handler === 'function') {
ReflectApply(handler, this, args);
} else {
var len = handler.length;
var listeners = arrayClone(handler, len);
for (var i = 0; i < len; ++i)
ReflectApply(listeners[i], this, args);
}
return true;
};
function _addListener(target, type, listener, prepend) {
var m;
var events;
var existing;
checkListener(listener);
events = target._events;
if (events === undefined) {
events = target._events = Object.create(null);
target._eventsCount = 0;
} else {
// To avoid recursion in the case that type === "newListener"! Before
// adding it to the listeners, first emit "newListener".
if (events.newListener !== undefined) {
target.emit('newListener', type,
listener.listener ? listener.listener : listener);
// Re-assign `events` because a newListener handler could have caused the
// this._events to be assigned to a new object
events = target._events;
}
existing = events[type];
}
if (existing === undefined) {
// Optimize the case of one listener. Don't need the extra array object.
existing = events[type] = listener;
++target._eventsCount;
} else {
if (typeof existing === 'function') {
// Adding the second element, need to change to array.
existing = events[type] =
prepend ? [listener, existing] : [existing, listener];
// If we've already got an array, just append.
} else if (prepend) {
existing.unshift(listener);
} else {
existing.push(listener);
}
// Check for listener leak
m = _getMaxListeners(target);
if (m > 0 && existing.length > m && !existing.warned) {
existing.warned = true;
// No error code for this since it is a Warning
// eslint-disable-next-line no-restricted-syntax
var w = new Error('Possible EventEmitter memory leak detected. ' +
existing.length + ' ' + String(type) + ' listeners ' +
'added. Use emitter.setMaxListeners() to ' +
'increase limit');
w.name = 'MaxListenersExceededWarning';
w.emitter = target;
w.type = type;
w.count = existing.length;
ProcessEmitWarning(w);
}
}
return target;
}
EventEmitter.prototype.addListener = function addListener(type, listener) {
return _addListener(this, type, listener, false);
};
EventEmitter.prototype.on = EventEmitter.prototype.addListener;
EventEmitter.prototype.prependListener =
function prependListener(type, listener) {
return _addListener(this, type, listener, true);
};
function onceWrapper() {
if (!this.fired) {
this.target.removeListener(this.type, this.wrapFn);
this.fired = true;
if (arguments.length === 0)
return this.listener.call(this.target);
return this.listener.apply(this.target, arguments);
}
}
function _onceWrap(target, type, listener) {
var state = { fired: false, wrapFn: undefined, target: target, type: type, listener: listener };
var wrapped = onceWrapper.bind(state);
wrapped.listener = listener;
state.wrapFn = wrapped;
return wrapped;
}
EventEmitter.prototype.once = function once(type, listener) {
checkListener(listener);
this.on(type, _onceWrap(this, type, listener));
return this;
};
EventEmitter.prototype.prependOnceListener =
function prependOnceListener(type, listener) {
checkListener(listener);
this.prependListener(type, _onceWrap(this, type, listener));
return this;
};
// Emits a 'removeListener' event if and only if the listener was removed.
EventEmitter.prototype.removeListener =
function removeListener(type, listener) {
var list, events, position, i, originalListener;
checkListener(listener);
events = this._events;
if (events === undefined)
return this;
list = events[type];
if (list === undefined)
return this;
if (list === listener || list.listener === listener) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else {
delete events[type];
if (events.removeListener)
this.emit('removeListener', type, list.listener || listener);
}
} else if (typeof list !== 'function') {
position = -1;
for (i = list.length - 1; i >= 0; i--) {
if (list[i] === listener || list[i].listener === listener) {
originalListener = list[i].listener;
position = i;
break;
}
}
if (position < 0)
return this;
if (position === 0)
list.shift();
else {
spliceOne(list, position);
}
if (list.length === 1)
events[type] = list[0];
if (events.removeListener !== undefined)
this.emit('removeListener', type, originalListener || listener);
}
return this;
};
EventEmitter.prototype.off = EventEmitter.prototype.removeListener;
EventEmitter.prototype.removeAllListeners =
function removeAllListeners(type) {
var listeners, events, i;
events = this._events;
if (events === undefined)
return this;
// not listening for removeListener, no need to emit
if (events.removeListener === undefined) {
if (arguments.length === 0) {
this._events = Object.create(null);
this._eventsCount = 0;
} else if (events[type] !== undefined) {
if (--this._eventsCount === 0)
this._events = Object.create(null);
else
delete events[type];
}
return this;
}
// emit removeListener for all listeners on all events
if (arguments.length === 0) {
var keys = Object.keys(events);
var key;
for (i = 0; i < keys.length; ++i) {
key = keys[i];
if (key === 'removeListener') continue;
this.removeAllListeners(key);
}
this.removeAllListeners('removeListener');
this._events = Object.create(null);
this._eventsCount = 0;
return this;
}
listeners = events[type];
if (typeof listeners === 'function') {
this.removeListener(type, listeners);
} else if (listeners !== undefined) {
// LIFO order
for (i = listeners.length - 1; i >= 0; i--) {
this.removeListener(type, listeners[i]);
}
}
return this;
};
function _listeners(target, type, unwrap) {
var events = target._events;
if (events === undefined)
return [];
var evlistener = events[type];
if (evlistener === undefined)
return [];
if (typeof evlistener === 'function')
return unwrap ? [evlistener.listener || evlistener] : [evlistener];
return unwrap ?
unwrapListeners(evlistener) : arrayClone(evlistener, evlistener.length);
}
EventEmitter.prototype.listeners = function listeners(type) {
return _listeners(this, type, true);
};
EventEmitter.prototype.rawListeners = function rawListeners(type) {
return _listeners(this, type, false);
};
EventEmitter.listenerCount = function(emitter, type) {
if (typeof emitter.listenerCount === 'function') {
return emitter.listenerCount(type);
} else {
return listenerCount.call(emitter, type);
}
};
EventEmitter.prototype.listenerCount = listenerCount;
function listenerCount(type) {
var events = this._events;
if (events !== undefined) {
var evlistener = events[type];
if (typeof evlistener === 'function') {
return 1;
} else if (evlistener !== undefined) {
return evlistener.length;
}
}
return 0;
}
EventEmitter.prototype.eventNames = function eventNames() {
return this._eventsCount > 0 ? ReflectOwnKeys(this._events) : [];
};
function arrayClone(arr, n) {
var copy = new Array(n);
for (var i = 0; i < n; ++i)
copy[i] = arr[i];
return copy;
}
function spliceOne(list, index) {
for (; index + 1 < list.length; index++)
list[index] = list[index + 1];
list.pop();
}
function unwrapListeners(arr) {
var ret = new Array(arr.length);
for (var i = 0; i < ret.length; ++i) {
ret[i] = arr[i].listener || arr[i];
}
return ret;
}
function once(emitter, name) {
return new Promise(function (resolve, reject) {
function errorListener(err) {
emitter.removeListener(name, resolver);
reject(err);
}
function resolver() {
if (typeof emitter.removeListener === 'function') {
emitter.removeListener('error', errorListener);
}
resolve([].slice.call(arguments));
};
eventTargetAgnosticAddListener(emitter, name, resolver, { once: true });
if (name !== 'error') {
addErrorHandlerIfEventEmitter(emitter, errorListener, { once: true });
}
});
}
function addErrorHandlerIfEventEmitter(emitter, handler, flags) {
if (typeof emitter.on === 'function') {
eventTargetAgnosticAddListener(emitter, 'error', handler, flags);
}
}
function eventTargetAgnosticAddListener(emitter, name, listener, flags) {
if (typeof emitter.on === 'function') {
if (flags.once) {
emitter.once(name, listener);
} else {
emitter.on(name, listener);
}
} else if (typeof emitter.addEventListener === 'function') {
// EventTarget does not have `error` event semantics like Node
// EventEmitters, we do not listen for `error` events here.
emitter.addEventListener(name, function wrapListener(arg) {
// IE does not have builtin `{ once: true }` support so we
// have to do it manually.
if (flags.once) {
emitter.removeEventListener(name, wrapListener);
}
listener(arg);
});
} else {
throw new TypeError('The "emitter" argument must be of type EventEmitter. Received type ' + typeof emitter);
}
}

162
pages/Sub/Socket/socket - 副本.js

@ -0,0 +1,162 @@
import EventEmitter from "./events.js"
export class Msg{
data = "" // 数据载体
ev = "" // 事件类型
_success = 0 // 是否发送成功
constructor(ev, data, _success) {
this.ev = ev;
this.data = data;
this._success = _success;
}
}
Msg.create = function(ev, data, _success){
return new Msg(ev, data, _success)
}
class Sc {
#isConnected = false;
#isConnecting = false;
// #url = "ws://82.157.123.54:9010/ajaxchattest"
#url = "ws://127.0.0.1:8888"
#instance = null
#event = null
#isClose = false
#time = 0 // 重连的次数
#maxTime = 10 // 能够重连的次数
#timerID = -1 // 心跳检测
// WILL: 是否需要检测心跳检测没回应的次数断开连接
#heartTimes = 0 // 心跳检测次数
constructor() {
this.#event = new EventEmitter()
}
get event(){
return this.#event;
}
close(){
if(!this.#isConnected) return
if(!this.#instance) return
this.#isClose = true
this.#instance.close()
}
/**
* 心跳检测
*/
heartTime(){
if(!this.#isConnected) return
if(!this.#instance) return
if(this.#timerID!=-1) return
this.send({ping: new Date().getTime()})
this.#timerID = setInterval(()=>{
this.send({ping: new Date().getTime()})
},5000)
}
clearHeartTime(){
clearInterval(this.#timerID)
this.#timerID = -1
}
send(data){
return new Promise((resolve,reject)=>{
const that = this;
if(this.#isConnected){
const encryptData = encodeURIComponent(JSON.stringify(data));
this.#instance.send({
data: encryptData,
success(){
resolve()
},
fail() {
that.#event.emit('onMessage',{...data, _success: 2})
// reject()
resolve()
}
});
}else{
that.#event.emit('onMessage',{...data, _success: 2})
// reject()
resolve()
}
})
}
initConnect(opts = {}, isReConnect) {
if(typeof opts === "boolean"){
isReConnect = opts
opts = {}
}
if(this.#isConnecting&&!isReConnect) return
if (this.#isConnected) return
this.#isConnecting = true
this.#instance = uni.connectSocket({
url: this.#url,
header: opts.header || {},
complete: () => {}
});
const onError = (errMsg)=>{
// console.log(errMsg);
}
const onMessage = (data)=>{
// console.log(data);
}
const onOpen = async ()=>{
}
const onClose = ()=>{
}
this.#event.on('onMessage', onMessage)
this.#event.on('onError', onError)
this.#event.on('onOpen', onOpen)
this.#event.on('onClose', onClose)
this.#instance.onOpen(async ()=>{
this.#isConnected = true
this.#isConnecting = false
this.#time = 0
this.#event.emit('#msg', "客户端已连接")
this.#event.emit('onOpen', isReConnect)
// this.heartTime()
if(isReConnect){
this.send({"reconnect": true})
}
})
this.#instance.onClose(()=>{
this.#isConnected = false
this.#isConnecting = false
this.#instance = null
// this.clearHeartTime()
this.#event.off("onClose", onClose)
this.#event.off("onError", onError)
this.#event.off("onOpen", onOpen)
this.#event.off("onMessage", onMessage)
if(!this.#isClose){
this.#time++
if(this.#time<=this.#maxTime){
this.#isConnecting = true
setTimeout(()=>{
this.#event.emit('#msg', `${this.#time}次重连`)
// 重连
this.initConnect(opts, true)
},2000)
}else{
this.#event.emit('#msg', "客户端已离线,请重试")
this.#event.emit('onClose')
}
}else{
this.isClose = false
this.#event.emit('#msg', "客户端已离线")
this.#event.emit('onClose')
}
})
this.#instance.onError((errMsg)=>{
this.#event.emit('onError', errMsg)
})
this.#instance.onMessage((data)=>{
// let rData = JSON.parse(decodeURIComponent(data.data).slice(0,-66));
let rData = JSON.parse(decodeURIComponent(data.data));
if(rData.hasOwnProperty("ping")) return
this.#event.emit('onMessage',rData)
})
}
}
export default new Sc()

170
pages/Sub/Socket/socket.js

@ -0,0 +1,170 @@
import EventEmitter from "./events.js"
export class Msg{
data = "" // 数据载体
ev = "" // 事件类型
_success = 0 // 是否发送成功
constructor(ev, data, _success) {
this.ev = ev;
this.data = data;
this._success = _success;
}
}
Msg.create = function(ev, data, _success){
return new Msg(ev, data, _success)
}
Msg.clone = function(data, _success){
return new Msg(data.ev, data.data, _success!=undefined ? _success : data._success)
}
class Sc {
#isConnected = false;
#isConnecting = false;
// #url = "ws://82.157.123.54:9010/ajaxchattest"
#url = "ws://127.0.0.1:8888"
#instance = null
#event = null
#isClose = false
#time = 0 // 重连的次数
#maxTime = 10 // 能够重连的次数
#timerID = -1 // 心跳检测
// WILL: 是否需要检测心跳检测没回应的次数断开连接
#heartTimes = 0 // 心跳检测次数
constructor() {
this.#event = new EventEmitter()
}
get event(){
return this.#event;
}
close(){
if(!this.#isConnected) return
if(!this.#instance) return
this.#isClose = true
this.#instance.close()
}
/**
* 心跳检测
*/
heartTime(){
if(!this.#isConnected) return
if(!this.#instance) return
if(this.#timerID!=-1) return
this.send({ping: new Date().getTime()}, true)
this.#timerID = setInterval(()=>{
this.send({ping: new Date().getTime()}, true)
},5000)
}
clearHeartTime(){
clearInterval(this.#timerID)
this.#timerID = -1
}
send(data, isPing){
return new Promise((resolve,reject)=>{
const that = this;
if(!data instanceof Msg || isPing) {
console.error("载体不是msg实例")
return
}
if(this.#isConnected){
const encryptData = encodeURIComponent(JSON.stringify(data));
this.#instance.send({
data: encryptData,
success(){
resolve()
},
fail() {
that.#event.emit('onMessage',Msg.clone(data,2))
// reject()
resolve()
}
});
}else{
that.#event.emit('onMessage',Msg.clone(data,2))
// reject()
resolve()
}
})
}
initConnect(opts = {}, isReConnect) {
if(typeof opts === "boolean"){
isReConnect = opts
opts = {}
}
if(this.#isConnecting&&!isReConnect) return
if (this.#isConnected) return
this.#isConnecting = true
this.#instance = uni.connectSocket({
url: this.#url,
header: opts.header || {},
complete: () => {}
});
const onError = (errMsg)=>{
// console.log(errMsg);
}
const onMessage = (data)=>{
// console.log(data);
}
const onOpen = async ()=>{
}
const onClose = ()=>{
}
this.#event.on('onMessage', onMessage)
this.#event.on('onError', onError)
this.#event.on('onOpen', onOpen)
this.#event.on('onClose', onClose)
this.#instance.onOpen(async ()=>{
this.#isConnected = true
this.#isConnecting = false
this.#time = 0
this.#event.emit('onMessage', Msg.create("msg","客户端已连接",1))
this.#event.emit('onOpen', isReConnect)
// this.heartTime()
if(isReConnect){
this.send(Msg.create("reconnect",true,1))
}
})
this.#instance.onClose(()=>{
this.#isConnected = false
this.#isConnecting = false
this.#instance = null
// this.clearHeartTime()
this.#event.off("onClose", onClose)
this.#event.off("onError", onError)
this.#event.off("onOpen", onOpen)
this.#event.off("onMessage", onMessage)
if(!this.#isClose){
this.#time++
if(this.#time<=this.#maxTime){
this.#isConnecting = true
setTimeout(()=>{
this.#event.emit('onMessage', Msg.create("msg",`${this.#time}次重连`,1))
// 重连
this.initConnect(opts, true)
},2000)
}else{
this.#event.emit('onMessage', Msg.create("msg","客户端已离线,请重试",1))
this.#event.emit('onClose')
}
}else{
this.isClose = false
this.#event.emit('onMessage', Msg.create("msg","客户端已离线",1))
this.#event.emit('onClose')
}
})
this.#instance.onError((errMsg)=>{
this.#event.emit('onError', errMsg)
})
this.#instance.onMessage((data)=>{
// let rData = JSON.parse(decodeURIComponent(data.data).slice(0,-66));
let rData = JSON.parse(decodeURIComponent(data.data));
console.log(`原始数据:`,rData);
if(rData.hasOwnProperty("ping")) return
rData._success = rData._success == 0 ? 1 : rData._success
this.#event.emit('onMessage',rData)
})
}
}
export default new Sc()

81
pages/Sub/Tabs/Tabs.vue

@ -0,0 +1,81 @@
<template>
<niu-page>
<niu-navbar fixed color="white" bg="#39b54a">
Tabs
</niu-navbar>
<niu-tabs :list="colorList" @change="change" v-model="active"></niu-tabs>
<niu-tabs :list="colorList" @change="change" capsule v-model="active"></niu-tabs>
<niu-tabs :list="fiveList" full @change="change" v-model="active"></niu-tabs>
<niu-tabs :list="fiveList" :bar="false" full @change="change" v-model="active">
<template #default="{data, pData, index}">
2{{data.text}}
</template>
<template #bar="{show, barLeft, barWidth}">
<view v-if="show" :style="[
{
transition: 'left .3s ease',
position: 'absolute',
top: '50%',
transform: 'translateY(-50%)',
left: barLeft+barWidth/4 + 'px',
height: '55%',
border: '1px solid red',
borderRadius: '100rpx',
bottom: 0,
width: barWidth/2 + 'px',
}
]"></view>
</template>
</niu-tabs>
<niu-tabs :list="fiveList" @change="change" v-model="active"></niu-tabs>
<cell title="建筑面积(m²)" inputType="digit" oneline v-model="formData.buildingArea" placeholder="请输入"></cell>
</niu-page>
</template>
<script>
import cell from "./cell.vue"
export default {
components: {
cell
},
data() {
return {
formData:{
buildingArea: ''
},
active: 0,
fiveList: [
{ text: "金吒" },
{ text: "木吒" },
{ text: "水吒" },
{ text: "火吒" },
{ text: "土吒" }
],
colorList: [
{ text: "嫣红", color: "#e54d42" },
{ text: "桔橙", color: "#f37b1d" },
{ text: "明黄", color: "#fbbd08" },
{ text: "橄榄", color: "#8dc63f" },
{ text: "森绿", color: "#39b54a" },
{ text: "天青", color: "#1cbbb4" },
{ text: "海蓝", color: "#0081ff" },
{ text: "姹紫", color: "#6739b6" },
{ text: "木槿", color: "#9c26b0" },
{ text: "桃粉", color: "#e03997" },
{ text: "棕褐", color: "#a5673f" },
{ text: "玄灰", color: "#8799a3" },
{ text: "草灰", color: "#aaaaaa" },
{ text: "墨黑", color: "#333333" },
{ text: "雅白", color: "#ffffff" },
]
};
},methods: {
change(index) {
}
},
}
</script>
<style lang="scss">
</style>

241
pages/Sub/Tabs/cell.vue

@ -0,0 +1,241 @@
<template>
<view class="cell" :class="[oneline?'oneline':'manylines', boder?'boder':'', hboder?'hboder':'']" @click.stop="click">
<view class="cell-one">
{{title}}
</view>
<view class="cell-two" :class="[noPaddingBottom?'nobottom':'']">
<slot>
<template v-if="cellType=='input'">
<input class="input" style="flex: 1;height: 100%;" placeholder-style="color: #CCCCCC;font-size: 32rpx" :maxlength="maxlength" v-model="myValue" @input="input" :disabled="cellType=='input'&&!disabled?false:'disabled'" :type="inputType" :placeholder="placeholder">
<text v-if="append" style="margin-left: 10rpx;height: 100%;display: inline-block;">{{append}}</text>
</template>
<template v-if="cellType=='textarea'">
<textarea placeholder-style="color: #CCCCCC;font-size: 30rpx" @input="input" style="width: 100%;height: 140rpx;" v-model="myValue" :disabled="cellType=='textarea'&&!disabled?false:'disabled'"
:placeholder="placeholder" :maxlength="maxlength"/>
<view v-if="textearaNum&&maxlength!=-1" class="textarea-num">
{{valueStr.length}}/{{maxlength}}
</view>
</template>
</slot>
<view v-if="arrow">
<u-icon name="arrow-right" color="#CCCCCC" size="28"></u-icon>
</view>
</view>
<view class="cell-three" v-if="oneline&&$slots.three">
<slot name="three"></slot>
</view>
</view>
</template>
<script>
export default {
computed:{
valueStr(){
return this.value + ''
}
},
props: {
append: {
type: String,
default: ""
},
value: {
type: String | Number,
default: ""
},
noPaddingBottom: {
type: Boolean,
default: false
},
title: {
type: String,
default: ""
},
disabled:{
type: Boolean,
default: false
},
cellType:{
type: String,
default: "input"
},
maxlength:{
type: Number,
default: -1
},
textearaNum: {
type: Boolean,
default: true
},
inputType: {
type: String,
default: "text"
},
boder: {
type: Boolean,
default: true
},
hboder: {
type: Boolean,
default: false
},
oneline: {
type: Boolean,
default: false
},
arrow:{
type: Boolean,
default: false
},
placeholder: {
type: String,
default: ""
}
},
data() {
return {
myValue: ""
}
},
watch: {
value(newValue, oldValue) {
this.myValue = newValue
}
},
created() {
this.myValue = this.value
},
methods: {
input(e){
// 1.
// 2.
// 3.
console.log(e);
let myValue = this.myValue + ''
console.log("before,"+myValue);
// if(myValue.indexOf('.')!=myValue.lastIndexOf(".")){
// let a = myValue.split("")
// a.splice(myValue.lastIndexOf("."),1)
// myValue = a.join('')
// }
// if(myValue.indexOf('.')!=myValue.lastIndexOf(".")){
// let a = myValue.split("")
// a.splice(myValue.lastIndexOf("."),1)
// myValue = a.join('')
// }
// if(myValue.indexOf('.') == -1 && myValue){
// //
// myValue = myValue.replace(/[^(0-9 | \-)]/g,'')
// } else if (myValue.indexOf(".") == 0) {
// myValue = myValue.replace(/[^$#$]/g, "0.");
// myValue = myValue.replace(/\.{2,}/g, ".");
// } else if (myValue.indexOf(".") > 0){
// myValue = myValue.replace(/[^(0-9 | \. | \-)]/g,'')
// }
// if (myValue.lastIndexOf("-") > 0) {
// myValue = myValue.replace(/\-/g,'')
// }
if(isNaN(+myValue)){
}else this.myValue = myValue
//
// if(myValue.indexOf('.') != -1){
// let value = myValue.slice(0,1)
// myValue = +(''+value).replace(/[^0-9]/g,'')+'.'
// }else{
// myValue = +(''+myValue).replace(/[^0-9]/g,'')
// }
// this.myValue = myValue
// console.log("after,"+myValue);
// if(isNaN(+this.myValue)){
// this.myValue = 0
// }
// if(this.myValue> 5){
// this.myValue = 5
// }
console.log("after,"+myValue);
this.$emit("input", this.myValue)
},
click(e) {
this.$emit("click", e)
}
},
}
</script>
<style lang="scss" scoped>
.cell-two .input{
outline: 0;
border: 0;
background: transparent;
text-align: inherit;
}
.cell{
padding-right: 32rpx;
margin-left: 32rpx;
&.boder{
border-bottom: 1px solid #EEEEEE;
}
&.hboder{
padding: 0 32rpx;
margin-left: 0;
border-bottom: 1px solid #EEEEEE;
}
.cell-one{
font-size: 32rpx;
font-weight: 400;
color: #666666;
}
.cell-two{
font-size: 32rpx;
}
.cell-three{
font-size: 32rpx;
}
&.oneline{
display: flex;
align-items: center;
height: 104rpx;
line-height: 104rpx;
.cell-one{
padding-right: 20rpx;
}
.cell-two{
flex: 1;
width: 0;
height: 100%;
text-align: right;
display: flex;
align-items: center;
}
.cell-three{
display: flex;
height: 100%;
align-items: center;
padding-left: 12rpx;
&.no{
padding-left: 0;
}
}
}
&.manylines{
.cell-one{
line-height: 92rpx;
}
.cell-two{
padding-bottom: 32rpx;
}
.cell-two.nobottom{
padding-bottom: 0;
}
}
.textarea-num{
font-size: 18rpx;
padding-top: 6rpx;
font-weight: 400;
text-align: right;
line-height: 1;
color: #999999;
}
}
</style>

19
pages/about/about.vue

@ -1,19 +0,0 @@
<template>
<Page className="about tabpage">
xx
</Page>
</template>
<script>
export default {
data() {
return {
};
}
}
</script>
<style lang="scss">
</style>

99
pages/index/index.vue

@ -1,49 +1,98 @@
<template>
<Page className="home tabpage">
<niu-navbar bg="#FE504F" :block="false" fixed :back="false" color="white">
Niu-UI
<niu-page>
<niu-navbar fixed :back="false" color="white" bg="#39b54a">
爱能森组件库
</niu-navbar>
<mescroll-body :top="$n.$obs.$Top+'px'" @init="mescrollInit" :topbar="false" :bottombar="false"
@down="downCallback" @up="upCallback" :down="downOption" :up="upOption">
<div @click="go('/pages/about/about')">asd</div>
<div>
<niu-input></niu-input>
</div>
</mescroll-body>
</Page>
<view class="title">
组件池
</view>
<view>
<Cell title="Tabs" content="选项卡切换" path="/pages/Sub/Tabs/Tabs"></Cell>
<!-- <Swiper></Swiper> -->
</view>
<view class="title">
模板池
</view>
<view>
<Cell title="Socket" content="socket测试" path="/pages/Sub/Socket/Socket"></Cell>
<Cell title="Canvas" content="海报绘制" path="/pages/Sub/Socket/Socket"></Cell>
</view>
<view class="title">
项目功能Demo
</view>
<view>
<Cell title="时间选择器" content="" path="/pages/Sub/Pro/SelectTime/SelectTime"></Cell>
</view>
<view class="">
<text style="font-size:12rpx;">sadsa</text>
<niu-image circle border="2px solid red" mode="aspectFill" inline rect="100rpx" preview src="https://i.loli.net/2021/08/02/PEKnxSkbAHdtFfi.png"></niu-image>
</view>
<textarea style="width: 100%;" @blur="adjust" :maxlength="maxlength" placeholder="请输入" v-model="text"></textarea>
<div style="text-align: right;">{{text.length}}/{{maxlength}}</div>
{{text}}
<niu-grid :num="4">
<niu-grid-item rect="100px" icon="https://i.loli.net/2021/08/02/PEKnxSkbAHdtFfi.png" text="1231"></niu-grid-item>
<niu-grid-item>11</niu-grid-item>
<niu-grid-item>11</niu-grid-item>
<niu-grid-item>11</niu-grid-item>
<niu-grid-item>11</niu-grid-item>
<niu-grid-item>11</niu-grid-item>
</niu-grid>
<niu-tabbar></niu-tabbar>
<view class="mask" style="position: fixed;left: 0;right: 0;top: 0;bottom: 0;background-color: rgba(#949494, 180);z-index: 1;"></view>
</niu-page>
</template>
<script>
import config from "@/mixins/mescroll-ui/mixin.js";
import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
export default {
mixins: [MescrollMixin, config("common")],
data() {
return {
title: 'Hello'
text: '',
maxlength: 200
}
},
onReady() {
uni.request()
},
methods: {
go(path){
adjust(){
// let value = this.text
// value = value.replace(/\uD83C[\uDF00-\uDFFF]|\uD83D[\uDC00-\uDE4F]/g, "");
// if(value.length>this.maxlength) {
// value = value.slice(0, this.maxlength);
// }
// this.text = value
},
go(path) {
uni.navigateTo({
url: path
})
},
upCallback(mescroll) {
console.log(12);
setTimeout(() => {
mescroll.endBySize(9,10)
}, 1000)
},
}
}
</script>
<style lang="scss" scoped>
.red {
color: red;
.title{
font-size: 36rpx;
padding: 20rpx;
color: #333333;
}
.color-list{
display: flex;
padding: 0 20rpx;
text-align: center;
}
.box-list{
display: flex;
flex-wrap: wrap;
.box-item{
text-align: center;
width: 33.3333%;
height: 100rpx;
line-height: 100rpx;
color: #FFFFFF;
}
}
</style>

1
static/dir.svg

@ -0,0 +1 @@
<svg class="icon" style="width: 1em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M917.661538 945.230769H106.338462A66.953846 66.953846 0 0 1 39.384615 878.276923V149.661538A70.892308 70.892308 0 0 1 110.276923 78.769231h308.381539a92.16 92.16 0 0 1 74.436923 39.384615l52.381538 70.892308a19.298462 19.298462 0 0 0 15.36 7.876923h354.461539A70.892308 70.892308 0 0 1 984.615385 267.815385v610.461538a66.953846 66.953846 0 0 1-66.953847 66.953846z" fill="#FFD031" /><path d="M118.153846 275.692308m39.384616 0l708.923076 0q39.384615 0 39.384616 39.384615l0 0q0 39.384615-39.384616 39.384615l-708.923076 0q-39.384615 0-39.384616-39.384615l0 0q0-39.384615 39.384616-39.384615Z" fill="#FFFFFF" /></svg>

After

Width:  |  Height:  |  Size: 808 B

1
static/file.svg

@ -0,0 +1 @@
<svg class="icon" style="width: 1em;height: 1em;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path d="M0 0h1024v1024H0z" fill="#D8D8D8" fill-opacity="0" /><path d="M213.075 252.963L576.178 177.5c39.61-8.232 78.454 16.99 87.048 56.523l101.436 466.614c8.65 39.79-16.594 79.058-56.384 87.707-0.22 0.048-0.44 0.095-0.66 0.14l-363.102 75.465c-39.61 8.232-78.454-16.99-87.048-56.523L156.032 340.81c-8.65-39.79 16.594-79.058 56.384-87.707 0.22-0.048 0.44-0.095 0.66-0.14z" fill="#5ED68C" /><path d="M565.23 187.733l223.25 218.272V756.44c0 39.814-33.01 72.09-73.728 72.09H329.728c-40.719 0-73.728-32.276-73.728-72.09V259.823c0-39.814 33.01-72.09 73.728-72.09h235.503z" fill="#F9EEAE" /><path d="M566.974 187.733l221.505 216.583 0.001 3.691H661.504c-54.292 0-98.304-44.012-98.304-98.304v-121.97h3.774z" fill="#F2DC66" /><path d="M406.528 702.123c13.141 0 24.405-3.755 33.792-11.264 10.07-8.022 16.384-19.115 19.115-33.451h-19.456c-2.39 9.216-6.486 16.213-12.288 20.821-5.462 4.096-12.63 6.315-21.334 6.315-13.312 0-23.21-4.267-29.525-12.459-5.803-7.68-8.704-18.773-8.704-33.109 0-13.995 2.901-24.917 8.875-32.768 6.485-8.875 16.042-13.141 28.842-13.141 8.534 0 15.531 1.706 20.992 5.461 5.632 3.755 9.387 9.557 11.435 17.579h19.456c-1.877-12.288-7.168-22.187-16.043-29.355-9.216-7.51-21.162-11.264-35.498-11.264-19.456 0-34.304 6.315-44.544 19.285-9.046 11.264-13.483 26.112-13.483 44.203 0 18.432 4.267 33.11 12.97 44.032 9.9 12.63 25.089 19.115 45.398 19.115z m116.736 0c15.53 0 27.648-3.243 36.181-9.387 8.534-6.315 12.8-15.019 12.8-25.941 0-11.264-5.29-19.968-15.872-26.283-4.778-2.73-15.36-6.827-31.914-11.947-11.264-3.584-18.262-6.144-20.822-7.509-5.802-3.072-8.533-7.339-8.533-12.459 0-5.802 2.39-10.069 7.51-12.629 4.095-2.219 9.898-3.243 17.578-3.243 8.875 0 15.701 1.536 20.139 4.95 4.437 3.242 7.68 8.704 9.386 16.213h19.798c-1.195-13.312-6.144-23.21-14.678-29.525-8.021-5.974-19.285-8.875-33.621-8.875-13.141 0-23.893 2.901-32.427 8.875-9.216 6.144-13.653 14.677-13.653 25.429 0 10.752 4.608 18.944 13.995 24.576 3.584 2.048 12.97 5.461 27.989 10.24 13.483 4.096 21.333 6.827 23.723 8.021 7.509 3.755 11.434 8.875 11.434 15.36 0 5.12-2.73 9.046-7.85 12.118-5.12 2.901-12.288 4.437-21.163 4.437-10.07 0-17.408-1.877-22.187-5.29-5.29-3.926-8.533-10.582-9.898-19.798H471.38c0.854 15.53 6.486 26.795 16.726 33.963 8.362 5.802 20.138 8.704 35.157 8.704z m122.368-2.39l43.35-121.856h-21.675l-33.451 99.328h-0.512l-33.45-99.328h-21.675l43.349 121.856h24.064z" fill="#D0B51F" /></svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

6
uni_modules/mescroll-uni/changelog.md

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

19
uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css

@ -1,19 +0,0 @@
.mescroll-body {
position: relative; /* 下拉刷新区域相对自身定位 */
height: auto; /* 不可固定高度,否则overflow:hidden导致无法滑动; 同时使设置的最小高生效,实现列表不满屏仍可下拉*/
overflow: hidden; /* 当有元素写在mescroll-body标签前面时,可遮住下拉刷新区域 */
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
}
/* 使sticky生效: 父元素不能overflow:hidden或者overflow:auto属性 */
.mescroll-body.mescorll-sticky{
overflow: unset !important
}
/* 适配 iPhoneX */
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
.mescroll-safearea {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}

400
uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue

@ -1,400 +0,0 @@
<template>
<view
class="mescroll-body mescroll-render-touch"
:class="{'mescorll-sticky': sticky}"
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}"
@touchstart="wxsBiz.touchstartEvent"
@touchmove="wxsBiz.touchmoveEvent"
@touchend="wxsBiz.touchendEvent"
@touchcancel="wxsBiz.touchendEvent"
:change:prop="wxsBiz.propObserver"
:prop="wxsProp"
>
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
<view class="downwarp-tip">{{downText}}</view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
<!-- #ifdef H5 -->
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
<!-- #endif -->
<!-- 适配iPhoneX -->
<view v-if="safearea" class="mescroll-safearea"></view>
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
<!-- #endif -->
</view>
</template>
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<script src="../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from "../mescroll-uni/wxs/renderjs.js";
export default {
mixins: [renderBiz]
}
</script>
<!-- #endif -->
<script>
// mescroll-uni.js,
import MeScroll from "../mescroll-uni/mescroll-uni.js";
//
import GlobalOption from "../mescroll-uni/mescroll-uni-option.js";
//
import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
//
import MescrollTop from "../mescroll-uni/components/mescroll-top.vue";
// wxs(renderjs)mixins
import WxsMixin from "../mescroll-uni/wxs/mixins.js";
/**
* mescroll-body 基于page滚动的下拉刷新和上拉加载组件, 支持嵌套原生组件, 性能好
* @property {Object} down 下拉刷新的参数配置
* @property {Object} up 上拉加载的参数配置
* @property {Object} i18n 国际化的参数配置
* @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
* @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
* @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
* @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
* @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
* @property {String, Number} height 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
* @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
* @property {Boolean} sticky 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法隐藏
* @event {Function} init 初始化完成的回调
* @event {Function} down 下拉刷新的回调
* @event {Function} up 上拉加载的回调
* @event {Function} emptyclick 点击empty配置的btnText按钮回调
* @event {Function} topclick 点击回到顶部的按钮回调
* @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
* @example <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-body>
*/
export default {
name: 'mescroll-body',
mixins: [WxsMixin],
components: {
MescrollTop
},
props: {
down: Object,
up: Object,
i18n: Object,
top: [String, Number],
topbar: [Boolean, String],
bottom: [String, Number],
safearea: Boolean,
height: [String, Number],
bottombar:{
type: Boolean,
default: true
},
sticky: Boolean
},
data() {
return {
mescroll: {optDown:{},optUp:{}}, // mescroll
downHight: 0, //:
downRate: 0, // (inOffset: rate<1; outOffset: rate>=1)
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // 0loading1loading2,END3,END
isShowEmpty: false, //
isShowToTop: false, //
windowHeight: 0, // 使
windowBottom: 0, // 使
statusBarHeight: 0 //
};
},
computed: {
// mescroll,windowHeight,使
minHeight(){
return this.toPx(this.height || '100%') + 'px'
},
// (px)
numTop() {
return this.toPx(this.top)
},
padTop() {
return this.numTop + 'px';
},
// (px)
numBottom() {
return this.toPx(this.bottom);
},
padBottom() {
return this.numBottom + 'px';
},
//
isDownReset() {
return this.downLoadType === 3 || this.downLoadType === 4;
},
//
transition() {
return this.isDownReset ? 'transform 300ms' : '';
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform使fixed,fixedmescroll
},
//
isDownLoading(){
return this.downLoadType === 3
},
//
downRotate(){
return 'rotate(' + 360 * this.downRate + 'deg)'
},
//
downText(){
if(!this.mescroll) return ""; //
switch (this.downLoadType){
case 1: return this.mescroll.optDown.textInOffset;
case 2: return this.mescroll.optDown.textOutOffset;
case 3: return this.mescroll.optDown.textLoading;
case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
default: return this.mescroll.optDown.textInOffset;
}
}
},
methods: {
//number,rpx,upx,px,% --> px
toPx(num) {
if (typeof num === 'string') {
if (num.indexOf('px') !== -1) {
if (num.indexOf('rpx') !== -1) {
// "10rpx"
num = num.replace('rpx', '');
} else if (num.indexOf('upx') !== -1) {
// "10upx"
num = num.replace('upx', '');
} else {
// "10px"
return Number(num.replace('px', ''));
}
} else if (num.indexOf('%') !== -1) {
// ,windowHeight,"10%"windowHeight10%
let rate = Number(num.replace('%', '')) / 100;
return this.windowHeight * rate;
}
}
return num ? uni.upx2px(Number(num)) : 0;
},
//
emptyClick() {
this.$emit('emptyclick', this.mescroll);
},
//
toTopClick() {
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); //
this.$emit('topclick', this.mescroll); //
}
},
// 使createdmescroll; mountedcssH5
created() {
let vm = this;
let diyOption = {
//
down: {
inOffset() {
vm.downLoadType = 1; // offset (mescroll,)
},
outOffset() {
vm.downLoadType = 2; // offset (mescroll,)
},
onMoving(mescroll, rate, downHight) {
// ,;
vm.downHight = downHight; // (mescroll,)
vm.downRate = rate; // (inOffset: rate<1; outOffset: rate>=1)
},
showLoading(mescroll, downHight) {
vm.downLoadType = 3; // (mescroll,)
vm.downHight = downHight; // (mescroll,)
},
beforeEndDownScroll(mescroll){
vm.downLoadType = 4;
return mescroll.optDown.beforeEndDelay //
},
endDownScroll() {
vm.downLoadType = 4; // (mescroll,)
vm.downHight = 0; // (mescroll,)
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} //
vm.downResetTimer = setTimeout(()=>{ // ,0,inOffsettextInOffset
if(vm.downLoadType === 4) vm.downLoadType = 0
},300)
},
//
callback: function(mescroll) {
vm.$emit('down', mescroll);
}
},
//
up: {
//
showLoading() {
vm.upLoadType = 1;
},
//
showNoMore() {
vm.upLoadType = 2;
},
//
hideUpScroll(mescroll) {
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
},
//
empty: {
onShow(isShow) {
//
vm.isShowEmpty = isShow;
}
},
//
toTop: {
onShow(isShow) {
//
vm.isShowToTop = isShow;
}
},
//
callback: function(mescroll) {
vm.$emit('up', mescroll);
}
}
};
let i18nType = mescrollI18n.getType() //
let i18nOption = {type: i18nType} //
MeScroll.extend(i18nOption, vm.i18n) //
MeScroll.extend(i18nOption, GlobalOption.i18n) //
MeScroll.extend(diyOption, i18nOption[i18nType]); //
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); //
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption, true); // true,body
//
vm.mescroll.i18n = i18nOption;
// initmescroll
vm.$emit('init', vm.mescroll);
//
const sys = uni.getSystemInfoSync();
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使pagescroll,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
if(typeof y === 'string'){
// view (ycss)
setTimeout(()=>{ // view; 使$nextTick
let selector;
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
selector = '#'+y // #. id
}else{
selector = y
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
if(y.indexOf('>>>')!=-1){ // ()
selector = y.split('>>>')[1].trim()
}
// #endif
}
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
if (rect) {
let top = rect.top
top += vm.mescroll.getScrollTop()
uni.pageScrollTo({
scrollTop: top,
duration: t
})
} else{
console.error(selector + ' does not exist');
}
}).exec()
},30)
} else{
// (y)
uni.pageScrollTo({
scrollTop: y,
duration: t
})
}
});
// up.toTop.safearea,vuesafearea
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
//
uni.$on("setMescrollGlobalOption", options=>{
if(!options) return;
let i18nType = options.i18n ? options.i18n.type : null
if(i18nType && vm.mescroll.i18n.type != i18nType){
vm.mescroll.i18n.type = i18nType
mescrollI18n.setType(i18nType)
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
}
if(options.down){
let down = MeScroll.extend({}, options.down)
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
}
if(options.up){
let up = MeScroll.extend({}, options.up)
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
}
})
},
destroyed() {
//
uni.$off("setMescrollGlobalOption")
}
};
</script>
<style>
@import "../mescroll-body/mescroll-body.css";
@import "../mescroll-uni/components/mescroll-down.css";
@import "../mescroll-uni/components/mescroll-up.css";
</style>

47
uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css

@ -1,47 +0,0 @@
/*下拉刷新--标语*/
.mescroll-downwarp .downwarp-slogan{
display: block;
width: 420rpx;
height: 168rpx;
margin: auto;
}
/*下拉刷新--向下进度动画*/
.mescroll-downwarp .downwarp-progress{
display: inline-block;
width: 40rpx;
height: 40rpx;
border: none;
margin: auto;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
background-image: url(https://www.mescroll.com/img/beibei/mescroll-progress.png);
transition: all 300ms;
}
/*下拉刷新--进度条*/
.mescroll-downwarp .downwarp-loading{
display: inline-block;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid #FF8095;
border-bottom-color: transparent;
}
/*下拉刷新--吉祥物*/
.mescroll-downwarp .downwarp-mascot{
position: absolute;
right: 16rpx;
bottom: 0;
width: 100rpx;
height: 100rpx;
background-size: contain;
background-repeat: no-repeat;
animation: animMascot .6s steps(1,end) infinite;
}
@keyframes animMascot {
0% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
25% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb2.png)}
50% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb3.png)}
75% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb4.png)}
100% {background-image: url(https://www.mescroll.com/img/beibei/mescroll-bb1.png)}
}

39
uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue

@ -1,39 +0,0 @@
<!-- 下拉刷新区域 -->
<template>
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
<view class="downwarp-content">
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
<view class="downwarp-mascot"></view>
</view>
</view>
</template>
<script>
export default {
props: {
option: Object , // down
type: Number // inOffset1 outOffset2 showLoading3 endDownScroll4
},
computed: {
// ,propdefault
mOption(){
return this.option || {}
},
//
isDownLoading(){
return this.type === 3
},
//
downRotate(){
return this.type === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
}
}
};
</script>
<style>
@import "../../../mescroll-uni/components/mescroll-down.css";
@import "./mescroll-down.css";
</style>

360
uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue

@ -1,360 +0,0 @@
<template>
<view
class="mescroll-body mescroll-render-touch"
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}"
:class="{'mescorll-sticky': sticky}"
@touchstart="wxsBiz.touchstartEvent"
@touchmove="wxsBiz.touchmoveEvent"
@touchend="wxsBiz.touchendEvent"
@touchcancel="wxsBiz.touchendEvent"
:change:prop="wxsBiz.propObserver"
:prop="wxsProp"
>
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
<view class="downwarp-mascot"></view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
<!-- #ifdef H5 -->
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
<!-- #endif -->
<!-- 适配iPhoneX -->
<view v-if="safearea" class="mescroll-safearea"></view>
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
<!-- #endif -->
</view>
</template>
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
export default {
mixins: [renderBiz]
}
</script>
<!-- #endif -->
<script>
import MeScroll from '../../mescroll-uni/mescroll-uni.js';
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
import GlobalOption from './mescroll-uni-option.js';
export default {
mixins: [WxsMixin],
components: {
MescrollTop
},
data() {
return {
mescroll: null, // mescroll
downHight: 0, //:
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // 0loading1loading2,END3,END
isShowEmpty: false, //
isShowToTop: false, //
windowHeight: 0, // 使
windowBottom: 0, // 使
statusBarHeight: 0 //
};
},
props: {
down: Object, //
up: Object, //
i18n: Object, //
top: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
topbar: [Boolean, String], // top, false (使:,, ,,,)
bottom: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
safearea: Boolean, // bottom, false (iPhoneX使)
height: [String, Number], // mescroll,windowHeight,使
bottombar:{ // TabBar(H5tab)
type: Boolean,
default: true
},
sticky: Boolean // sticky,false; true,mescroll-body,
},
computed: {
// mescroll,windowHeight,使
minHeight(){
return this.toPx(this.height || '100%') + 'px'
},
// (px)
numTop() {
return this.toPx(this.top)
},
padTop() {
return this.numTop + 'px';
},
// (px)
numBottom() {
return this.toPx(this.bottom);
},
padBottom() {
return this.numBottom + 'px';
},
//
isDownReset() {
return this.downLoadType === 3 || this.downLoadType === 4;
},
//
transition() {
return this.isDownReset ? 'transform 300ms' : '';
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform使fixed,fixedmescroll
},
//
isDownLoading(){
return this.downLoadType === 3
},
//
downRotate(){
return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
}
},
methods: {
//number,rpx,upx,px,% --> px
toPx(num) {
if (typeof num === 'string') {
if (num.indexOf('px') !== -1) {
if (num.indexOf('rpx') !== -1) {
// "10rpx"
num = num.replace('rpx', '');
} else if (num.indexOf('upx') !== -1) {
// "10upx"
num = num.replace('upx', '');
} else {
// "10px"
return Number(num.replace('px', ''));
}
} else if (num.indexOf('%') !== -1) {
// ,windowHeight,"10%"windowHeight10%
let rate = Number(num.replace('%', '')) / 100;
return this.windowHeight * rate;
}
}
return num ? uni.upx2px(Number(num)) : 0;
},
//
emptyClick() {
this.$emit('emptyclick', this.mescroll);
},
//
toTopClick() {
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); //
this.$emit('topclick', this.mescroll); //
}
},
// 使createdmescroll; mountedcssH5
created() {
let vm = this;
let diyOption = {
//
down: {
inOffset() {
vm.downLoadType = 1; // offset (mescroll,)
},
outOffset() {
vm.downLoadType = 2; // offset (mescroll,)
},
onMoving(mescroll, rate, downHight) {
// ,;
vm.downHight = downHight; // (mescroll,)
},
showLoading(mescroll, downHight) {
vm.downLoadType = 3; // (mescroll,)
vm.downHight = downHight; // (mescroll,)
},
endDownScroll() {
vm.downLoadType = 4; // (mescroll,)
vm.downHight = 0; // (mescroll,)
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} //
vm.downResetTimer = setTimeout(()=>{ // ,0,inOffsettextInOffset
if(vm.downLoadType === 4) vm.downLoadType = 0
},300)
},
//
callback: function(mescroll) {
vm.$emit('down', mescroll);
}
},
//
up: {
//
showLoading() {
vm.upLoadType = 1;
},
//
showNoMore() {
vm.upLoadType = 2;
},
//
hideUpScroll(mescroll) {
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
},
//
empty: {
onShow(isShow) {
//
vm.isShowEmpty = isShow;
}
},
//
toTop: {
onShow(isShow) {
//
vm.isShowToTop = isShow;
}
},
//
callback: function(mescroll) {
vm.$emit('up', mescroll);
}
}
};
let i18nType = mescrollI18n.getType() //
let i18nOption = {type: i18nType} //
MeScroll.extend(i18nOption, vm.i18n) //
MeScroll.extend(i18nOption, GlobalOption.i18n) //
MeScroll.extend(diyOption, i18nOption[i18nType]); //
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); //
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption, true); // true,body
//
vm.mescroll.i18n = i18nOption;
// initmescroll
vm.$emit('init', vm.mescroll);
//
const sys = uni.getSystemInfoSync();
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使pagescroll,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
if(typeof y === 'string'){
// view (ycss)
setTimeout(()=>{ // view; 使$nextTick
let selector;
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
selector = '#'+y // #. id
}else{
selector = y
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
if(y.indexOf('>>>')!=-1){ // ()
selector = y.split('>>>')[1].trim()
}
// #endif
}
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
if (rect) {
let top = rect.top
top += vm.mescroll.getScrollTop()
uni.pageScrollTo({
scrollTop: top,
duration: t
})
} else{
console.error(selector + ' does not exist');
}
}).exec()
},30)
} else{
// (y)
uni.pageScrollTo({
scrollTop: y,
duration: t
})
}
});
// up.toTop.safearea,vuesafearea
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
//
uni.$on("setMescrollGlobalOption", options=>{
if(!options) return;
let i18nType = options.i18n ? options.i18n.type : null
if(i18nType && vm.mescroll.i18n.type != i18nType){
vm.mescroll.i18n.type = i18nType
mescrollI18n.setType(i18nType)
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
}
if(options.down){
let down = MeScroll.extend({}, options.down)
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
}
if(options.up){
let up = MeScroll.extend({}, options.up)
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
}
})
},
destroyed() {
//
uni.$off("setMescrollGlobalOption")
}
};
</script>
<style>
@import "../../mescroll-body/mescroll-body.css";
@import "../../mescroll-uni/components/mescroll-down.css";
@import "../../mescroll-uni/components/mescroll-up.css";
@import "./components/mescroll-down.css";
</style>

49
uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js

@ -1,49 +0,0 @@
// mescroll-uni和mescroll-body 的全局配置
const GlobalOption = {
down: {
// 其他down的配置参数也可以写,这里只展示了常用的配置:
offset: uni.upx2px(140), // 在列表顶部,下拉大于140upx,松手即可触发下拉刷新的回调
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
},
up: {
// 其他up的配置参数也可以写,这里只展示了常用的配置:
offset: 150, // 距底部多远时,触发upCallback
toTop: {
// 回到顶部按钮,需配置src才显示
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
},
empty: {
use: true, // 是否显示空布局
icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
}
},
// 国际化配置
i18n: {
// 中文
zh: {
up: {
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
empty: {
tip: '~ 暂无相关数据 ~' // 空提示
}
}
},
// 英文
en: {
up: {
textLoading: 'loading ...',
textNoMore: '-- END --',
empty: {
tip: '~ absolutely empty ~'
}
}
}
}
}
export default GlobalOption

437
uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue

@ -1,437 +0,0 @@
<template>
<view class="mescroll-uni-warp">
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
<view class="mescroll-uni-content mescroll-render-touch"
@touchstart="wxsBiz.touchstartEvent"
@touchmove="wxsBiz.touchmoveEvent"
@touchend="wxsBiz.touchendEvent"
@touchcancel="wxsBiz.touchendEvent"
:change:prop="wxsBiz.propObserver"
:prop="wxsProp">
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
<view class="downwarp-mascot"></view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
<!-- #ifdef H5 -->
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
<!-- #endif -->
<!-- 适配iPhoneX -->
<view v-if="safearea" class="mescroll-safearea"></view>
</view>
</scroll-view>
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
<!-- #endif -->
</view>
</template>
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
export default {
mixins: [renderBiz]
}
</script>
<!-- #endif -->
<script>
import MeScroll from '../../mescroll-uni/mescroll-uni.js';
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
import GlobalOption from './mescroll-uni-option.js';
export default {
mixins: [WxsMixin],
components: {
MescrollTop
},
data() {
return {
mescroll: null, // mescroll
viewId: 'id_' + Math.random().toString(36).substr(2,16), // mescrollid(,)
downHight: 0, //:
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // : 0(loading), 1loading, 2,END, 3(,END)
isShowEmpty: false, //
isShowToTop: false, //
scrollTop: 0, //
scrollAnim: false, //
windowTop: 0, // 使
windowBottom: 0, // 使
windowHeight: 0, // 使
statusBarHeight: 0 //
}
},
props: {
down: Object, //
up: Object, //
i18n: Object, //
top: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
topbar: [Boolean, String], // top, false (使:,, ,,,)
bottom: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
safearea: Boolean, // bottom, false (iPhoneX使)
fixed: { // fixedmescroll, true
type: Boolean,
default: true
},
height: [String, Number], // mescroll, ,使fixed. (20, "20rpx", "20px", "20%", rpx, windowHeight)
bottombar:{ // TabBar(H5tab)
type: Boolean,
default: true
},
disableScroll: Boolean //
},
computed: {
// 使fixed (height,使)
isFixed(){
return !this.height && this.fixed
},
// mescroll
scrollHeight(){
if (this.isFixed) {
return "auto"
} else if(this.height){
return this.toPx(this.height) + 'px'
}else{
return "100%"
}
},
// (px)
numTop() {
return this.toPx(this.top)
},
fixedTop() {
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
},
padTop() {
return !this.isFixed ? this.numTop + 'px' : 0
},
// (px)
numBottom() {
return this.toPx(this.bottom)
},
fixedBottom() {
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
},
padBottom() {
return !this.isFixed ? this.numBottom + 'px' : 0
},
//
isDownReset(){
return this.downLoadType===3 || this.downLoadType===4
},
//
transition() {
return this.isDownReset ? 'transform 300ms' : ''
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform使fixed,fixedmescroll
},
//
scrollable(){
if(this.disableScroll) return false
return this.downLoadType===0 || this.isDownReset
},
//
isDownLoading(){
return this.downLoadType === 3
},
//
downRotate(){
return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
}
},
methods: {
//number,rpx,upx,px,% --> px
toPx(num){
if(typeof num === "string"){
if (num.indexOf('px') !== -1) {
if(num.indexOf('rpx') !== -1) { // "10rpx"
num = num.replace('rpx', '');
} else if(num.indexOf('upx') !== -1) { // "10upx"
num = num.replace('upx', '');
} else { // "10px"
return Number(num.replace('px', ''))
}
}else if (num.indexOf('%') !== -1){
// ,windowHeight,"10%"windowHeight10%
let rate = Number(num.replace("%","")) / 100
return this.windowHeight * rate
}
}
return num ? uni.upx2px(Number(num)) : 0
},
//,
scroll(e) {
this.mescroll.scroll(e.detail, () => {
this.$emit('scroll', this.mescroll) // this.mescroll.scrollTop; this.mescroll.isScrollUp
})
},
//
emptyClick() {
this.$emit('emptyclick', this.mescroll)
},
//
toTopClick() {
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); //
this.$emit('topclick', this.mescroll); //
},
// (使,)
setClientHeight() {
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
this.isExec = true; //
this.$nextTick(() => { // dom
this.getClientInfo(data=>{
this.isExec = false;
if (data) {
this.mescroll.setClientHeight(data.height);
} else if (this.clientNum != 3) { // ,dom,,3
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
setTimeout(() => {
this.setClientHeight()
}, this.clientNum * 100)
}
})
})
}
},
//
getClientInfo(success){
let query = uni.createSelectorQuery();
// #ifndef MP-ALIPAY || MP-DINGTALK
query = query.in(this) // in(this),in(this),
// #endif
let view = query.select('#' + this.viewId);
view.boundingClientRect(data => {
success(data)
}).exec();
}
},
// 使createdmescroll; mountedcssH5
created() {
let vm = this;
let diyOption = {
//
down: {
inOffset() {
vm.downLoadType = 1; // offset (mescroll,)
},
outOffset() {
vm.downLoadType = 2; // offset (mescroll,)
},
onMoving(mescroll, rate, downHight) {
// ,;
vm.downHight = downHight; // (mescroll,)
},
showLoading(mescroll, downHight) {
vm.downLoadType = 3; // (mescroll,)
vm.downHight = downHight; // (mescroll,)
},
endDownScroll() {
vm.downLoadType = 4; // (mescroll,)
vm.downHight = 0; // (mescroll,)
vm.downResetTimer && clearTimeout(vm.downResetTimer)
vm.downResetTimer = setTimeout(()=>{ // ,0,便this.transition,iOS
if(vm.downLoadType===4) vm.downLoadType = 0
},300)
},
//
callback: function(mescroll) {
vm.$emit('down', mescroll)
}
},
//
up: {
//
showLoading() {
vm.upLoadType = 1;
},
//
showNoMore() {
vm.upLoadType = 2;
},
//
hideUpScroll(mescroll) {
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
},
//
empty: {
onShow(isShow) { //
vm.isShowEmpty = isShow;
}
},
//
toTop: {
onShow(isShow) { //
vm.isShowToTop = isShow;
}
},
//
callback: function(mescroll) {
vm.$emit('up', mescroll);
// (mescroll)
vm.setClientHeight()
}
}
}
let i18nType = mescrollI18n.getType() //
let i18nOption = {type: i18nType} //
MeScroll.extend(i18nOption, vm.i18n) //
MeScroll.extend(i18nOption, GlobalOption.i18n) //
MeScroll.extend(diyOption, i18nOption[i18nType]); //
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); //
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption);
vm.mescroll.viewId = vm.viewId; // id
//
vm.mescroll.i18n = i18nOption;
// initmescroll
vm.$emit('init', vm.mescroll);
//
const sys = uni.getSystemInfoSync();
if(sys.windowTop) vm.windowTop = sys.windowTop;
if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使scrollview,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
vm.scrollAnim = (t !== 0); // t0,使
if(typeof y === 'string'){
// slotscroll-into-view, 使
vm.getClientInfo(function(rect){
let mescrollTop = rect.top // mescroll
let selector;
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
selector = '#'+y // #. id
}else{
selector = y
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
if(y.indexOf('>>>')!=-1){ // ()
selector = y.split('>>>')[1].trim()
}
// #endif
}
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
if (rect) {
let curY = vm.mescroll.getScrollTop()
let top = rect.top - mescrollTop
top += curY
if(!vm.isFixed) top -= vm.numTop
vm.scrollTop = curY;
vm.$nextTick(function() {
vm.scrollTop = top
})
} else{
console.error(selector + ' does not exist');
}
}).exec()
})
return;
}
let curY = vm.mescroll.getScrollTop()
if (t === 0 || t === 300) { // t使300,使
vm.scrollTop = curY;
vm.$nextTick(function() {
vm.scrollTop = y
})
} else {
vm.mescroll.getStep(curY, y, step => { // t
vm.scrollTop = step
}, t)
}
})
// up.toTop.safearea,vuesafearea
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
//
uni.$on("setMescrollGlobalOption", options=>{
if(!options) return;
let i18nType = options.i18n ? options.i18n.type : null
if(i18nType && vm.mescroll.i18n.type != i18nType){
vm.mescroll.i18n.type = i18nType
mescrollI18n.setType(i18nType)
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
}
if(options.down){
let down = MeScroll.extend({}, options.down)
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
}
if(options.up){
let up = MeScroll.extend({}, options.up)
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
}
})
},
mounted() {
//
this.setClientHeight()
},
destroyed() {
//
uni.$off("setMescrollGlobalOption")
}
}
</script>
<style>
@import "../../mescroll-uni/mescroll-uni.css";
@import "../../mescroll-uni/components/mescroll-down.css";
@import "../../mescroll-uni/components/mescroll-up.css";
@import "./components/mescroll-down.css";
</style>

44
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css

@ -1,44 +0,0 @@
/*下拉刷新--上下箭头*/
.mescroll-downwarp .downwarp-arrow {
display: inline-block;
width: 20px;
height: 20px;
margin: 10px;
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-arrow.png);
background-size: contain;
vertical-align: middle;
transition: all 300ms;
}
/*下拉刷新--旋转进度条*/
.mescroll-downwarp .downwarp-progress{
width: 36px;
height: 36px;
border: none;
margin: auto;
background-size: contain;
animation: progressRotate 0.6s steps(6, start) infinite;
}
@keyframes progressRotate {
0% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
}
16% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
}
32% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
}
48% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
}
64% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
}
80% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
}
100% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
}
}

53
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue

@ -1,53 +0,0 @@
<!-- 下拉刷新区域 -->
<template>
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
<view class="downwarp-content">
<view v-if="isDownLoading" class="downwarp-progress"></view>
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
<view class="downwarp-tip">{{ downText }}</view>
</view>
</view>
</template>
<script>
export default {
props: {
option: Object, // down
type: Number // inOffset1 outOffset2 showLoading3 endDownScroll4
},
computed: {
// ,propdefault
mOption() {
return this.option || {};
},
//
isDownLoading() {
return this.type === 3;
},
//
downRotate() {
return this.type === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
},
//
downText() {
switch (this.type) {
case 1:
return this.mOption.textInOffset;
case 2:
return this.mOption.textOutOffset;
case 3:
return this.mOption.textLoading;
case 4:
return this.mOption.textLoading;
default:
return this.mOption.textInOffset;
}
}
}
};
</script>
<style>
@import '../../../mescroll-uni/components/mescroll-down.css';
@import './mescroll-down.css';
</style>

32
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css

@ -1,32 +0,0 @@
/*上拉加载--旋转进度条*/
.mescroll-upwarp .upwarp-progress {
width: 36px;
height: 36px;
border: none;
margin: auto;
background-size: contain;
animation: progressRotate 0.6s steps(6, start) infinite;
}
@keyframes progressRotate {
0% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
}
16% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress2.png);
}
32% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress3.png);
}
48% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress4.png);
}
64% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress5.png);
}
80% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress6.png);
}
100% {
background-image: url(https://www.mescroll.com/img/xinlang/mescroll-progress1.png);
}
}

40
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue

@ -1,40 +0,0 @@
<!-- 上拉加载区域 -->
<template>
<view class="mescroll-upwarp" :style="{'background':mOption.bgColor,'color':mOption.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="isUpLoading">
<view class="upwarp-progress mescroll-rotate"></view>
<view class="upwarp-tip">{{ mOption.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
</view>
</template>
<script>
export default {
props: {
option: Object, // up
type: Number // 0loading1loading2,END3,END
},
computed: {
// ,propdefault
mOption() {
return this.option || {};
},
//
isUpLoading() {
return this.type === 1;
},
//
isUpNoMore() {
return this.type === 2;
}
}
};
</script>
<style>
@import '../../../mescroll-uni/components/mescroll-up.css';
@import './mescroll-up.css';
</style>

380
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue

@ -1,380 +0,0 @@
<template>
<view
class="mescroll-body mescroll-render-touch"
:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}"
:class="{'mescorll-sticky': sticky}"
@touchstart="wxsBiz.touchstartEvent"
@touchmove="wxsBiz.touchmoveEvent"
@touchend="wxsBiz.touchendEvent"
@touchcancel="wxsBiz.touchendEvent"
:change:prop="wxsBiz.propObserver"
:prop="wxsProp"
>
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<view v-if="isDownLoading" class="downwarp-progress"></view>
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
<view class="downwarp-tip">{{ downText }}</view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
<!-- #ifdef H5 -->
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
<!-- #endif -->
<!-- 适配iPhoneX -->
<view v-if="safearea" class="mescroll-safearea"></view>
<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
<!-- #endif -->
</view>
</template>
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
export default {
mixins: [renderBiz]
}
</script>
<!-- #endif -->
<script>
import MeScroll from '../../mescroll-uni/mescroll-uni.js';
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
import GlobalOption from './mescroll-uni-option.js';
export default {
mixins: [WxsMixin],
components: {
MescrollTop
},
data() {
return {
mescroll: null, // mescroll
downHight: 0, //:
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // 0loading1loading2,END3,END
isShowEmpty: false, //
isShowToTop: false, //
windowHeight: 0, // 使
windowBottom: 0, // 使
statusBarHeight: 0 //
};
},
props: {
down: Object, //
up: Object, //
i18n: Object, //
top: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
topbar: [Boolean, String], // top, false (使:,, ,,,)
bottom: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
safearea: Boolean, // bottom, false (iPhoneX使)
height: [String, Number], // mescroll,windowHeight,使
bottombar:{ // TabBar(H5tab)
type: Boolean,
default: true
},
sticky: Boolean // sticky,false; true,mescroll-body,
},
computed: {
// mescroll,windowHeight,使
minHeight(){
return this.toPx(this.height || '100%') + 'px'
},
// (px)
numTop() {
return this.toPx(this.top)
},
padTop() {
return this.numTop + 'px';
},
// (px)
numBottom() {
return this.toPx(this.bottom);
},
padBottom() {
return this.numBottom + 'px';
},
//
isDownReset() {
return this.downLoadType === 3 || this.downLoadType === 4;
},
//
transition() {
return this.isDownReset ? 'transform 300ms' : '';
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform使fixed,fixedmescroll
},
//
isDownLoading() {
return this.downLoadType === 3;
},
//
downRotate() {
return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
},
//
downText() {
if(!this.mescroll) return "";
switch (this.downLoadType) {
case 1:
return this.mescroll.optDown.textInOffset;
case 2:
return this.mescroll.optDown.textOutOffset;
case 3:
return this.mescroll.optDown.textLoading;
case 4:
return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
default:
return this.mescroll.optDown.textInOffset;
}
}
},
methods: {
//number,rpx,upx,px,% --> px
toPx(num) {
if (typeof num === 'string') {
if (num.indexOf('px') !== -1) {
if (num.indexOf('rpx') !== -1) {
// "10rpx"
num = num.replace('rpx', '');
} else if (num.indexOf('upx') !== -1) {
// "10upx"
num = num.replace('upx', '');
} else {
// "10px"
return Number(num.replace('px', ''));
}
} else if (num.indexOf('%') !== -1) {
// ,windowHeight,"10%"windowHeight10%
let rate = Number(num.replace('%', '')) / 100;
return this.windowHeight * rate;
}
}
return num ? uni.upx2px(Number(num)) : 0;
},
//
emptyClick() {
this.$emit('emptyclick', this.mescroll);
},
//
toTopClick() {
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); //
this.$emit('topclick', this.mescroll); //
}
},
// 使createdmescroll; mountedcssH5
created() {
let vm = this;
let diyOption = {
//
down: {
inOffset() {
vm.downLoadType = 1; // offset (mescroll,)
},
outOffset() {
vm.downLoadType = 2; // offset (mescroll,)
},
onMoving(mescroll, rate, downHight) {
// ,;
vm.downHight = downHight; // (mescroll,)
},
showLoading(mescroll, downHight) {
vm.downLoadType = 3; // (mescroll,)
vm.downHight = downHight; // (mescroll,)
},
beforeEndDownScroll(mescroll){
vm.downLoadType = 4;
return mescroll.optDown.beforeEndDelay //
},
endDownScroll() {
vm.downLoadType = 4; // (mescroll,)
vm.downHight = 0; // (mescroll,)
if(vm.downResetTimer) {clearTimeout(vm.downResetTimer); vm.downResetTimer = null} //
vm.downResetTimer = setTimeout(()=>{ // ,0,inOffsettextInOffset
if(vm.downLoadType === 4) vm.downLoadType = 0
},300)
},
//
callback: function(mescroll) {
vm.$emit('down', mescroll);
}
},
//
up: {
//
showLoading() {
vm.upLoadType = 1;
},
//
showNoMore() {
vm.upLoadType = 2;
},
//
hideUpScroll(mescroll) {
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
},
//
empty: {
onShow(isShow) {
//
vm.isShowEmpty = isShow;
}
},
//
toTop: {
onShow(isShow) {
//
vm.isShowToTop = isShow;
}
},
//
callback: function(mescroll) {
vm.$emit('up', mescroll);
}
}
};
let i18nType = mescrollI18n.getType() //
let i18nOption = {type: i18nType} //
MeScroll.extend(i18nOption, vm.i18n) //
MeScroll.extend(i18nOption, GlobalOption.i18n) //
MeScroll.extend(diyOption, i18nOption[i18nType]); //
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); //
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption, true); // true,body
//
vm.mescroll.i18n = i18nOption;
// initmescroll
vm.$emit('init', vm.mescroll);
//
const sys = uni.getSystemInfoSync();
if (sys.windowHeight) vm.windowHeight = sys.windowHeight;
if (sys.windowBottom) vm.windowBottom = sys.windowBottom;
if (sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使pagescroll,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
if(typeof y === 'string'){
// view (ycss)
setTimeout(()=>{ // view; 使$nextTick
let selector;
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
selector = '#'+y // #. id
}else{
selector = y
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
if(y.indexOf('>>>')!=-1){ // ()
selector = y.split('>>>')[1].trim()
}
// #endif
}
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
if (rect) {
let top = rect.top
top += vm.mescroll.getScrollTop()
uni.pageScrollTo({
scrollTop: top,
duration: t
})
} else{
console.error(selector + ' does not exist');
}
}).exec()
},30)
} else{
// (y)
uni.pageScrollTo({
scrollTop: y,
duration: t
})
}
});
// up.toTop.safearea,vuesafearea
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
//
uni.$on("setMescrollGlobalOption", options=>{
if(!options) return;
let i18nType = options.i18n ? options.i18n.type : null
if(i18nType && vm.mescroll.i18n.type != i18nType){
vm.mescroll.i18n.type = i18nType
mescrollI18n.setType(i18nType)
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
}
if(options.down){
let down = MeScroll.extend({}, options.down)
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
}
if(options.up){
let up = MeScroll.extend({}, options.up)
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
}
})
},
destroyed() {
//
uni.$off("setMescrollGlobalOption")
}
};
</script>
<style>
@import "../../mescroll-uni/mescroll-uni.css";
@import "../../mescroll-uni/components/mescroll-down.css";
@import "../../mescroll-uni/components/mescroll-up.css";
@import "./components/mescroll-down.css";
@import "./components/mescroll-up.css";
</style>

64
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js

@ -1,64 +0,0 @@
// 全局配置
// mescroll-body 和 mescroll-uni 通用
const GlobalOption = {
down: {
// 其他down的配置参数也可以写,这里只展示了常用的配置:
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
},
up: {
// 其他up的配置参数也可以写,这里只展示了常用的配置:
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
toTop: {
// 回到顶部按钮,需配置src才显示
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
},
empty: {
use: true, // 是否显示空布局
icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
}
},
// 国际化配置
i18n: {
// 中文
zh: {
down: {
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
textSuccess: '加载成功', // 加载成功的文本
textErr: '加载失败', // 加载失败的文本
},
up: {
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
empty: {
tip: '~ 空空如也 ~' // 空提示
}
}
},
// 英文
en: {
down: {
textInOffset: 'drop down refresh',
textOutOffset: 'release updates',
textLoading: 'loading ...',
textSuccess: 'loaded successfully',
textErr: 'loading failed'
},
up: {
textLoading: 'loading ...',
textNoMore: '-- END --',
empty: {
tip: '~ absolutely empty ~'
}
}
}
}
}
export default GlobalOption

462
uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue

@ -1,462 +0,0 @@
<template>
<view class="mescroll-uni-warp">
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
<view class="mescroll-uni-content mescroll-render-touch"
@touchstart="wxsBiz.touchstartEvent"
@touchmove="wxsBiz.touchmoveEvent"
@touchend="wxsBiz.touchendEvent"
@touchcancel="wxsBiz.touchendEvent"
:change:prop="wxsBiz.propObserver"
:prop="wxsProp">
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<view v-if="isDownLoading" class="downwarp-progress"></view>
<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
<view class="downwarp-tip">{{ downText }}</view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
<!-- #ifdef H5 -->
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
<!-- #endif -->
<!-- 适配iPhoneX -->
<view v-if="safearea" class="mescroll-safearea"></view>
</view>
</scroll-view>
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
<!-- #endif -->
</view>
</template>
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
export default {
mixins: [renderBiz]
}
</script>
<!-- #endif -->
<script>
import MeScroll from '../../mescroll-uni/mescroll-uni.js';
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
import GlobalOption from './mescroll-uni-option.js';
export default {
mixins: [WxsMixin],
components: {
MescrollTop
},
data() {
return {
mescroll: null, // mescroll
viewId: 'id_' + Math.random().toString(36).substr(2,16), // mescrollid(,)
downHight: 0, //:
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // : 0(loading), 1loading, 2,END, 3(,END)
isShowEmpty: false, //
isShowToTop: false, //
scrollTop: 0, //
scrollAnim: false, //
windowTop: 0, // 使
windowBottom: 0, // 使
windowHeight: 0, // 使
statusBarHeight: 0 //
}
},
props: {
down: Object, //
up: Object, //
i18n: Object, //
top: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
topbar: [Boolean, String], // top, false (使:,, ,,,)
bottom: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
safearea: Boolean, // bottom, false (iPhoneX使)
fixed: { // fixedmescroll, true
type: Boolean,
default: true
},
height: [String, Number], // mescroll, ,使fixed. (20, "20rpx", "20px", "20%", rpx, windowHeight)
bottombar:{ // TabBar(H5tab)
type: Boolean,
default: true
},
disableScroll: Boolean //
},
computed: {
// 使fixed (height,使)
isFixed(){
return !this.height && this.fixed
},
// mescroll
scrollHeight(){
if (this.isFixed) {
return "auto"
} else if(this.height){
return this.toPx(this.height) + 'px'
}else{
return "100%"
}
},
// (px)
numTop() {
return this.toPx(this.top)
},
fixedTop() {
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
},
padTop() {
return !this.isFixed ? this.numTop + 'px' : 0
},
// (px)
numBottom() {
return this.toPx(this.bottom)
},
fixedBottom() {
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
},
padBottom() {
return !this.isFixed ? this.numBottom + 'px' : 0
},
//
isDownReset(){
return this.downLoadType===3 || this.downLoadType===4
},
//
transition() {
return this.isDownReset ? 'transform 300ms' : ''
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform使fixed,fixedmescroll
},
//
scrollable(){
if(this.disableScroll) return false
return this.downLoadType===0 || this.isDownReset
},
//
isDownLoading() {
return this.downLoadType === 3;
},
//
downRotate() {
return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
},
//
downText() {
if(!this.mescroll) return "";
switch (this.downLoadType) {
case 1:
return this.mescroll.optDown.textInOffset;
case 2:
return this.mescroll.optDown.textOutOffset;
case 3:
return this.mescroll.optDown.textLoading;
case 4:
return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
default:
return this.mescroll.optDown.textInOffset;
}
}
},
methods: {
//number,rpx,upx,px,% --> px
toPx(num){
if(typeof num === "string"){
if (num.indexOf('px') !== -1) {
if(num.indexOf('rpx') !== -1) { // "10rpx"
num = num.replace('rpx', '');
} else if(num.indexOf('upx') !== -1) { // "10upx"
num = num.replace('upx', '');
} else { // "10px"
return Number(num.replace('px', ''))
}
}else if (num.indexOf('%') !== -1){
// ,windowHeight,"10%"windowHeight10%
let rate = Number(num.replace("%","")) / 100
return this.windowHeight * rate
}
}
return num ? uni.upx2px(Number(num)) : 0
},
//,
scroll(e) {
this.mescroll.scroll(e.detail, () => {
this.$emit('scroll', this.mescroll) // this.mescroll.scrollTop; this.mescroll.isScrollUp
})
},
//
emptyClick() {
this.$emit('emptyclick', this.mescroll)
},
//
toTopClick() {
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); //
this.$emit('topclick', this.mescroll); //
},
// (使,)
setClientHeight() {
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
this.isExec = true; //
this.$nextTick(() => { // dom
this.getClientInfo(data=>{
this.isExec = false;
if (data) {
this.mescroll.setClientHeight(data.height);
} else if (this.clientNum != 3) { // ,dom,,3
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
setTimeout(() => {
this.setClientHeight()
}, this.clientNum * 100)
}
})
})
}
},
//
getClientInfo(success){
let query = uni.createSelectorQuery();
// #ifndef MP-ALIPAY || MP-DINGTALK
query = query.in(this) // in(this),in(this),
// #endif
let view = query.select('#' + this.viewId);
view.boundingClientRect(data => {
success(data)
}).exec();
}
},
// 使createdmescroll; mountedcssH5
created() {
let vm = this;
let diyOption = {
//
down: {
inOffset() {
vm.downLoadType = 1; // offset (mescroll,)
},
outOffset() {
vm.downLoadType = 2; // offset (mescroll,)
},
onMoving(mescroll, rate, downHight) {
// ,;
vm.downHight = downHight; // (mescroll,)
},
showLoading(mescroll, downHight) {
vm.downLoadType = 3; // (mescroll,)
vm.downHight = downHight; // (mescroll,)
},
beforeEndDownScroll(mescroll){
vm.downLoadType = 4;
return mescroll.optDown.beforeEndDelay //
},
endDownScroll() {
vm.downLoadType = 4; // (mescroll,)
vm.downHight = 0; // (mescroll,)
vm.downResetTimer && clearTimeout(vm.downResetTimer)
vm.downResetTimer = setTimeout(()=>{ // ,0,便this.transition,iOS
if(vm.downLoadType===4) vm.downLoadType = 0
},300)
},
//
callback: function(mescroll) {
vm.$emit('down', mescroll)
}
},
//
up: {
//
showLoading() {
vm.upLoadType = 1;
},
//
showNoMore() {
vm.upLoadType = 2;
},
//
hideUpScroll(mescroll) {
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
},
//
empty: {
onShow(isShow) { //
vm.isShowEmpty = isShow;
}
},
//
toTop: {
onShow(isShow) { //
vm.isShowToTop = isShow;
}
},
//
callback: function(mescroll) {
vm.$emit('up', mescroll);
// (mescroll)
vm.setClientHeight()
}
}
}
let i18nType = mescrollI18n.getType() //
let i18nOption = {type: i18nType} //
MeScroll.extend(i18nOption, vm.i18n) //
MeScroll.extend(i18nOption, GlobalOption.i18n) //
MeScroll.extend(diyOption, i18nOption[i18nType]); //
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); //
let myOption = JSON.parse(JSON.stringify({
'down': vm.down,
'up': vm.up
})) // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption);
vm.mescroll.viewId = vm.viewId; // id
//
vm.mescroll.i18n = i18nOption;
// initmescroll
vm.$emit('init', vm.mescroll);
//
const sys = uni.getSystemInfoSync();
if(sys.windowTop) vm.windowTop = sys.windowTop;
if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使scrollview,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
vm.scrollAnim = (t !== 0); // t0,使
if(typeof y === 'string'){
// slotscroll-into-view, 使
vm.getClientInfo(function(rect){
let mescrollTop = rect.top // mescroll
let selector;
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
selector = '#'+y // #. id
}else{
selector = y
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
if(y.indexOf('>>>')!=-1){ // ()
selector = y.split('>>>')[1].trim()
}
// #endif
}
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
if (rect) {
let curY = vm.mescroll.getScrollTop()
let top = rect.top - mescrollTop
top += curY
if(!vm.isFixed) top -= vm.numTop
vm.scrollTop = curY;
vm.$nextTick(function() {
vm.scrollTop = top
})
} else{
console.error(selector + ' does not exist');
}
}).exec()
})
return;
}
let curY = vm.mescroll.getScrollTop()
if (t === 0 || t === 300) { // t使300,使
vm.scrollTop = curY;
vm.$nextTick(function() {
vm.scrollTop = y
})
} else {
vm.mescroll.getStep(curY, y, step => { // t
vm.scrollTop = step
}, t)
}
})
// up.toTop.safearea,vuesafearea
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
//
uni.$on("setMescrollGlobalOption", options=>{
if(!options) return;
let i18nType = options.i18n ? options.i18n.type : null
if(i18nType && vm.mescroll.i18n.type != i18nType){
vm.mescroll.i18n.type = i18nType
mescrollI18n.setType(i18nType)
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
}
if(options.down){
let down = MeScroll.extend({}, options.down)
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
}
if(options.up){
let up = MeScroll.extend({}, options.up)
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
}
})
},
mounted() {
//
this.setClientHeight()
},
destroyed() {
//
uni.$off("setMescrollGlobalOption")
}
}
</script>
<style>
@import "../../mescroll-uni/mescroll-uni.css";
@import "../../mescroll-uni/components/mescroll-down.css";
@import "../../mescroll-uni/components/mescroll-up.css";
@import "./components/mescroll-down.css";
@import "./components/mescroll-up.css";
</style>

116
uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue

@ -1,116 +0,0 @@
<!--空布局:
遵循easycom规范, 可作为独立的组件, 不使用mescroll的页面也能使用:
<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
-->
<template>
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
<view v-if="tip" class="empty-tip">{{ tip }}</view>
<view v-if="btnText" class="empty-btn" @click="emptyClick">{{ btnText }}</view>
</view>
</template>
<script>
//
import GlobalOption from '../mescroll-uni/mescroll-uni-option.js';
//
import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
export default {
props: {
// empty: GlobalOption.up.empty
option: {
type: Object,
default() {
return {};
}
}
},
// 使computed,option
computed: {
//
icon() {
if (this.option.icon != null) { // 使,
return this.option.icon
} else{
let i18nType = mescrollI18n.getType() //
if (this.option.i18n) {
return this.option.i18n[i18nType].icon
} else{
return GlobalOption.i18n[i18nType].up.empty.icon || GlobalOption.up.empty.icon
}
}
},
//
tip() {
if (this.option.tip != null) { //
return this.option.tip
} else{
let i18nType = mescrollI18n.getType() //
if (this.option.i18n) {
return this.option.i18n[i18nType].tip
} else{
return GlobalOption.i18n[i18nType].up.empty.tip || GlobalOption.up.empty.tip
}
}
},
//
btnText() {
if (this.option.i18n) {
let i18nType = mescrollI18n.getType() //
return this.option.i18n[i18nType].btnText
} else{
return this.option.btnText
}
}
},
methods: {
//
emptyClick() {
this.$emit('emptyclick');
}
}
};
</script>
<style>
/* 无任何数据的空布局 */
.mescroll-empty {
box-sizing: border-box;
width: 100%;
padding: 100rpx 50rpx;
text-align: center;
}
.mescroll-empty.empty-fixed {
z-index: 99;
position: absolute; /*transform会使fixed失效,最终会降级为absolute */
top: 100rpx;
left: 0;
}
.mescroll-empty .empty-icon {
width: 280rpx;
height: 280rpx;
}
.mescroll-empty .empty-tip {
margin-top: 20rpx;
font-size: 24rpx;
color: gray;
}
.mescroll-empty .empty-btn {
display: inline-block;
margin-top: 40rpx;
min-width: 200rpx;
padding: 18rpx;
font-size: 28rpx;
border: 1rpx solid #e04b28;
border-radius: 60rpx;
color: #e04b28;
}
.mescroll-empty .empty-btn:active {
opacity: 0.75;
}
</style>

55
uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css

@ -1,55 +0,0 @@
/* 下拉刷新区域 */
.mescroll-downwarp {
position: absolute;
top: -100%;
left: 0;
width: 100%;
height: 100%;
text-align: center;
}
/* 下拉刷新--内容区,定位于区域底部 */
.mescroll-downwarp .downwarp-content {
position: absolute;
left: 0;
bottom: 0;
width: 100%;
min-height: 60rpx;
padding: 20rpx 0;
text-align: center;
}
/* 下拉刷新--提示文本 */
.mescroll-downwarp .downwarp-tip {
display: inline-block;
font-size: 28rpx;
vertical-align: middle;
margin-left: 16rpx;
/* color: gray; 已在style设置color,此处删去*/
}
/* 下拉刷新--旋转进度条 */
.mescroll-downwarp .downwarp-progress {
display: inline-block;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid gray;
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
vertical-align: middle;
}
/* 旋转动画 */
.mescroll-downwarp .mescroll-rotate {
animation: mescrollDownRotate 0.6s linear infinite;
}
@keyframes mescrollDownRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

47
uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue

@ -1,47 +0,0 @@
<!-- 下拉刷新区域 -->
<template>
<view v-if="mOption.use" class="mescroll-downwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
<view class="downwarp-content">
<view class="downwarp-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mOption.textColor, 'transform':downRotate}"></view>
<view class="downwarp-tip">{{downText}}</view>
</view>
</view>
</template>
<script>
export default {
props: {
option: Object , // down
type: Number, // inOffset1 outOffset2 showLoading3 endDownScroll4
rate: Number // (inOffset: rate<1; outOffset: rate>=1)
},
computed: {
// ,propdefault
mOption(){
return this.option || {}
},
//
isDownLoading(){
return this.type === 3
},
//
downRotate(){
return 'rotate(' + 360 * this.rate + 'deg)'
},
//
downText(){
switch (this.type){
case 1: return this.mOption.textInOffset;
case 2: return this.mOption.textOutOffset;
case 3: return this.mOption.textLoading;
case 4: return this.mOption.textLoading;
default: return this.mOption.textInOffset;
}
}
}
};
</script>
<style>
@import "./mescroll-down.css";
</style>

83
uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue

@ -1,83 +0,0 @@
<!-- 回到顶部的按钮 -->
<template>
<image
v-if="mOption.src"
class="mescroll-totop"
:class="[value ? 'mescroll-totop-in' : 'mescroll-totop-out', {'mescroll-totop-safearea': mOption.safearea}]"
:style="{'z-index':mOption.zIndex, 'left': left, 'right': right, 'bottom':addUnit(mOption.bottom), 'width':addUnit(mOption.width), 'border-radius':addUnit(mOption.radius)}"
:src="mOption.src"
mode="widthFix"
@click="toTopClick"
/>
</template>
<script>
export default {
props: {
// up.toTop
option: Object,
//
value: false
},
computed: {
// ,propdefault
mOption(){
return this.option || {}
},
//
left(){
return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
},
// ()
right() {
return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
}
},
methods: {
addUnit(num){
if(!num) return 0;
if(typeof num === 'number') return num + 'rpx';
return num
},
toTopClick() {
this.$emit('input', false); // 使v-model
this.$emit('click'); //
}
}
};
</script>
<style>
/* 回到顶部的按钮 */
.mescroll-totop {
z-index: 9990;
position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
right: 20rpx;
bottom: 120rpx;
width: 72rpx;
height: auto;
border-radius: 50%;
opacity: 0;
transition: opacity 0.5s; /* 过渡 */
margin-bottom: var(--window-bottom); /* css变量 */
}
/* 适配 iPhoneX */
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
.mescroll-totop-safearea {
margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
}
}
/* 显示 -- 淡入 */
.mescroll-totop-in {
opacity: 1;
}
/* 隐藏 -- 淡出且不接收事件*/
.mescroll-totop-out {
opacity: 0;
pointer-events: none;
}
</style>

47
uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css

@ -1,47 +0,0 @@
/* 上拉加载区域 */
.mescroll-upwarp {
box-sizing: border-box;
min-height: 110rpx;
padding: 30rpx 0;
text-align: center;
clear: both;
}
/*提示文本 */
.mescroll-upwarp .upwarp-tip,
.mescroll-upwarp .upwarp-nodata {
display: inline-block;
font-size: 28rpx;
vertical-align: middle;
/* color: gray; 已在style设置color,此处删去*/
}
.mescroll-upwarp .upwarp-tip {
margin-left: 16rpx;
}
/*旋转进度条 */
.mescroll-upwarp .upwarp-progress {
display: inline-block;
width: 32rpx;
height: 32rpx;
border-radius: 50%;
border: 2rpx solid gray;
border-bottom-color: transparent !important; /*已在style设置border-color,此处需加 !important*/
vertical-align: middle;
}
/* 旋转动画 */
.mescroll-upwarp .mescroll-rotate {
animation: mescrollUpRotate 0.6s linear infinite;
}
@keyframes mescrollUpRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}

39
uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue

@ -1,39 +0,0 @@
<!-- 上拉加载区域 -->
<template>
<view class="mescroll-upwarp" :style="{'background-color':mOption.bgColor,'color':mOption.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="isUpLoading">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mOption.textColor}"></view>
<view class="upwarp-tip">{{ mOption.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="isUpNoMore" class="upwarp-nodata">{{ mOption.textNoMore }}</view>
</view>
</template>
<script>
export default {
props: {
option: Object, // up
type: Number // 0loading1loading2
},
computed: {
// ,propdefault
mOption() {
return this.option || {};
},
//
isUpLoading() {
return this.type === 1;
},
//
isUpNoMore() {
return this.type === 2;
}
}
};
</script>
<style>
@import './mescroll-up.css';
</style>

15
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js

@ -1,15 +0,0 @@
// 国际化工具类
const mescrollI18n = {
// 默认语言
def: "zh",
// 获取当前语言类型
getType(){
return uni.getStorageSync("mescroll-i18n") || this.def
},
// 设置当前语言类型
setType(type){
uni.setStorageSync("mescroll-i18n", type)
}
}
export default mescrollI18n

57
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js

@ -1,57 +0,0 @@
// mescroll-body 和 mescroll-uni 通用
const MescrollMixin = {
data() {
return {
mescroll: null //mescroll实例对象
}
},
// 注册系统自带的下拉刷新 (配置down.native为true时生效, 还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
onPullDownRefresh(){
this.mescroll && this.mescroll.onPullDownRefresh();
},
// 注册列表滚动事件,用于判定在顶部可下拉刷新,在指定位置可显示隐藏回到顶部按钮 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
onPageScroll(e) {
this.mescroll && this.mescroll.onPageScroll(e);
},
// 注册滚动到底部的事件,用于上拉加载 (此方法为页面生命周期,无法在子组件中触发, 仅在mescroll-body生效)
onReachBottom() {
this.mescroll && this.mescroll.onReachBottom();
},
methods: {
// mescroll组件初始化的回调,可获取到mescroll对象
mescrollInit(mescroll) {
this.mescroll = mescroll;
this.mescrollInitByRef(); // 兼容字节跳动小程序
},
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
mescrollInitByRef() {
if(!this.mescroll || !this.mescroll.resetUpScroll){
let mescrollRef = this.$refs.mescrollRef;
if(mescrollRef) this.mescroll = mescrollRef.mescroll
}
},
// 下拉刷新的回调 (mixin默认resetUpScroll)
downCallback() {
if(this.mescroll.optUp.use){
this.mescroll.resetUpScroll()
}else{
setTimeout(()=>{
this.mescroll.endSuccess();
}, 500)
}
},
// 上拉加载的回调
upCallback() {
// mixin默认延时500自动结束加载
setTimeout(()=>{
this.mescroll.endErr();
}, 500)
}
},
mounted() {
this.mescrollInitByRef(); // 兼容字节跳动小程序, 避免未设置@init或@init此时未能取到ref的情况
}
}
export default MescrollMixin;

64
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js

@ -1,64 +0,0 @@
// 全局配置
// mescroll-body 和 mescroll-uni 通用
const GlobalOption = {
down: {
// 其他down的配置参数也可以写,这里只展示了常用的配置:
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
},
up: {
// 其他up的配置参数也可以写,这里只展示了常用的配置:
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
toTop: {
// 回到顶部按钮,需配置src才显示
src: "https://www.mescroll.com/img/mescroll-totop.png", // 图片路径 (建议放入static目录, 如 /static/img/mescroll-totop.png )
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000px
right: 20, // 到右边的距离, 默认20 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
bottom: 120, // 到底部的距离, 默认120 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
width: 72 // 回到顶部图标的宽度, 默认72 (支持"20rpx", "20px", "20%"格式的值, 纯数字则默认单位rpx)
},
empty: {
use: true, // 是否显示空布局
icon: "https://www.mescroll.com/img/mescroll-empty.png" // 图标路径 (建议放入static目录, 如 /static/img/mescroll-empty.png )
}
},
// 国际化配置
i18n: {
// 中文
zh: {
down: {
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
textSuccess: '加载成功', // 加载成功的文本
textErr: '加载失败', // 加载失败的文本
},
up: {
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
empty: {
tip: '~ 空空如也 ~' // 空提示
}
}
},
// 英文
en: {
down: {
textInOffset: 'drop down refresh',
textOutOffset: 'release updates',
textLoading: 'loading ...',
textSuccess: 'loaded successfully',
textErr: 'loading failed'
},
up: {
textLoading: 'loading ...',
textNoMore: '-- END --',
empty: {
tip: '~ absolutely empty ~'
}
}
}
}
}
export default GlobalOption

36
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css

@ -1,36 +0,0 @@
.mescroll-uni-warp{
height: 100%;
}
.mescroll-uni-content{
height: 100%;
}
.mescroll-uni {
position: relative;
width: 100%;
height: 100%;
min-height: 200rpx;
overflow-y: auto;
box-sizing: border-box; /* 避免设置padding出现双滚动条的问题 */
}
/* 定位的方式固定高度 */
.mescroll-uni-fixed{
z-index: 1;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
width: auto; /* 使right生效 */
height: auto; /* 使bottom生效 */
}
/* 适配 iPhoneX */
@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
.mescroll-safearea {
padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);
}
}

799
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.js

@ -1,799 +0,0 @@
/* mescroll
* version 1.3.7
* 2021-04-12 wenju
* https://www.mescroll.com
*/
export default function MeScroll(options, isScrollBody) {
let me = this;
me.version = '1.3.7'; // mescroll版本号
me.options = options || {}; // 配置
me.isScrollBody = isScrollBody || false; // 滚动区域是否为原生页面滚动; 默认为scroll-view
me.isDownScrolling = false; // 是否在执行下拉刷新的回调
me.isUpScrolling = false; // 是否在执行上拉加载的回调
let hasDownCallback = me.options.down && me.options.down.callback; // 是否配置了down的callback
// 初始化下拉刷新
me.initDownScroll();
// 初始化上拉加载,则初始化
me.initUpScroll();
// 自动加载
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
// 自动触发下拉刷新 (只有配置了down的callback才自动触发下拉刷新)
if ((me.optDown.use || me.optDown.native) && me.optDown.auto && hasDownCallback) {
if (me.optDown.autoShowLoading) {
me.triggerDownScroll(); // 显示下拉进度,执行下拉回调
} else {
me.optDown.callback && me.optDown.callback(me); // 不显示下拉进度,直接执行下拉回调
}
}
// 自动触发上拉加载
if(!me.isUpAutoLoad){ // 部分小程序(头条小程序)emit是异步, 会导致isUpAutoLoad判断有误, 先延时确保先执行down的callback,再执行up的callback
setTimeout(function(){
me.optUp.use && me.optUp.auto && !me.isUpAutoLoad && me.triggerUpScroll();
},100)
}
}, 30); // 需让me.optDown.inited和me.optUp.inited先执行
}
/* 配置参数:下拉刷新 */
MeScroll.prototype.extendDownScroll = function(optDown) {
// 下拉刷新的配置
MeScroll.extend(optDown, {
use: true, // 是否启用下拉刷新; 默认true
auto: true, // 是否在初始化完毕之后自动执行下拉刷新的回调; 默认true
native: false, // 是否使用系统自带的下拉刷新; 默认false; 仅mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
autoShowLoading: false, // 如果设置auto=true(在初始化完毕之后自动执行下拉刷新的回调),那么是否显示下拉刷新的进度; 默认false
isLock: false, // 是否锁定下拉刷新,默认false;
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
startTop: 100, // scroll-view快速滚动到顶部时,此时的scroll-top可能大于0, 此值用于控制最大的误差
inOffsetRate: 1, // 在列表顶部,下拉的距离小于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
outOffsetRate: 0.2, // 在列表顶部,下拉的距离大于offset时,改变下拉区域高度比例;值小于1且越接近0,高度变化越小,表现为越往下越难拉
bottomOffset: 20, // 当手指touchmove位置在距离body底部20px范围内的时候结束上拉刷新,避免Webview嵌套导致touchend事件不执行
minAngle: 45, // 向下滑动最少偏移的角度,取值区间 [0,90];默认45度,即向下滑动的角度大于45度则触发下拉;而小于45度,将不触发下拉,避免与左右滑动的轮播等组件冲突;
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
textSuccess: '加载成功', // 加载成功的文本
textErr: '加载失败', // 加载失败的文本
beforeEndDelay: 0, // 延时结束的时长 (显示加载成功/失败的时长, android小程序设置此项结束下拉会卡顿, 配置后请注意测试)
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorTop)
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
inited: null, // 下拉刷新初始化完毕的回调
inOffset: null, // 下拉的距离进入offset范围内那一刻的回调
outOffset: null, // 下拉的距离大于offset那一刻的回调
onMoving: null, // 下拉过程中的回调,滑动过程一直在执行; rate下拉区域当前高度与指定距离的比值(inOffset: rate<1; outOffset: rate>=1); downHight当前下拉区域的高度
beforeLoading: null, // 准备触发下拉刷新的回调: 如果return true,将不触发showLoading和callback回调; 常用来完全自定义下拉刷新, 参考案例【淘宝 v6.8.0】
showLoading: null, // 显示下拉刷新进度的回调
afterLoading: null, // 显示下拉刷新进度的回调之后,马上要执行的代码 (如: 在wxs中使用)
beforeEndDownScroll: null, // 准备结束下拉的回调. 返回结束下拉的延时执行时间,默认0ms; 常用于结束下拉之前再显示另外一小段动画,才去隐藏下拉刷新的场景, 参考案例【dotJump】
endDownScroll: null, // 结束下拉刷新的回调
afterEndDownScroll: null, // 结束下拉刷新的回调,马上要执行的代码 (如: 在wxs中使用)
callback: function(mescroll) {
// 下拉刷新的回调;默认重置上拉加载列表为第一页
mescroll.resetUpScroll();
}
})
}
/* 配置参数:上拉加载 */
MeScroll.prototype.extendUpScroll = function(optUp) {
// 上拉加载的配置
MeScroll.extend(optUp, {
use: true, // 是否启用上拉加载; 默认true
auto: true, // 是否在初始化完毕之后自动执行上拉加载的回调; 默认true
isLock: false, // 是否锁定上拉加载,默认false;
isBoth: true, // 上拉加载时,如果滑动到列表顶部是否可以同时触发下拉刷新;默认true,两者可同时触发;
callback: null, // 上拉加载的回调;function(page,mescroll){ }
page: {
num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
size: 10, // 每页数据的数量
time: null // 加载第一页数据服务器返回的时间; 防止用户翻页时,后台新增了数据从而导致下一页数据重复;
},
noMoreSize: 5, // 如果列表已无数据,可设置列表的总数量要大于等于5条才显示无更多数据;避免列表数据过少(比如只有一条数据),显示无更多数据会不好看
offset: 150, // 距底部多远时,触发upCallback,仅mescroll-uni生效 ( mescroll-body配置的是pages.json的 onReachBottomDistance )
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
bgColor: "transparent", // 背景颜色 (建议在pages.json中再设置一下backgroundColorBottom)
textColor: "gray", // 文本颜色 (当bgColor配置了颜色,而textColor未配置时,则textColor会默认为白色)
inited: null, // 初始化完毕的回调
showLoading: null, // 显示加载中的回调
showNoMore: null, // 显示无更多数据的回调
hideUpScroll: null, // 隐藏上拉加载的回调
errDistance: 60, // endErr的时候需往上滑动一段距离,使其往下滑动时再次触发onReachBottom,仅mescroll-body生效
toTop: {
// 回到顶部按钮,需配置src才显示
src: null, // 图片路径,默认null (绝对路径或网络图)
offset: 1000, // 列表滚动多少距离才显示回到顶部按钮,默认1000
duration: 300, // 回到顶部的动画时长,默认300ms (当值为0或300则使用系统自带回到顶部,更流畅; 其他值则通过step模拟,部分机型可能不够流畅,所以非特殊情况不建议修改此项)
btnClick: null, // 点击按钮的回调
onShow: null, // 是否显示的回调
zIndex: 9990, // fixed定位z-index值
left: null, // 到左边的距离, 默认null. 此项有值时,right不生效. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
right: 20, // 到右边的距离, 默认20 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
bottom: 120, // 到底部的距离, 默认120 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
safearea: false, // bottom的偏移量是否加上底部安全区的距离, 默认false, 需要适配iPhoneX时使用 (具体的界面如果不配置此项,则取本vue的safearea值)
width: 72, // 回到顶部图标的宽度, 默认72 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
radius: "50%" // 圆角, 默认"50%" (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx)
},
empty: {
use: true, // 是否显示空布局
icon: null, // 图标路径
tip: '~ 暂无相关数据 ~', // 提示
btnText: '', // 按钮
btnClick: null, // 点击按钮的回调
onShow: null, // 是否显示的回调
fixed: false, // 是否使用fixed定位,默认false; 配置fixed为true,以下的top和zIndex才生效 (transform会使fixed失效,最终会降级为absolute)
top: "100rpx", // fixed定位的top值 (完整的单位值,如 "10%"; "100rpx")
zIndex: 99 // fixed定位z-index值
},
onScroll: false // 是否监听滚动事件
})
}
/* 配置参数 */
MeScroll.extend = function(userOption, defaultOption) {
if (!userOption) return defaultOption;
for (let key in defaultOption) {
if (userOption[key] == null) {
let def = defaultOption[key];
if (def != null && typeof def === 'object') {
userOption[key] = MeScroll.extend({}, def); // 深度匹配
} else {
userOption[key] = def;
}
} else if (typeof userOption[key] === 'object') {
MeScroll.extend(userOption[key], defaultOption[key]); // 深度匹配
}
}
return userOption;
}
/* 简单判断是否配置了颜色 (非透明,非白色) */
MeScroll.prototype.hasColor = function(color) {
if(!color) return false;
let c = color.toLowerCase();
return c != "#fff" && c != "#ffffff" && c != "transparent" && c != "white"
}
/* -------初始化下拉刷新------- */
MeScroll.prototype.initDownScroll = function() {
let me = this;
// 配置参数
me.optDown = me.options.down || {};
if(!me.optDown.textColor && me.hasColor(me.optDown.bgColor)) me.optDown.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
me.extendDownScroll(me.optDown);
// 如果是mescroll-body且配置了native,则禁止自定义的下拉刷新
if(me.isScrollBody && me.optDown.native){
me.optDown.use = false
}else{
me.optDown.native = false // 仅mescroll-body支持,mescroll-uni不支持
}
me.downHight = 0; // 下拉区域的高度
// 在页面中加入下拉布局
if (me.optDown.use && me.optDown.inited) {
// 初始化完毕的回调
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
me.optDown.inited(me);
}, 0)
}
}
/* 列表touchstart事件 */
MeScroll.prototype.touchstartEvent = function(e) {
if (!this.optDown.use) return;
this.startPoint = this.getPoint(e); // 记录起点
this.startTop = this.getScrollTop(); // 记录此时的滚动条位置
this.startAngle = 0; // 初始角度
this.lastPoint = this.startPoint; // 重置上次move的点
this.maxTouchmoveY = this.getBodyHeight() - this.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
this.inTouchend = false; // 标记不是touchend
}
/* 列表touchmove事件 */
MeScroll.prototype.touchmoveEvent = function(e) {
if (!this.optDown.use) return;
let me = this;
let scrollTop = me.getScrollTop(); // 当前滚动条的距离
let curPoint = me.getPoint(e); // 当前点
let moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
// 向下拉 && 在顶部
// mescroll-body,直接判定在顶部即可
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
if (moveY > 0 && (
(me.isScrollBody && scrollTop <= 0)
||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
)) {
// 可下拉的条件
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
me.optUp.isBoth))) {
// 下拉的初始角度是否在配置的范围内
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
if (me.startAngle < me.optDown.minAngle) return; // 如果小于配置的角度,则不往下执行下拉刷新
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
me.inTouchend = true; // 标记执行touchend
me.touchendEvent(); // 提前触发touchend
return;
}
me.preventDefault(e); // 阻止默认事件
let diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
// 下拉距离 < 指定距离
if (me.downHight < me.optDown.offset) {
if (me.movetype !== 1) {
me.movetype = 1; // 加入标记,保证只执行一次
me.isDownEndSuccess = null; // 重置是否加载成功的状态 (wxs执行的是wxs.wxs)
me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
}
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
// 指定距离 <= 下拉距离
} else {
if (me.movetype !== 2) {
me.movetype = 2; // 加入标记,保证只执行一次
me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
}
if (diff > 0) { // 向下拉
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
} else { // 向上收
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
}
}
me.downHight = Math.round(me.downHight) // 取整
let rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
}
}
me.lastPoint = curPoint; // 记录本次移动的点
}
/* 列表touchend事件 */
MeScroll.prototype.touchendEvent = function(e) {
if (!this.optDown.use) return;
// 如果下拉区域高度已改变,则需重置回来
if (this.isMoveDown) {
if (this.downHight >= this.optDown.offset) {
// 符合触发刷新的条件
this.triggerDownScroll();
} else {
// 不符合的话 则重置
this.downHight = 0;
this.endDownScrollCall(this);
}
this.movetype = 0;
this.isMoveDown = false;
} else if (!this.isScrollBody && this.getScrollTop() === this.startTop) { // scroll-view到顶/左/右/底的滑动事件
let isScrollUp = this.getPoint(e).y - this.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
// 上滑
if (isScrollUp) {
// 需检查滑动的角度
let angle = this.getAngle(this.getPoint(e), this.startPoint); // 两点之间的角度,区间 [0,90]
if (angle > 80) {
// 检查并触发上拉
this.triggerUpScroll(true);
}
}
}
}
/* 根据点击滑动事件获取第一个手指的坐标 */
MeScroll.prototype.getPoint = function(e) {
if (!e) {
return {
x: 0,
y: 0
}
}
if (e.touches && e.touches[0]) {
return {
x: e.touches[0].pageX,
y: e.touches[0].pageY
}
} else if (e.changedTouches && e.changedTouches[0]) {
return {
x: e.changedTouches[0].pageX,
y: e.changedTouches[0].pageY
}
} else {
return {
x: e.clientX,
y: e.clientY
}
}
}
/* 计算两点之间的角度: 区间 [0,90]*/
MeScroll.prototype.getAngle = function(p1, p2) {
let x = Math.abs(p1.x - p2.x);
let y = Math.abs(p1.y - p2.y);
let z = Math.sqrt(x * x + y * y);
let angle = 0;
if (z !== 0) {
angle = Math.asin(y / z) / Math.PI * 180;
}
return angle
}
/* 触发下拉刷新 */
MeScroll.prototype.triggerDownScroll = function() {
if (this.optDown.beforeLoading && this.optDown.beforeLoading(this)) {
//return true则处于完全自定义状态
} else {
this.showDownScroll(); // 下拉刷新中...
!this.optDown.native && this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
}
}
/* 显示下拉进度布局 */
MeScroll.prototype.showDownScroll = function() {
this.isDownScrolling = true; // 标记下拉中
if (this.optDown.native) {
uni.startPullDownRefresh(); // 系统自带的下拉刷新
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
} else{
this.downHight = this.optDown.offset; // 更新下拉区域高度
this.showDownLoadingCall(this.downHight); // 下拉刷新中...
}
}
MeScroll.prototype.showDownLoadingCall = function(downHight) {
this.optDown.showLoading && this.optDown.showLoading(this, downHight); // 下拉刷新中...
this.optDown.afterLoading && this.optDown.afterLoading(this, downHight); // 下拉刷新中...触发之后马上要执行的代码
}
/* 显示系统自带的下拉刷新时需要处理的业务 */
MeScroll.prototype.onPullDownRefresh = function() {
this.isDownScrolling = true; // 标记下拉中
this.showDownLoadingCall(0); // 仍触发showLoading,因为上拉加载用到
this.optDown.callback && this.optDown.callback(this); // 执行回调,联网加载数据
}
/* 结束下拉刷新 */
MeScroll.prototype.endDownScroll = function() {
if (this.optDown.native) { // 结束原生下拉刷新
this.isDownScrolling = false;
this.endDownScrollCall(this);
uni.stopPullDownRefresh();
return
}
let me = this;
// 结束下拉刷新的方法
let endScroll = function() {
me.downHight = 0;
me.isDownScrolling = false;
me.endDownScrollCall(me);
if(!me.isScrollBody){
me.setScrollHeight(0) // scroll-view重置滚动区域,使数据不满屏时仍可检查触发翻页
me.scrollTo(0,0) // scroll-view需重置滚动条到顶部,避免startTop大于0时,对下拉刷新的影响
}
}
// 结束下拉刷新时的回调
let delay = 0;
if (me.optDown.beforeEndDownScroll) {
delay = me.optDown.beforeEndDownScroll(me); // 结束下拉刷新的延时,单位ms
if(me.isDownEndSuccess == null) delay = 0; // 没有执行加载中,则不延时
}
if (typeof delay === 'number' && delay > 0) {
setTimeout(endScroll, delay);
} else {
endScroll();
}
}
MeScroll.prototype.endDownScrollCall = function() {
this.optDown.endDownScroll && this.optDown.endDownScroll(this);
this.optDown.afterEndDownScroll && this.optDown.afterEndDownScroll(this);
}
/* 锁定下拉刷新:isLock=ture,null锁定;isLock=false解锁 */
MeScroll.prototype.lockDownScroll = function(isLock) {
if (isLock == null) isLock = true;
this.optDown.isLock = isLock;
}
/* 锁定上拉加载:isLock=ture,null锁定;isLock=false解锁 */
MeScroll.prototype.lockUpScroll = function(isLock) {
if (isLock == null) isLock = true;
this.optUp.isLock = isLock;
}
/* -------初始化上拉加载------- */
MeScroll.prototype.initUpScroll = function() {
let me = this;
// 配置参数
me.optUp = me.options.up || {use: false}
if(!me.optUp.textColor && me.hasColor(me.optUp.bgColor)) me.optUp.textColor = "#fff"; // 当bgColor有值且textColor未设置,则textColor默认白色
me.extendUpScroll(me.optUp);
if (me.optUp.use === false) return; // 配置不使用上拉加载时,则不初始化上拉布局
me.optUp.hasNext = true; // 如果使用上拉,则默认有下一页
me.startNum = me.optUp.page.num + 1; // 记录page开始的页码
// 初始化完毕的回调
if (me.optUp.inited) {
setTimeout(function() { // 待主线程执行完毕再执行,避免new MeScroll未初始化,在回调获取不到mescroll的实例
me.optUp.inited(me);
}, 0)
}
}
/*滚动到底部的事件 (仅mescroll-body生效)*/
MeScroll.prototype.onReachBottom = function() {
if (this.isScrollBody && !this.isUpScrolling) { // 只能支持下拉刷新的时候同时可以触发上拉加载,否则滚动到底部就需要上滑一点才能触发onReachBottom
if (!this.optUp.isLock && this.optUp.hasNext) {
this.triggerUpScroll();
}
}
}
/*列表滚动事件 (仅mescroll-body生效)*/
MeScroll.prototype.onPageScroll = function(e) {
if (!this.isScrollBody) return;
// 更新滚动条的位置 (主要用于判断下拉刷新时,滚动条是否在顶部)
this.setScrollTop(e.scrollTop);
// 顶部按钮的显示隐藏
if (e.scrollTop >= this.optUp.toTop.offset) {
this.showTopBtn();
} else {
this.hideTopBtn();
}
}
/*列表滚动事件*/
MeScroll.prototype.scroll = function(e, onScroll) {
// 更新滚动条的位置
this.setScrollTop(e.scrollTop);
// 更新滚动内容高度
this.setScrollHeight(e.scrollHeight);
// 向上滑还是向下滑动
if (this.preScrollY == null) this.preScrollY = 0;
this.isScrollUp = e.scrollTop - this.preScrollY > 0;
this.preScrollY = e.scrollTop;
// 上滑 && 检查并触发上拉
this.isScrollUp && this.triggerUpScroll(true);
// 顶部按钮的显示隐藏
if (e.scrollTop >= this.optUp.toTop.offset) {
this.showTopBtn();
} else {
this.hideTopBtn();
}
// 滑动监听
this.optUp.onScroll && onScroll && onScroll()
}
/* 触发上拉加载 */
MeScroll.prototype.triggerUpScroll = function(isCheck) {
if (!this.isUpScrolling && this.optUp.use && this.optUp.callback) {
// 是否校验在底部; 默认不校验
if (isCheck === true) {
let canUp = false;
// 还有下一页 && 没有锁定 && 不在下拉中
if (this.optUp.hasNext && !this.optUp.isLock && !this.isDownScrolling) {
if (this.getScrollBottom() <= this.optUp.offset) { // 到底部
canUp = true; // 标记可上拉
}
}
if (canUp === false) return;
}
this.showUpScroll(); // 上拉加载中...
this.optUp.page.num++; // 预先加一页,如果失败则减回
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
this.num = this.optUp.page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
this.size = this.optUp.page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
this.time = this.optUp.page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
this.optUp.callback(this); // 执行回调,联网加载数据
}
}
/* 显示上拉加载中 */
MeScroll.prototype.showUpScroll = function() {
this.isUpScrolling = true; // 标记上拉加载中
this.optUp.showLoading && this.optUp.showLoading(this); // 回调
}
/* 显示上拉无更多数据 */
MeScroll.prototype.showNoMore = function() {
this.optUp.hasNext = false; // 标记无更多数据
this.optUp.showNoMore && this.optUp.showNoMore(this); // 回调
}
/* 隐藏上拉区域**/
MeScroll.prototype.hideUpScroll = function() {
this.optUp.hideUpScroll && this.optUp.hideUpScroll(this); // 回调
}
/* 结束上拉加载 */
MeScroll.prototype.endUpScroll = function(isShowNoMore) {
if (isShowNoMore != null) { // isShowNoMore=null,不处理下拉状态,下拉刷新的时候调用
if (isShowNoMore) {
this.showNoMore(); // isShowNoMore=true,显示无更多数据
} else {
this.hideUpScroll(); // isShowNoMore=false,隐藏上拉加载
}
}
this.isUpScrolling = false; // 标记结束上拉加载
}
/*
*isShowLoading 是否显示进度布局;
* 1.默认null,不传参,则显示上拉加载的进度布局
* 2.传参true, 则显示下拉刷新的进度布局
* 3.传参false,则不显示上拉和下拉的进度 (常用于静默更新列表数据)
*/
MeScroll.prototype.resetUpScroll = function(isShowLoading) {
if (this.optUp && this.optUp.use) {
let page = this.optUp.page;
this.prePageNum = page.num; // 缓存重置前的页码,加载失败可退回
this.prePageTime = page.time; // 缓存重置前的时间,加载失败可退回
page.num = this.startNum; // 重置为第一页
page.time = null; // 重置时间为空
if (!this.isDownScrolling && isShowLoading !== false) { // 如果不是下拉刷新触发的resetUpScroll并且不配置列表静默更新,则显示进度;
if (isShowLoading == null) {
this.removeEmpty(); // 移除空布局
this.showUpScroll(); // 不传参,默认显示上拉加载的进度布局
} else {
this.showDownScroll(); // 传true,显示下拉刷新的进度布局,不清空列表
}
}
this.isUpAutoLoad = true; // 标记上拉已经自动执行过,避免初始化时多次触发上拉回调
this.num = page.num; // 把最新的页数赋值在mescroll上,避免对page的影响
this.size = page.size; // 把最新的页码赋值在mescroll上,避免对page的影响
this.time = page.time; // 把最新的页码赋值在mescroll上,避免对page的影响
this.optUp.callback && this.optUp.callback(this); // 执行上拉回调
}
}
/* 设置page.num的值 */
MeScroll.prototype.setPageNum = function(num) {
this.optUp.page.num = num - 1;
}
/* 设置page.size的值 */
MeScroll.prototype.setPageSize = function(size) {
this.optUp.page.size = size;
}
/* ,
* dataSize: 当前页的数据量(必传)
* totalPage: 总页数(必传)
* systime: 服务器时间 (可空)
*/
MeScroll.prototype.endByPage = function(dataSize, totalPage, systime) {
let hasNext;
if (this.optUp.use && totalPage != null) hasNext = this.optUp.page.num < totalPage; // 是否还有下一页
this.endSuccess(dataSize, hasNext, systime);
}
/* ,
* dataSize: 当前页的数据量(必传)
* totalSize: 列表所有数据总数量(必传)
* systime: 服务器时间 (可空)
*/
MeScroll.prototype.endBySize = function(dataSize, totalSize, systime) {
let hasNext;
if (this.optUp.use && totalSize != null) {
let loadSize = (this.optUp.page.num - 1) * this.optUp.page.size + dataSize; // 已加载的数据总数
hasNext = loadSize < totalSize; // 是否还有下一页
}
this.endSuccess(dataSize, hasNext, systime);
}
/* ,
* dataSize: 当前页的数据个数(不是所有页的数据总和),用于上拉加载判断是否还有下一页.如果不传,则会判断还有下一页
* hasNext: 是否还有下一页,布尔类型;用来解决这个小问题:比如列表共有20条数据,每页加载10条,共2页.如果只根据dataSize判断,则需翻到第三页才会知道无更多数据,如果传了hasNext,则翻到第二页即可显示无更多数据.
* systime: 服务器时间(可空);用来解决这个小问题:当准备翻下一页时,数据库新增了几条记录,此时翻下一页,前面的几条数据会和上一页的重复;这里传入了systime,那么upCallback的page.time就会有值,把page.time传给服务器,让后台过滤新加入的那几条记录
*/
MeScroll.prototype.endSuccess = function(dataSize, hasNext, systime) {
let me = this;
// 结束下拉刷新
if (me.isDownScrolling) {
me.isDownEndSuccess = true
me.endDownScroll();
}
// 结束上拉加载
if (me.optUp.use) {
let isShowNoMore; // 是否已无更多数据
if (dataSize != null) {
let pageNum = me.optUp.page.num; // 当前页码
let pageSize = me.optUp.page.size; // 每页长度
// 如果是第一页
if (pageNum === 1) {
if (systime) me.optUp.page.time = systime; // 设置加载列表数据第一页的时间
}
if (dataSize < pageSize || hasNext === false) {
// 返回的数据不满一页时,则说明已无更多数据
me.optUp.hasNext = false;
if (dataSize === 0 && pageNum === 1) {
// 如果第一页无任何数据且配置了空布局
isShowNoMore = false;
me.showEmpty();
} else {
// 总列表数少于配置的数量,则不显示无更多数据
let allDataSize = (pageNum - 1) * pageSize + dataSize;
if (allDataSize < me.optUp.noMoreSize) {
isShowNoMore = false;
} else {
isShowNoMore = true;
}
me.removeEmpty(); // 移除空布局
}
} else {
// 还有下一页
isShowNoMore = false;
me.optUp.hasNext = true;
me.removeEmpty(); // 移除空布局
}
}
// 隐藏上拉
me.endUpScroll(isShowNoMore);
}
}
/* 回调失败,结束下拉刷新和上拉加载 */
MeScroll.prototype.endErr = function(errDistance) {
// 结束下拉,回调失败重置回原来的页码和时间
if (this.isDownScrolling) {
this.isDownEndSuccess = false
let page = this.optUp.page;
if (page && this.prePageNum) {
page.num = this.prePageNum;
page.time = this.prePageTime;
}
this.endDownScroll();
}
// 结束上拉,回调失败重置回原来的页码
if (this.isUpScrolling) {
this.optUp.page.num--;
this.endUpScroll(false);
// 如果是mescroll-body,则需往回滚一定距离
if(this.isScrollBody && errDistance !== 0){ // 不处理0
if(!errDistance) errDistance = this.optUp.errDistance; // 不传,则取默认
this.scrollTo(this.getScrollTop() - errDistance, 0) // 往上回滚的距离
}
}
}
/* 显示空布局 */
MeScroll.prototype.showEmpty = function() {
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(true)
}
/* 移除空布局 */
MeScroll.prototype.removeEmpty = function() {
this.optUp.empty.use && this.optUp.empty.onShow && this.optUp.empty.onShow(false)
}
/* 显示回到顶部的按钮 */
MeScroll.prototype.showTopBtn = function() {
if (!this.topBtnShow) {
this.topBtnShow = true;
this.optUp.toTop.onShow && this.optUp.toTop.onShow(true);
}
}
/* 隐藏回到顶部的按钮 */
MeScroll.prototype.hideTopBtn = function() {
if (this.topBtnShow) {
this.topBtnShow = false;
this.optUp.toTop.onShow && this.optUp.toTop.onShow(false);
}
}
/* 获取滚动条的位置 */
MeScroll.prototype.getScrollTop = function() {
return this.scrollTop || 0
}
/* 记录滚动条的位置 */
MeScroll.prototype.setScrollTop = function(y) {
this.scrollTop = y;
}
/* 滚动到指定位置 */
MeScroll.prototype.scrollTo = function(y, t) {
this.myScrollTo && this.myScrollTo(y, t) // scrollview需自定义回到顶部方法
}
/* 自定义scrollTo */
MeScroll.prototype.resetScrollTo = function(myScrollTo) {
this.myScrollTo = myScrollTo
}
/* 滚动条到底部的距离 */
MeScroll.prototype.getScrollBottom = function() {
return this.getScrollHeight() - this.getClientHeight() - this.getScrollTop()
}
/*
star: 开始值
end: 结束值
callback(step,timer): 回调step值,计步器timer,可自行通过window.clearInterval(timer)结束计步器;
t: 计步时长,传0则直接回调end值;不传则默认300ms
rate: 周期;不传则默认30ms计步一次
* */
MeScroll.prototype.getStep = function(star, end, callback, t, rate) {
let diff = end - star; // 差值
if (t === 0 || diff === 0) {
callback && callback(end);
return;
}
t = t || 300; // 时长 300ms
rate = rate || 30; // 周期 30ms
let count = t / rate; // 次数
let step = diff / count; // 步长
let i = 0; // 计数
let timer = setInterval(function() {
if (i < count - 1) {
star += step;
callback && callback(star, timer);
i++;
} else {
callback && callback(end, timer); // 最后一次直接设置end,避免计算误差
clearInterval(timer);
}
}, rate);
}
/* 滚动容器的高度 */
MeScroll.prototype.getClientHeight = function(isReal) {
let h = this.clientHeight || 0
if (h === 0 && isReal !== true) { // 未获取到容器的高度,可临时取body的高度 (可能会有误差)
h = this.getBodyHeight()
}
return h
}
MeScroll.prototype.setClientHeight = function(h) {
this.clientHeight = h;
}
/* 滚动内容的高度 */
MeScroll.prototype.getScrollHeight = function() {
return this.scrollHeight || 0;
}
MeScroll.prototype.setScrollHeight = function(h) {
this.scrollHeight = h;
}
/* body的高度 */
MeScroll.prototype.getBodyHeight = function() {
return this.bodyHeight || 0;
}
MeScroll.prototype.setBodyHeight = function(h) {
this.bodyHeight = h;
}
/* 阻止浏览器默认滚动事件 */
MeScroll.prototype.preventDefault = function(e) {
// 小程序不支持e.preventDefault, 已在wxs中禁止
// app的bounce只能通过配置pages.json的style.app-plus.bounce为"none"来禁止, 或使用renderjs禁止
// cancelable:是否可以被禁用; defaultPrevented:是否已经被禁用
if (e && e.cancelable && !e.defaultPrevented) e.preventDefault()
}

477
uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue

@ -1,477 +0,0 @@
<template>
<view class="mescroll-uni-warp">
<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
<view class="mescroll-uni-content mescroll-render-touch"
@touchstart="wxsBiz.touchstartEvent"
@touchmove="wxsBiz.touchmoveEvent"
@touchend="wxsBiz.touchendEvent"
@touchcancel="wxsBiz.touchendEvent"
:change:prop="wxsBiz.propObserver"
:prop="wxsProp">
<!-- 状态栏 -->
<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
<view class="downwarp-content">
<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
<view class="downwarp-tip">{{downText}}</view>
</view>
</view>
<!-- 列表内容 -->
<slot></slot>
<!-- 空布局 -->
<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
<view v-show="upLoadType===1">
<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
</view>
<!-- 无数据 -->
<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
</view>
</view>
<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
<!-- #ifdef H5 -->
<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
<!-- #endif -->
<!-- 适配iPhoneX -->
<view v-if="safearea" class="mescroll-safearea"></view>
</view>
</scroll-view>
<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
<!-- #endif -->
</view>
</template>
<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from './wxs/renderjs.js';
export default {
mixins:[renderBiz]
}
</script>
<!-- #endif -->
<script>
// mescroll-uni.js,
import MeScroll from './mescroll-uni.js';
//
import GlobalOption from './mescroll-uni-option.js';
//
import mescrollI18n from './mescroll-i18n.js';
//
import MescrollTop from './components/mescroll-top.vue';
// wxs(renderjs)mixins
import WxsMixin from './wxs/mixins.js';
/**
* mescroll-uni 嵌在页面某个区域的下拉刷新和上拉加载组件, 如嵌在弹窗,浮层,swiper中...
* @property {Object} down 下拉刷新的参数配置
* @property {Object} up 上拉加载的参数配置
* @property {Object} i18n 国际化的参数配置
* @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
* @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
* @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
* @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
* @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
* @property {String, Number} height 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
* @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
* @property {Boolean} disableScroll 是否禁止滚动, 默认false
* @event {Function} init 初始化完成的回调
* @event {Function} down 下拉刷新的回调
* @event {Function} up 上拉加载的回调
* @event {Function} emptyclick 点击empty配置的btnText按钮回调
* @event {Function} topclick 点击回到顶部的按钮回调
* @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
* @example <mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-uni>
*/
export default {
name: 'mescroll-uni',
mixins: [WxsMixin],
components: {
MescrollTop
},
props: {
down: Object,
up: Object,
i18n: Object,
top: [String, Number],
topbar: [Boolean, String],
bottom: [String, Number],
safearea: Boolean,
fixed: {
type: Boolean,
default: true
},
height: [String, Number],
bottombar:{
type: Boolean,
default: true
},
disableScroll: Boolean
},
data() {
return {
mescroll: {optDown:{},optUp:{}}, // mescroll
viewId: 'id_' + Math.random().toString(36).substr(2,16), // mescrollid(,)
downHight: 0, //:
downRate: 0, // (inOffset: rate<1; outOffset: rate>=1)
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // : 0(loading), 1loading, 2,END, 3(,END)
isShowEmpty: false, //
isShowToTop: false, //
scrollTop: 0, //
scrollAnim: false, //
windowTop: 0, // 使
windowBottom: 0, // 使
windowHeight: 0, // 使
statusBarHeight: 0 //
}
},
computed: {
// 使fixed (height,使)
isFixed(){
return !this.height && this.fixed
},
// mescroll
scrollHeight(){
if (this.isFixed) {
return "auto"
} else if(this.height){
return this.toPx(this.height) + 'px'
}else{
return "100%"
}
},
// (px)
numTop() {
return this.toPx(this.top)
},
fixedTop() {
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
},
padTop() {
return !this.isFixed ? this.numTop + 'px' : 0
},
// (px)
numBottom() {
return this.toPx(this.bottom)
},
fixedBottom() {
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
},
padBottom() {
return !this.isFixed ? this.numBottom + 'px' : 0
},
//
isDownReset(){
return this.downLoadType===3 || this.downLoadType===4
},
//
transition() {
return this.isDownReset ? 'transform 300ms' : '';
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform使fixed,fixedmescroll
},
//
scrollable(){
if(this.disableScroll) return false
return this.downLoadType===0 || this.isDownReset
},
//
isDownLoading(){
return this.downLoadType === 3
},
//
downRotate(){
return 'rotate(' + 360 * this.downRate + 'deg)'
},
//
downText(){
if(!this.mescroll) return ""; //
switch (this.downLoadType){
case 1: return this.mescroll.optDown.textInOffset;
case 2: return this.mescroll.optDown.textOutOffset;
case 3: return this.mescroll.optDown.textLoading;
case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
default: return this.mescroll.optDown.textInOffset;
}
}
},
methods: {
//number,rpx,upx,px,% --> px
toPx(num){
if(typeof num === "string"){
if (num.indexOf('px') !== -1) {
if(num.indexOf('rpx') !== -1) { // "10rpx"
num = num.replace('rpx', '');
} else if(num.indexOf('upx') !== -1) { // "10upx"
num = num.replace('upx', '');
} else { // "10px"
return Number(num.replace('px', ''))
}
}else if (num.indexOf('%') !== -1){
// ,windowHeight,"10%"windowHeight10%
let rate = Number(num.replace("%","")) / 100
return this.windowHeight * rate
}
}
return num ? uni.upx2px(Number(num)) : 0
},
//,
scroll(e) {
this.mescroll.scroll(e.detail, () => {
this.$emit('scroll', this.mescroll) // this.mescroll.scrollTop; this.mescroll.isScrollUp
})
},
//
emptyClick() {
this.$emit('emptyclick', this.mescroll)
},
//
toTopClick() {
this.mescroll.scrollTo(0, this.mescroll.optUp.toTop.duration); //
this.$emit('topclick', this.mescroll); //
},
// (使,)
setClientHeight() {
if (this.mescroll.getClientHeight(true) === 0 && !this.isExec) {
this.isExec = true; //
this.$nextTick(() => { // dom
this.getClientInfo(data=>{
this.isExec = false;
if (data) {
this.mescroll.setClientHeight(data.height);
} else if (this.clientNum != 3) { // ,dom,,3
this.clientNum = this.clientNum == null ? 1 : this.clientNum + 1;
setTimeout(() => {
this.setClientHeight()
}, this.clientNum * 100)
}
})
})
}
},
//
getClientInfo(success){
let query = uni.createSelectorQuery();
// #ifndef MP-ALIPAY || MP-DINGTALK
query = query.in(this) // in(this),in(this),
// #endif
let view = query.select('#' + this.viewId);
view.boundingClientRect(data => {
success(data)
}).exec();
}
},
// 使createdmescroll; mountedcssH5
created() {
let vm = this;
let diyOption = {
//
down: {
inOffset() {
vm.downLoadType = 1; // offset (mescroll,)
},
outOffset() {
vm.downLoadType = 2; // offset (mescroll,)
},
onMoving(mescroll, rate, downHight) {
// ,;
vm.downHight = downHight; // (mescroll,)
vm.downRate = rate; // (inOffset: rate<1; outOffset: rate>=1)
},
showLoading(mescroll, downHight) {
vm.downLoadType = 3; // (mescroll,)
vm.downHight = downHight; // (mescroll,)
},
beforeEndDownScroll(mescroll){
vm.downLoadType = 4;
return mescroll.optDown.beforeEndDelay //
},
endDownScroll() {
vm.downLoadType = 4; // (mescroll,)
vm.downHight = 0; // (mescroll,)
vm.downResetTimer && clearTimeout(vm.downResetTimer)
vm.downResetTimer = setTimeout(()=>{ // ,0,便this.transition,iOS
if(vm.downLoadType===4) vm.downLoadType = 0
},300)
},
//
callback: function(mescroll) {
vm.$emit('down', mescroll)
}
},
//
up: {
//
showLoading() {
vm.upLoadType = 1;
},
//
showNoMore() {
vm.upLoadType = 2;
},
//
hideUpScroll(mescroll) {
vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
},
//
empty: {
onShow(isShow) { //
vm.isShowEmpty = isShow;
}
},
//
toTop: {
onShow(isShow) { //
vm.isShowToTop = isShow;
}
},
//
callback: function(mescroll) {
vm.$emit('up', mescroll);
// (mescroll)
vm.setClientHeight()
}
}
}
let i18nType = mescrollI18n.getType() //
let i18nOption = {type: i18nType} //
MeScroll.extend(i18nOption, vm.i18n) //
MeScroll.extend(i18nOption, GlobalOption.i18n) //
MeScroll.extend(diyOption, i18nOption[i18nType]); //
MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); //
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption);
vm.mescroll.viewId = vm.viewId; // id
vm.mescroll.i18n = i18nOption; //
// initmescroll
vm.$emit('init', vm.mescroll);
//
const sys = uni.getSystemInfoSync();
if(sys.windowTop) vm.windowTop = sys.windowTop;
if(sys.windowBottom) vm.windowBottom = sys.windowBottom;
if(sys.windowHeight) vm.windowHeight = sys.windowHeight;
if(sys.statusBarHeight) vm.statusBarHeight = sys.statusBarHeight;
// 使downbottomOffset
vm.mescroll.setBodyHeight(sys.windowHeight);
// 使scrollview,scrollTo
vm.mescroll.resetScrollTo((y, t) => {
vm.scrollAnim = (t !== 0); // t0,使
if(typeof y === 'string'){
// slotscroll-into-view, 使
vm.getClientInfo(function(rect){
let mescrollTop = rect.top // mescroll
let selector;
if(y.indexOf('#')==-1 && y.indexOf('.')==-1){
selector = '#'+y // #. id
}else{
selector = y
// #ifdef APP-PLUS || H5 || MP-ALIPAY || MP-DINGTALK
if(y.indexOf('>>>')!=-1){ // ()
selector = y.split('>>>')[1].trim()
}
// #endif
}
uni.createSelectorQuery().select(selector).boundingClientRect(function(rect){
if (rect) {
let curY = vm.mescroll.getScrollTop()
let top = rect.top - mescrollTop
top += curY
if(!vm.isFixed) top -= vm.numTop
vm.scrollTop = curY;
vm.$nextTick(function() {
vm.scrollTop = top
})
} else{
console.error(selector + ' does not exist');
}
}).exec()
})
return;
}
let curY = vm.mescroll.getScrollTop()
if (t === 0 || t === 300) { // t使300,使
vm.scrollTop = curY;
vm.$nextTick(function() {
vm.scrollTop = y
})
} else {
vm.mescroll.getStep(curY, y, step => { // t
vm.scrollTop = step
}, t)
}
})
// up.toTop.safearea,vuesafearea
if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
vm.mescroll.optUp.toTop.safearea = vm.safearea;
}
//
uni.$on("setMescrollGlobalOption", options=>{
if(!options) return;
let i18nType = options.i18n ? options.i18n.type : null
if(i18nType && vm.mescroll.i18n.type != i18nType){
vm.mescroll.i18n.type = i18nType
mescrollI18n.setType(i18nType)
MeScroll.extend(options, vm.mescroll.i18n[i18nType])
}
if(options.down){
let down = MeScroll.extend({}, options.down)
vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
}
if(options.up){
let up = MeScroll.extend({}, options.up)
vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
}
})
},
mounted() {
//
this.setClientHeight()
},
destroyed() {
//
uni.$off("setMescrollGlobalOption")
}
}
</script>
<style>
@import "./mescroll-uni.css";
@import "./components/mescroll-down.css";
@import './components/mescroll-up.css';
</style>

47
uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js

@ -1,47 +0,0 @@
/**
* mescroll-body写在子组件时,需通过mescroll的mixins补充子组件缺少的生命周期
*/
const MescrollCompMixin = {
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件 (一级)
onPageScroll(e) {
this.handlePageScroll(e)
},
onReachBottom() {
this.handleReachBottom()
},
// 当down的native: true时, 还需传递此方法进到子组件
onPullDownRefresh(){
this.handlePullDownRefresh()
},
data() {
return {
mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
onPageScroll: e=>{
this.handlePageScroll(e)
},
onReachBottom: ()=>{
this.handleReachBottom()
},
onPullDownRefresh: ()=>{
this.handlePullDownRefresh()
}
}
}
},
methods:{
handlePageScroll(e){
let item = this.$refs["mescrollItem"];
if(item && item.mescroll) item.mescroll.onPageScroll(e);
},
handleReachBottom(){
let item = this.$refs["mescrollItem"];
if(item && item.mescroll) item.mescroll.onReachBottom();
},
handlePullDownRefresh(){
let item = this.$refs["mescrollItem"];
if(item && item.mescroll) item.mescroll.onPullDownRefresh();
}
}
}
export default MescrollCompMixin;

66
uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js

@ -1,66 +0,0 @@
/**
* mescroll-more-item的mixins, 仅在多个 mescroll-body 写在子组件时使用 (参考 mescroll-more 案例)
*/
const MescrollMoreItemMixin = {
// 支付宝小程序不支持props的mixin,需写在具体的页面中
// #ifndef MP-ALIPAY || MP-DINGTALK
props:{
i: Number, // 每个tab页的专属下标
index: { // 当前tab的下标
type: Number,
default(){
return 0
}
}
},
// #endif
data() {
return {
downOption:{
auto:false // 不自动加载
},
upOption:{
auto:false // 不自动加载
},
isInit: false // 当前tab是否已初始化
}
},
watch:{
// 监听下标的变化
index(val){
if (this.i === val && !this.isInit) this.mescrollTrigger()
}
},
methods: {
// 以ref的方式初始化mescroll对象 (兼容字节跳动小程序)
mescrollInitByRef() {
if(!this.mescroll || !this.mescroll.resetUpScroll){
// 字节跳动小程序编辑器不支持一个页面存在相同的ref, 多mescroll的ref需动态生成, 格式为'mescrollRef下标'
let mescrollRef = this.$refs.mescrollRef || this.$refs['mescrollRef'+this.i];
if(mescrollRef) this.mescroll = mescrollRef.mescroll
}
},
// mescroll组件初始化的回调,可获取到mescroll对象 (覆盖mescroll-mixins.js的mescrollInit, 为了标记isInit)
mescrollInit(mescroll) {
this.mescroll = mescroll;
this.mescrollInitByRef && this.mescrollInitByRef(); // 兼容字节跳动小程序
// 自动加载当前tab的数据
if(this.i === this.index){
this.mescrollTrigger()
}
},
// 主动触发加载
mescrollTrigger(){
this.isInit = true; // 标记为true
if (this.mescroll) {
if (this.mescroll.optDown.use) {
this.mescroll.triggerDownScroll();
} else{
this.mescroll.triggerUpScroll();
}
}
}
}
}
export default MescrollMoreItemMixin;

74
uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js

@ -1,74 +0,0 @@
/**
* mescroll-body写在子组件时, 需通过mescroll的mixins补充子组件缺少的生命周期
*/
const MescrollMoreMixin = {
data() {
return {
tabIndex: 0, // 当前tab下标
mescroll: { // mescroll-body写在子子子...组件的情况 (多级)
onPageScroll: e=>{
this.handlePageScroll(e)
},
onReachBottom: ()=>{
this.handleReachBottom()
},
onPullDownRefresh: ()=>{
this.handlePullDownRefresh()
}
}
}
},
// 因为子组件无onPageScroll和onReachBottom的页面生命周期,需在页面传递进到子组件
onPageScroll(e) {
this.handlePageScroll(e)
},
onReachBottom() {
this.handleReachBottom()
},
// 当down的native: true时, 还需传递此方法进到子组件
onPullDownRefresh(){
this.handlePullDownRefresh()
},
methods:{
handlePageScroll(e){
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onPageScroll(e);
},
handleReachBottom(){
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onReachBottom();
},
handlePullDownRefresh(){
let mescroll = this.getMescroll(this.tabIndex);
mescroll && mescroll.onPullDownRefresh();
},
// 根据下标获取对应子组件的mescroll
getMescroll(i){
if(!this.mescrollItems) this.mescrollItems = [];
if(!this.mescrollItems[i]) {
// v-for中的refs
let vForItem = this.$refs["mescrollItem"];
if(vForItem){
this.mescrollItems[i] = vForItem[i]
}else{
// 普通的refs,不可重复
this.mescrollItems[i] = this.$refs["mescrollItem"+i];
}
}
let item = this.mescrollItems[i]
return item ? item.mescroll : null
},
// 切换tab,恢复滚动条位置
tabChange(i){
let mescroll = this.getMescroll(i);
if(mescroll){
// 延时(比$nextTick靠谱一些),确保元素已渲染
setTimeout(()=>{
mescroll.scrollTo(mescroll.getScrollTop(),0)
},30)
}
}
}
}
export default MescrollMoreMixin;

109
uni_modules/mescroll-uni/components/mescroll-uni/wxs/mixins.js

@ -1,109 +0,0 @@
// 定义在wxs (含renderjs) 逻辑层的数据和方法, 与视图层相互通信
const WxsMixin = {
data() {
return {
// 传入wxs视图层的数据 (响应式)
wxsProp: {
optDown:{}, // 下拉刷新的配置
scrollTop:0, // 滚动条的距离
bodyHeight:0, // body的高度
isDownScrolling:false, // 是否正在下拉刷新中
isUpScrolling:false, // 是否正在上拉加载中
isScrollBody:true, // 是否为mescroll-body滚动
isUpBoth:true, // 上拉加载时,是否同时可以下拉刷新
t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
},
// 标记调用wxs视图层的方法
callProp: {
callType: '', // 方法名
t: 0 // 数据更新的标记 (只有数据更新了,才会触发wxs的Observer)
},
// 不用wxs的平台使用此处的wxsBiz对象,抹平wxs的写法 (微信小程序和APP使用的wxsBiz对象是./wxs/wxs.wxs)
// #ifndef MP-WEIXIN || MP-QQ || APP-PLUS || H5
wxsBiz: {
//注册列表touchstart事件,用于下拉刷新
touchstartEvent: e=> {
this.mescroll.touchstartEvent(e);
},
//注册列表touchmove事件,用于下拉刷新
touchmoveEvent: e=> {
this.mescroll.touchmoveEvent(e);
},
//注册列表touchend事件,用于下拉刷新
touchendEvent: e=> {
this.mescroll.touchendEvent(e);
},
propObserver(){}, // 抹平wxs的写法
callObserver(){} // 抹平wxs的写法
},
// #endif
// 不用renderjs的平台使用此处的renderBiz对象,抹平renderjs的写法 (app 和 h5 使用的renderBiz对象是./wxs/renderjs.js)
// #ifndef APP-PLUS || H5
renderBiz: {
propObserver(){} // 抹平renderjs的写法
}
// #endif
}
},
methods: {
// wxs视图层调用逻辑层的回调
wxsCall(msg){
if(msg.type === 'setWxsProp'){
// 更新wxsProp数据 (值改变才触发更新)
this.wxsProp = {
optDown: this.mescroll.optDown,
scrollTop: this.mescroll.getScrollTop(),
bodyHeight: this.mescroll.getBodyHeight(),
isDownScrolling: this.mescroll.isDownScrolling,
isUpScrolling: this.mescroll.isUpScrolling,
isUpBoth: this.mescroll.optUp.isBoth,
isScrollBody:this.mescroll.isScrollBody,
t: Date.now()
}
}else if(msg.type === 'setLoadType'){
// 设置inOffset,outOffset的状态
this.downLoadType = msg.downLoadType
// 状态挂载到mescroll对象, 以便在其他组件中使用, 比如<me-video>中
this.$set(this.mescroll, 'downLoadType', this.downLoadType)
// 重置是否加载成功的状态
this.$set(this.mescroll, 'isDownEndSuccess', null)
}else if(msg.type === 'triggerDownScroll'){
// 主动触发下拉刷新
this.mescroll.triggerDownScroll();
}else if(msg.type === 'endDownScroll'){
// 结束下拉刷新
this.mescroll.endDownScroll();
}else if(msg.type === 'triggerUpScroll'){
// 主动触发上拉加载
this.mescroll.triggerUpScroll(true);
}
}
},
mounted() {
// #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5
// 配置主动触发wxs显示加载进度的回调
this.mescroll.optDown.afterLoading = ()=>{
this.callProp = {callType: "showLoading", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
}
// 配置主动触发wxs隐藏加载进度的回调
this.mescroll.optDown.afterEndDownScroll = ()=>{
this.callProp = {callType: "endDownScroll", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
let delay = 300 + (this.mescroll.optDown.beforeEndDelay || 0)
setTimeout(()=>{
if(this.downLoadType === 4 || this.downLoadType === 0){
this.callProp = {callType: "clearTransform", t: Date.now()} // 触发wxs的方法 (值改变才触发更新)
}
// 状态挂载到mescroll对象, 以便在其他组件中使用, 比如<me-video>中
this.$set(this.mescroll, 'downLoadType', this.downLoadType)
}, delay)
}
// 初始化wxs的数据
this.wxsCall({type: 'setWxsProp'})
// #endif
}
}
export default WxsMixin;

92
uni_modules/mescroll-uni/components/mescroll-uni/wxs/renderjs.js

@ -1,92 +0,0 @@
// 使用renderjs直接操作window对象,实现动态控制app和h5的bounce
// bounce: iOS橡皮筋,Android半月弧,h5浏览器下拉背景等效果 (下拉刷新时禁止)
// https://uniapp.dcloud.io/frame?id=renderjs
// 与wxs的me实例一致
var me = {}
// 初始化window对象的touch事件 (仅初始化一次)
if(window && !window.$mescrollRenderInit){
window.$mescrollRenderInit = true
window.addEventListener('touchstart', function(e){
if (me.disabled()) return;
me.startPoint = me.getPoint(e); // 记录起点
}, {passive: true})
window.addEventListener('touchmove', function(e){
if (me.disabled()) return;
if (me.getScrollTop() > 0) return; // 需在顶部下拉,才禁止bounce
var curPoint = me.getPoint(e); // 当前点
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
// 向下拉
if (moveY > 0) {
// 可下拉的条件
if (!me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling && me.isUpBoth))) {
// 只有touch在mescroll的view上面,才禁止bounce
var el = e.target;
var isMescrollTouch = false;
while (el && el.tagName && el.tagName !== 'UNI-PAGE-BODY' && el.tagName != "BODY") {
var cls = el.classList;
if (cls && cls.contains('mescroll-render-touch')) {
isMescrollTouch = true
break;
}
el = el.parentNode; // 继续检查其父元素
}
// 禁止bounce (不会对swiper和iOS侧滑返回造成影响)
if (isMescrollTouch && e.cancelable && !e.defaultPrevented) e.preventDefault();
}
}
}, {passive: false})
}
/* 获取滚动条的位置 */
me.getScrollTop = function() {
return me.scrollTop || 0
}
/* 是否禁用下拉刷新 */
me.disabled = function(){
return !me.optDown || !me.optDown.use || me.optDown.native
}
/* 根据点击滑动事件获取第一个手指的坐标 */
me.getPoint = function(e) {
if (!e) {
return {x: 0,y: 0}
}
if (e.touches && e.touches[0]) {
return {x: e.touches[0].pageX,y: e.touches[0].pageY}
} else if (e.changedTouches && e.changedTouches[0]) {
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
} else {
return {x: e.clientX,y: e.clientY}
}
}
/**
* 监听逻辑层数据的变化 (实时更新数据)
*/
function propObserver(wxsProp) {
me.optDown = wxsProp.optDown
me.scrollTop = wxsProp.scrollTop
me.isDownScrolling = wxsProp.isDownScrolling
me.isUpScrolling = wxsProp.isUpScrolling
me.isUpBoth = wxsProp.isUpBoth
}
/* 导出模块 */
const renderBiz = {
data() {
return {
propObserver: propObserver,
}
}
}
export default renderBiz;

268
uni_modules/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs

@ -1,268 +0,0 @@
// 使用wxs处理交互动画, 提高性能, 同时避免小程序bounce对下拉刷新的影响
// https://uniapp.dcloud.io/frame?id=wxs
// https://developers.weixin.qq.com/miniprogram/dev/framework/view/interactive-animation.html
// 模拟mescroll实例, 与mescroll.js的写法尽量保持一致
var me = {}
// ------ 自定义下拉刷新动画 start ------
/* 下拉过程中的回调,滑动过程一直在执行 (rate<1为inOffset; rate>1为outOffset) */
me.onMoving = function (ins, rate, downHight){
ins.requestAnimationFrame(function () {
ins.selectComponent('.mescroll-wxs-content').setStyle({
'will-change': 'transform', // 可解决下拉过程中, image和swiper脱离文档流的问题
'transform': 'translateY(' + downHight + 'px)',
'transition': ''
})
// 环形进度条
var progress = ins.selectComponent('.mescroll-wxs-progress')
progress && progress.setStyle({transform: 'rotate(' + 360 * rate + 'deg)'})
})
}
/* 显示下拉刷新进度 */
me.showLoading = function (ins){
me.downHight = me.optDown.offset
ins.requestAnimationFrame(function () {
ins.selectComponent('.mescroll-wxs-content').setStyle({
'will-change': 'auto',
'transform': 'translateY(' + me.downHight + 'px)',
'transition': 'transform 300ms'
})
})
}
/* 结束下拉 */
me.endDownScroll = function (ins){
me.downHight = 0;
me.isDownScrolling = false;
ins.requestAnimationFrame(function () {
ins.selectComponent('.mescroll-wxs-content').setStyle({
'will-change': 'auto',
'transform': 'translateY(0)', // 不可以写空串,否则scroll-view渲染不完整 (延时350ms会调clearTransform置空)
'transition': 'transform 300ms'
})
})
}
/* 结束下拉动画执行完毕后, 清除transform和transition, 避免对列表内容样式造成影响, 如: h5的list-msg示例下拉进度条漏出来等 */
me.clearTransform = function (ins){
ins.requestAnimationFrame(function () {
ins.selectComponent('.mescroll-wxs-content').setStyle({
'will-change': '',
'transform': '',
'transition': ''
})
})
}
// ------ 自定义下拉刷新动画 end ------
/**
* 监听逻辑层数据的变化 (实时更新数据)
*/
function propObserver(wxsProp) {
me.optDown = wxsProp.optDown
me.scrollTop = wxsProp.scrollTop
me.bodyHeight = wxsProp.bodyHeight
me.isDownScrolling = wxsProp.isDownScrolling
me.isUpScrolling = wxsProp.isUpScrolling
me.isUpBoth = wxsProp.isUpBoth
me.isScrollBody = wxsProp.isScrollBody
me.startTop = wxsProp.scrollTop // 及时更新touchstart触发的startTop, 避免scroll-view快速惯性滚动到顶部取值不准确
}
/**
* 监听逻辑层数据的变化 (调用wxs的方法)
*/
function callObserver(callProp, oldValue, ins) {
if (me.disabled()) return;
if(callProp.callType){
// 逻辑层(App Service)的style已失效,需在视图层(Webview)设置style
if(callProp.callType === 'showLoading'){
me.showLoading(ins)
}else if(callProp.callType === 'endDownScroll'){
me.endDownScroll(ins)
}else if(callProp.callType === 'clearTransform'){
me.clearTransform(ins)
}
}
}
/**
* touch事件
*/
function touchstartEvent(e, ins) {
me.downHight = 0; // 下拉的距离
me.startPoint = me.getPoint(e); // 记录起点
me.startTop = me.getScrollTop(); // 记录此时的滚动条位置
me.startAngle = 0; // 初始角度
me.lastPoint = me.startPoint; // 重置上次move的点
me.maxTouchmoveY = me.getBodyHeight() - me.optDown.bottomOffset; // 手指触摸的最大范围(写在touchstart避免body获取高度为0的情况)
me.inTouchend = false; // 标记不是touchend
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
}
function touchmoveEvent(e, ins) {
var isPrevent = true // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
if (me.disabled()) return isPrevent;
var scrollTop = me.getScrollTop(); // 当前滚动条的距离
var curPoint = me.getPoint(e); // 当前点
var moveY = curPoint.y - me.startPoint.y; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
// 向下拉 && 在顶部
// mescroll-body,直接判定在顶部即可
// scroll-view在滚动时不会触发touchmove,当触顶/底/左/右时,才会触发touchmove
// scroll-view滚动到顶部时,scrollTop不一定为0,也有可能大于0; 在iOS的APP中scrollTop可能为负数,不一定和startTop相等
if (moveY > 0 && (
(me.isScrollBody && scrollTop <= 0)
||
(!me.isScrollBody && (scrollTop <= 0 || (scrollTop <= me.optDown.startTop && scrollTop === me.startTop)) )
)) {
// 可下拉的条件
if (!me.inTouchend && !me.isDownScrolling && !me.optDown.isLock && (!me.isUpScrolling || (me.isUpScrolling &&
me.isUpBoth))) {
// 下拉的角度是否在配置的范围内
if(!me.startAngle) me.startAngle = me.getAngle(me.lastPoint, curPoint); // 两点之间的角度,区间 [0,90]
if (me.startAngle < me.optDown.minAngle) return isPrevent; // 如果小于配置的角度,则不往下执行下拉刷新
// 如果手指的位置超过配置的距离,则提前结束下拉,避免Webview嵌套导致touchend无法触发
if (me.maxTouchmoveY > 0 && curPoint.y >= me.maxTouchmoveY) {
me.inTouchend = true; // 标记执行touchend
touchendEvent(e, ins); // 提前触发touchend
return isPrevent;
}
isPrevent = false // 小程序是return false
var diff = curPoint.y - me.lastPoint.y; // 和上次比,移动的距离 (大于0向下,小于0向上)
// 下拉距离 < 指定距离
if (me.downHight < me.optDown.offset) {
if (me.movetype !== 1) {
me.movetype = 1; // 加入标记,保证只执行一次
// me.optDown.inOffset && me.optDown.inOffset(me); // 进入指定距离范围内那一刻的回调,只执行一次
me.callMethod(ins, {type: 'setLoadType', downLoadType: 1})
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
}
me.downHight += diff * me.optDown.inOffsetRate; // 越往下,高度变化越小
// 指定距离 <= 下拉距离
} else {
if (me.movetype !== 2) {
me.movetype = 2; // 加入标记,保证只执行一次
// me.optDown.outOffset && me.optDown.outOffset(me); // 下拉超过指定距离那一刻的回调,只执行一次
me.callMethod(ins, {type: 'setLoadType', downLoadType: 2})
me.isMoveDown = true; // 标记下拉区域高度改变,在touchend重置回来
}
if (diff > 0) { // 向下拉
me.downHight += diff * me.optDown.outOffsetRate; // 越往下,高度变化越小
} else { // 向上收
me.downHight += diff; // 向上收回高度,则向上滑多少收多少高度
}
}
me.downHight = Math.round(me.downHight) // 取整
var rate = me.downHight / me.optDown.offset; // 下拉区域当前高度与指定距离的比值
// me.optDown.onMoving && me.optDown.onMoving(me, rate, me.downHight); // 下拉过程中的回调,一直在执行
me.onMoving(ins, rate, me.downHight)
}
}
me.lastPoint = curPoint; // 记录本次移动的点
return isPrevent // false表示不往上冒泡,相当于调用了同时调用了stopPropagation和preventDefault (对小程序生效, h5和app无效)
}
function touchendEvent(e, ins) {
// 如果下拉区域高度已改变,则需重置回来
if (me.isMoveDown) {
if (me.downHight >= me.optDown.offset) {
// 符合触发刷新的条件
me.downHight = me.optDown.offset; // 更新下拉区域高度
// me.triggerDownScroll();
me.callMethod(ins, {type: 'triggerDownScroll'})
} else {
// 不符合的话 则重置
me.downHight = 0;
// me.optDown.endDownScroll && me.optDown.endDownScroll(me);
me.callMethod(ins, {type: 'endDownScroll'})
}
me.movetype = 0;
me.isMoveDown = false;
} else if (!me.isScrollBody && me.getScrollTop() === me.startTop) { // scroll-view到顶/左/右/底的滑动事件
var isScrollUp = me.getPoint(e).y - me.startPoint.y < 0; // 和起点比,移动的距离,大于0向下拉,小于0向上拉
// 上滑
if (isScrollUp) {
// 需检查滑动的角度
var angle = me.getAngle(me.getPoint(e), me.startPoint); // 两点之间的角度,区间 [0,90]
if (angle > 80) {
// 检查并触发上拉
// me.triggerUpScroll(true);
me.callMethod(ins, {type: 'triggerUpScroll'})
}
}
}
me.callMethod(ins, {type: 'setWxsProp'}) // 同步更新wxsProp的数据 (小程序是异步的,可能touchmove先执行,才到propObserver; h5和app是同步)
}
/* 是否禁用下拉刷新 */
me.disabled = function(){
return !me.optDown || !me.optDown.use || me.optDown.native
}
/* 根据点击滑动事件获取第一个手指的坐标 */
me.getPoint = function(e) {
if (!e) {
return {x: 0,y: 0}
}
if (e.touches && e.touches[0]) {
return {x: e.touches[0].pageX,y: e.touches[0].pageY}
} else if (e.changedTouches && e.changedTouches[0]) {
return {x: e.changedTouches[0].pageX,y: e.changedTouches[0].pageY}
} else {
return {x: e.clientX,y: e.clientY}
}
}
/* 计算两点之间的角度: 区间 [0,90]*/
me.getAngle = function (p1, p2) {
var x = Math.abs(p1.x - p2.x);
var y = Math.abs(p1.y - p2.y);
var z = Math.sqrt(x * x + y * y);
var angle = 0;
if (z !== 0) {
angle = Math.asin(y / z) / Math.PI * 180;
}
return angle
}
/* 获取滚动条的位置 */
me.getScrollTop = function() {
return me.scrollTop || 0
}
/* 获取body的高度 */
me.getBodyHeight = function() {
return me.bodyHeight || 0;
}
/* 调用逻辑层的方法 */
me.callMethod = function(ins, param) {
if(ins) ins.callMethod('wxsCall', param)
}
/* 导出模块 */
module.exports = {
propObserver: propObserver,
callObserver: callObserver,
touchstartEvent: touchstartEvent,
touchmoveEvent: touchmoveEvent,
touchendEvent: touchendEvent
}

80
uni_modules/mescroll-uni/package.json

@ -1,80 +0,0 @@
{
"id": "mescroll-uni",
"displayName": "【wxs+renderjs实现】高性能的下拉刷新上拉加载组件",
"version": "1.3.7",
"description": "支持uni-app的下拉刷新和上拉加载的组件,支持原生页面和局部区域滚动,支持国际化",
"keywords": [
"下拉刷新",
"上拉加载",
"翻页",
"分页",
"wxs"
],
"repository": "https://github.com/mescroll/mescroll",
"engines": {
"HBuilderX": "^3.1.0"
},
"dcloudext": {
"category": [
"前端组件",
"通用组件"
],
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": ""
},
"declaration": {
"ads": "无",
"data": "无",
"permissions": "无"
},
"npmurl": "https://www.npmjs.com/package/mescroll-uni"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "y",
"aliyun": "y"
},
"client": {
"App": {
"app-vue": "y",
"app-nvue": "y"
},
"H5-mobile": {
"Safari": "y",
"Android Browser": "y",
"微信浏览器(Android)": "y",
"QQ浏览器(Android)": "y"
},
"H5-pc": {
"Chrome": "y",
"IE": "y",
"Edge": "y",
"Firefox": "y",
"Safari": "y"
},
"小程序": {
"微信": "y",
"阿里": "y",
"百度": "y",
"字节跳动": "y",
"QQ": "y"
},
"快应用": {
"华为": "y",
"联盟": "y"
}
}
}
}
}

45
uni_modules/mescroll-uni/readme.md

@ -1,45 +0,0 @@
## mescroll --【wxs+renderjs实现】高性能的下拉刷新上拉加载组件
1. mescroll的uni版本 是专门用在uni-app的下拉刷新和上拉加载的组件
2. mescroll的uni版本 继承了mescroll.js的实用功能: 自动处理分页, 自动控制无数据, 空布局提示, 回到顶部按钮 ..
3. mescroll的uni版本 丰富的案例, 自由灵活的api, 超详细的注释, 可让您快速自定义真正属于自己的下拉上拉组件
<br/>
## 最新文档(1.3.7版本): <a href="https://www.mescroll.com/uni.html">https://www.mescroll.com/uni.html</a>
2021-04-13 by 小瑾同学 (文档可能会有缓存,建议打开时刷新一下)
## 1.3.5版本已调整为[uni_modules](https://uniapp.dcloud.io/uni_modules)
uni_modules版本的mescroll-body 和 mescroll-empty 支持 [easycom规范](https://uniapp.dcloud.io/collocation/pages?id=easycom)
所以 main.js 无需再为mescroll-body注册全局组件
所以个别页面要单独使用 mescroll-empty , 也无需手动注册
#### 1.3.5以前的用户升级为uni_modules版本:
```
1. 删除原来的 @/components/mescroll-uni 组件
2. 删除 main.js 注册的 mescroll 组件
3. 从插件市场导入最新mescroll组件 (1.3.5+uni_modules版本)
4. 全局搜索 '@/components/mescroll-uni/' 替换为 '@/uni_modules/mescroll-uni/components/mescroll-uni/'
5. mescroll-empty遵循easycom规范, 若某些页面单独使用 'mescroll-empty.vue', 可删除手动导入的代码
```
## 近期已更新优化的内容:
1. 微信小程序, app, h5使用高性能wxs和renderjs, 下拉刷新更流畅丝滑, 尤其能明显解决Android小程序下拉卡顿的问题
2. 新增`入门极简`示例, 国际化`mescroll-i18n.vue`示例, 轮播吸顶菜单`mescroll-swiper-sticky.vue`示例
3. 新增 "局部区域滚动" 的案例: mescroll-body-part.vue 和 mescroll-uni-part.vue
4. 新增 me-video 视频组件, 解决APP端视频下拉悬浮错位的问题, 参考 mescroll-options.vue 示例
5. 新增 me-tabs 组件,tabs支持水平滑动; 优化mescroll-more和mescroll-swiper的案例, 顶部tab支持水平滑动
6. 吸顶悬浮提供了原生sticky和监听滚动条实现的示例: sticky.vue 和 sticky-scroll.vue (推荐使用sticky样式实现)
7. mescroll.scrollTo(y)的y支持css选择器, 包括跨自定义组件的后代选择器, 支持滚动到子组件的view (参考 mescroll-options.vue)
8. topbar 顶部是否预留状态栏的高度, 默认false; 还可支持设置状态栏背景: 如 '#ffff00', 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)'
9. down.bgColor 和 up.bgColor 加载区域的背景,不仅支持色值, 而且还是支持背景图和渐变: 如 'url(xxx) 0 0/100% 100%', 'linear-gradient(xx)'
10. topbar,bgColor支持一行代码定义background: [https://www.runoob.com/cssref/css3-pr-background.html](https://www.runoob.com/cssref/css3-pr-background.html)
<br/>
<br/>
<a href="https://ext.dcloud.net.cn/plugin?id=343&update_log">查看更多 ... </a>
<br/>
#### mescroll不支持nvue,也暂无支持的计划哈,so sorry~

3
uni_modules/niu-ui/assets/script/util.js

@ -0,0 +1,3 @@
export function (){
}

145
uni_modules/niu-ui/assets/style/common.scss

@ -0,0 +1,145 @@
page {
// background-color: #FFF8F8;
background-color: #F1F1F1;
// min-height: 100%;
height: 100%;
font-size: 28rpx;
line-height: 1.2; //
color: #000;
}
page .tabpage{
// 怪异盒模型
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
/* #ifdef H5 */
//貌似不需要了已经是去除了tabbar的高度了
//自定义tabbar的时候需要
// 底部tab栏高度
margin-bottom: 0;//var(--window-bottom);
/* #endif */
/* #ifndef H5 */
margin-bottom: 0;
/* #endif */
// 触发BFC
// overflow-x: hidden;
// overflow-y: scroll;
min-height: 100%;
}
page .page{
// 怪异盒模型
-webkit-overflow-scrolling: touch;
box-sizing: border-box;
// 触发BFC
// overflow-x: hidden;
// overflow-y: scroll;
min-height: 100%;
}
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
display: none;
}
page *{
box-sizing: border-box;
}
.a-flexbox{
display: flex;
&.a-flexbox--fixed{
flex-shrink: 0;
flex-grow: 0;
}
&.a-flexbox--inline{
display: inline-flex;
}
&.a-flexbox__column{
flex-direction: column;
}
&.a-flexbox__row{
flex-direction: row;
}
&.a-flexbox__wrap{
flex-wrap: wrap;
}
&.a-flexbox__center{
align-items: center;
justify-content: center;
}
&.a-flexbox__center--x{
justify-content: center;
}
&.a-flexbox--around{
justify-content: space-around;
}
&.a-flexbox--between{
justify-content: space-between;
}
&.a-flexbox__start--x{
justify-content: flex-start;
}
&.a-flexbox__end--x{
justify-content: flex-end;
}
&.a-flexbox__center--y{
align-items: center;
}
&.a-flexbox__start--y{
align-items: flex-start;
}
&.a-flexbox__end--y{
align-items: flex-end;
}
.a-flexbox__noshrink{
flex-shrink: 0;
}
.a-flexbox__full{
flex: 1;
width: 0;
}
.a-flexbox__full--h{
flex: 1;
height: 0;
}
}
// 单行省略号
.a-ov{
@include ellipsis(1)
}
// 两行省略号
.a-ov2{
@include ellipsis(2)
}
// 包裹输入框
.a-wrapinput{
position:relative;
display: flex;
align-items: center;
// height: 100%;
>input{
width: 0;
flex: 1;
// height: 100%;
font-size: inherit;
line-height: inherit;
}
.sub{
display: flex;
align-items: center;
margin-right: 8rpx;
}
}
// 包裹文本域
.a-wraptextarea{
min-height: 160rpx;
height: 160rpx;
>textarea{
font-size: inherit;
width: 100%;
height: 100%;
}
}

112
uni_modules/niu-ui/assets/style/global.scss

@ -0,0 +1,112 @@
/*
* 内部通用scss
*/
@mixin clearfix {
&:after {
clear: both;
content: '.';
display: block;
height: 0;
line-height: 0;
overflow: hidden;
}
// *height: 1%;
}
@mixin hairline {
&::after {
content: " ";
box-sizing: border-box;
pointer-events: none;
border: 0 solid #ebedf0;
position: absolute;
top: -50%;
right: -50%;
bottom: -50%;
left: -50%;
-webkit-transform: scale(0.5);
transform: scale(0.5);
@content
}
};
@mixin hairline--no {
@include hairline{
border: 0;
};
}
@mixin hairline--all {
@include hairline{
border-width: 1px;
};
}
@mixin hairline--right {
@include hairline{
border-right-width: 1px;
};
}
@mixin hairline--left {
@include hairline{
border-left-width: 1px;
};
}
@mixin hairline--bottom {
@include hairline{
border-bottom-width: 1px;
};
}
@mixin hairline--top {
@include hairline{
border-top-width: 1px;
};
}
@mixin hairline--right--bottom {
@include hairline{
border-left-width: 0;
border-top-width: 0;
border-right-width: 1px;
border-bottom-width: 1px;
};
}
@mixin hairline--left--top {
@include hairline{
border-right-width: 0;
border-bottom-width: 0;
border-left-width: 1px;
border-top-width: 1px;
};
}
//一行溢出
@mixin text-overflow{
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
};
//两行溢出
@mixin ov($num: 2){
text-overflow: -o-ellipsis-lastline;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: $num;
line-clamp: $num;
-webkit-box-orient: vertical;
}
@mixin ellipsis($lines: 1) {
@if ($lines==1) {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
} @else {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: $lines;
overflow: hidden;
text-overflow: ellipsis;
word-break: break-all;
}
}

32
uni_modules/niu-ui/components/niu-button/niu-button.vue

@ -0,0 +1,32 @@
<template>
<button class="niu-btn niu-btn--default" hover-class="none">
阿松大
</button>
</template>
<script>
</script>
<style lang="scss" scoped>
.niu-btn{
background-color: inherit;
line-height: inherit;
font-size: inherit;
color: inherit;
margin: 0;
padding: 0;
border: 0;
border-radius: 0;
&::after{
border: 0;
border-radius: 0;
}
display: inline-block;
&.niu-btn--default{
padding: 8rpx 36rpx;
color: white;
border-radius: 8rpx;
background-color: #1AAD19;
}
}
</style>

5
uni_modules/niu-ui/components/niu-card/niu-card.vue

@ -0,0 +1,5 @@
<template>
<view>
{{$n.navbar.top}}px
</view>
</template>

125
uni_modules/niu-ui/components/niu-grid-item/niu-grid-item.vue

@ -0,0 +1,125 @@
<template>
<view class="grid-item" :class="{hover}" :style="{width:center?'':width,padding: getPadding}">
<view class="grid-item__wrapper">
<image :style="{width:rect,height:rect,}"
class="grid-item__icon"
v-if="type=='icon'&&icon"
:src="icon" mode="aspectFill"></image>
<view class="grid-item__text" :style="{padding: top}" :class="{oneline}">
<text v-if="always">{{text}}</text>
<slot><text v-if="!always">{{text}}</text></slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: "niu-grid-item",
props: {
always:{
type: Boolean,
default: false,
},
icon:{
type: String,
default: '',
},
button:{
type: String,
default: '',
},
topText:{
type: String,
default: '',
},
text:{
type: String,
default: '',
},
padding: {
type: String,
default: '15rpx 20rpx',
},
oneline:{
type: Boolean,
default: false
},
top: {
type: String,
default: '',
}
},
data() {
return {
num: 5,
rect: '80rpx',
type: 'icon',
hover: true,
center: false
}
},
computed: {
width(){
if(this.num == 0) return 0 + '%';
return 100/this.num + '%';
},
getPadding(){
return this.padding
}
},
mounted() {
let parent = this.$n.util.getParent('niu-grid',this);
console.log(parent);
if(parent){
this.num = parent.num;
this.rect = parent.rect;
this.type = parent.type;
this.hover = parent.hover;
this.center = parent.center;
}
},
methods: {
bindGetUserInfo(e) {
this.$emit("userinfo",e)
}
},
}
</script>
<style lang="scss" scoped>
.grid-item{
display: inline-flex;
box-sizing: border-box;
height: 100%;
&.hover:active{
background-color: #f5f5f5;
}
.grid-item__wrapper{
margin: auto;
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
position: relative;
.grid-item__text{
white-space: nowrap;
&.oneline{
width: 100%;
@include ellipsis(1);
}
}
.button{
position: absolute;
left: 0;
bottom: 0;
top: 0;
right: 0;
z-index: 1;
opacity: 0;
}
}
}
</style>

55
uni_modules/niu-ui/components/niu-grid/niu-grid.vue

@ -0,0 +1,55 @@
<template>
<view class="niu-grid" :class="{center}" :style="[gridStyle]">
<view class="niu-grid-item">
<slot></slot>
</view>
</view>
</template>
<script>
export default{
name: "niu-grid",
props:{
num: {
type: Number,
default: 5
},
hover: {
type: Boolean,
default: true
},
center: {
type: Boolean,
default: false
},
type: {
type: String,
default: 'icon'
},
gridStyle: {
type: Object,
default: ()=>{}
},
rect: {
type: String,
default: '80rpx'
},
}
}
</script>
<style lang="scss" scoped>
.niu-grid{
box-sizing: border-box;
&.center{
display: flex;
align-items: center;
justify-content: center;
.niu-grid-item{
margin: 0;
}
}
}
</style>

141
uni_modules/niu-ui/components/niu-image/niu-image.vue

@ -0,0 +1,141 @@
<template>
<view @click.stop="$emit('click')" class="niu-image" :class="[inline?'niu-image--inline':'']" :style="[customStyle]">
<image v-if="preview" @click.stop="clickPreview" class="image" :mode="mode" :src="src"
:style="{borderRadius: radius,border:border}" :class="[circle?'niu-image--circle':'']"></image>
<image v-if="!preview" class="image" :mode="mode" :src="src" :style="{borderRadius: radius,border:border}"
:class="[circle?'niu-image--circle':'']"></image>
</view>
</template>
<script>
export default {
props: {
mode: {
type: String,
default: 'scaleToFill'
},
radius: {
type: String,
default: ''
},
border: {
type: String,
default: ''
},
src: {
type: String,
default: ""
},
width: {
type: String,
default: ""
},
inline: {
type: Boolean,
default: false
},
height: {
type: String,
default: ''
},
circle: {
type: Boolean,
default: false
},
rect: {
type: String,
default: ''
},
preview: {
type: Boolean,
default: false
},
left: {
type: String,
default: ''
},
right: {
type: String,
default: ''
},
top: {
type: String,
default: ''
},
bottom: {
type: String,
default: ''
},
margin: {
type: String,
default: ''
}
},
data() {
return {
};
},
methods: {
clickPreview(e) {
if (!this.preview) {
return
}
uni.previewImage({
urls: [this.src]
})
}
},
computed: {
customStyle() {
let style = {};
if (this.left) {
style.marginLeft = this.left
}
if (this.right) {
style.marginRight = this.right
}
if (this.top) {
style.marginTop = this.top
}
if (this.bottom) {
style.marginBottom = this.bottom
}
if (this.margin) {
style.margin = this.margin
}
let obj = {
width: this.width ? this.width : this.rect ? this.rect : '100%',
height: this.height ? this.height : this.rect ? this.rect : '100%',
...style
}
return obj
}
},
}
</script>
<style lang="scss" scoped>
.niu-image {
// inline-block
&--circle {
border-radius: 50%;
overflow: hidden;
}
&--inline {
display: inline-block;
line-height: 1;
}
font-size: 0;
.image {
display: block;
width: 100%;
height: 100%;
}
}
</style>

2
uni_modules/niu-ui/components/niu-navbar/niu-navbar.vue

@ -175,6 +175,7 @@
// #endif
},
created() {
this.$n.navbar.top = this.getNavbarHeight()
// #ifdef MP
let statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
let menuButtonInfo = uni.getMenuButtonBoundingClientRect()
@ -192,7 +193,6 @@
if (pages.length == 1 && mainPagePath.includes(pages[0].route)) {
this.firstPage = true;
}
this.$n.$obs.setTop(this.getNavbarHeight())
// #ifdef H5
window.addEventListener("scroll", this.scroll, false)
// #endif

14
components/Page/Page.vue → uni_modules/niu-ui/components/niu-page/niu-page.vue

@ -1,5 +1,5 @@
<template>
<view :className="className">
<view :class="[className, Tab?'tabpage':'page']" :style="inStyle">
<slot></slot>
</view>
</template>
@ -8,10 +8,18 @@
export default {
name: "Page",
props: {
Tab: {
type: Boolean,
default: false
},
className: {
type: String || Array,
default: ''
},
inStyle:{
type: String || Object,
default: ''
}
},
data() {
return {
@ -31,6 +39,6 @@
}
</script>
<style lang="scss">
<style lang="scss" scoped>
</style>

82
uni_modules/niu-ui/components/niu-tabbar/niu-tabbar.vue

@ -0,0 +1,82 @@
<template>
<view class="niu-tabbar">
<view class="niu-tabbar__block"></view>
<view class="niu-tabbar__wrapper">
<view class="niu-tabbar__item__wrapper" v-for="(item,index) in list">
<view class="niu-tabbar__item">
<view class="niu-tabbar__icon">
<image src="https://i.loli.net/2021/08/02/PEKnxSkbAHdtFfi.png"></image>
</view>
<view class="niu-tabbar__text">
首页
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default{
data() {
return {
list: []
}
},
}
</script>
<style lang="scss" scoped>
// TODO
$height: 110rpx;
$bg: white;
.niu-tabbar{
.niu-tabbar__block{
height: $height;
}
.niu-tabbar__wrapper{
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: $height;
background-color: $bg;
display: flex;
justify-content: center;
align-items: center;
text-align: center;
.niu-tabbar__item__wrapper{
flex: 1;
width: 0;
display: flex;
justify-content: center;
align-items: center;
.niu-tabbar__item{
position: relative;
padding: 10rpx 20rpx;
&::after{
content: "";
position: absolute;
right: 0;
top: 10rpx;
width: 10rpx;
height: 10rpx;
background-color: red;
}
.niu-tabbar__icon{
image{
width: 55rpx;
height: 55rpx;
}
}
.niu-tabbar__text{
line-height: 1;
font-size: 24rpx;
color: #333333;
padding-top: 5rpx;
}
}
}
}
}
</style>

315
uni_modules/niu-ui/components/niu-tabs/niu-tabs.vue

@ -0,0 +1,315 @@
<template>
<view class="niu-x-tabs component">
<!-- <view class="scroll-view"> -->
<scroll-view :show-scrollbar="false" class="scroll-view" :scroll-x='!full' :scroll-with-animation='true' :scroll-left="left">
<view class="niu-x-tabs__wrapper">
<view class="niu-x-tabs__inner" :class="{full,capsule}" :style="{backgroundColor: bg, height: height}">
<view class="niu-x-tabs__item" :class="[
value==index?'niu-x-tabs__active':'',capsule?'capsule':'',
value==index?activeClass:normalClass
]"
:style="[value==index?activeCustomStyle:normalCustomStyle, anim?{transition: '.3s linear'}:{}]" :key="index" v-for="(item,index) in list" @click="click(index,true)">
<slot :data="item" :pData="pData" :index="index">
<text>{{item[akey]}}</text>
<view v-if="value==index && activeImage" class="niu-x-tabs__bg">
<image class="image" :src="activeImage" mode="widthFix"></image>
</view>
</slot>
</view>
<!-- {{ !!(value<list.length) }} -->
<slot name="bar"
:show="barIsShow"
:barLeft="barLeft"
:barWidth="barWidth"
>
<view v-if="bar&&value<list.length" class="niu-x-tabs__bar" :class="{capsule}" :style="[{left:barLeft + 'px',width:barWidth + 'px',backgroundColor: barColor},, anim?{transition: 'left .3s ease, width .3s ease'}:{}]"></view>
</slot>
</view>
</view>
</scroll-view>
<!-- </view> -->
</view>
</template>
<script>
export default {
props: {
list: {
type: Array,
default: () => []
},
pData: {
type: Object|Array,
default: () => {}
},
akey: {
type: String,
default: 'text'
},
width: {
type: String,
default: 'auto'
},
height: {
type: String,
default: '80rpx'
},
value: {
type: Number,
default: 0
},
full: {
type: Boolean,
default: false
},
capsule: {
type: Boolean,
default: false
},
normalColor: {
type: String,
default: '#B3B3B3'
},
activeColor: {
type: String,
default: '#39b54a'
},
capsuleActiveColor: {
type: String,
default: '#FFFFFF'
},
normalClass: {
type: String,
default: null
},
activeImage: {
type: String,
default: ''
},
activeClass: {
type: String,
default: null
},
normalStyle: {
type: Object,
default: null
},
activeStyle: {
type: Object,
default: null
},
bar: {
type: Boolean,
default: true
},
barColor: {
type: String,
default: '#39b54a'
},
bg: {
type: String,
default: ''
}
},
data() {
return {
anim: false,
left: 0,
isRender: false,
allLeft: [],
barLeft: 0,
barWidth: 200,
x_ml: 0 //
};
},
computed: {
barIsShow(){
return !!(this.value<this.list.length)
},
activeCustomStyle() {
let customStyle = this.activeStyle ? this.activeStyle : {};
return {
color: this.capsule?this.capsuleActiveColor:this.activeColor,
width: this.width,
...customStyle
}
},
normalCustomStyle() {
let customStyle = this.normalStyle ? this.normalStyle : {};
return {
color: this.normalColor,
width: this.width,
...customStyle
}
}
},
mounted() {
this.init()
},
watch: {
list(newValue, oldValue) {
this.init()
},
value(newValue, oldValue) {
if (this.isRender) return
if(newValue>=this.list.length) return
this.clickHere(newValue)
}
},
methods: {
init(index = 0) {
this.isRender = true
this.getRect('.niu-x-tabs__inner').then(res => {
this.x_ml = res.left;
})
this.allLeft = []
let p = uni.upx2px(50)
this.getAllReact('.niu-x-tabs__inner .niu-x-tabs__item').then(res => {
this.allLeft = res.map(v => {
return [v.left - this.x_ml, v.width]
})
this.isRender = false
this.clickHere(index)
this.left = index != 0 ? (this.allLeft[index][0]) : 0;
setTimeout(()=>{
this.anim = true
},300)
})
},
click(index) {
this.$emit("input", index)
},
clickHere(index, isClick) {
if (isClick && this.value == index) return
if (this.allLeft.length) {
this.$emit("change", index)
this.barLeft = this.allLeft[index][0]
this.barWidth = this.allLeft[index][1]
this.left = (this.allLeft[index][0] - this.allLeft[index][1] * 2)
}
},
getAllReact(selector) {
return new Promise(resolve => {
this.$nextTick(() => {
let query = uni.createSelectorQuery()
.in(this)
.selectAll(selector);
query.boundingClientRect(res => {
resolve(res)
}).exec()
})
})
},
getRect(selector) {
return new Promise(resolve => {
this.$nextTick(() => {
let query = uni.createSelectorQuery()
.in(this)
.select(selector);
query.boundingClientRect(res => {
resolve(res)
}).exec()
})
})
}
}
}
</script>
<style lang="scss" scoped>
$themeColor: #39b54a;
$capsuleColor: #ffffff;
$bg: #F0F0F0;
::-webkit-scrollbar {
width: 0;
height: 0;
color: transparent;
}
.niu-x-tabs.component {
position: relative;
background-color: transparent;
.niu-x-tabs__wrapper {
// width: 100%;
// overflow: auto;
}
.niu-x-tabs__inner {
display: inline-flex;
white-space: nowrap;
position: relative;
&.capsule {
overflow: hidden;
background-color: $bg;
}
&.full {
display: flex;
justify-content: space-around;
.niu-x-tabs__item {
flex: 1;
text-align: center;
}
}
// float: left;
.niu-x-tabs__item {
padding: 0 20rpx;
position: relative;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
&.niu-x-tabs__active {
color: $themeColor;
}
&.capsule {
margin: 0;
padding: 0 20rpx;
&.niu-x-tabs__active {
color: $capsuleColor;
}
}
.niu-x-tabs__bg {
position: absolute;
top: 0;
left: 0;
bottom: 0;
right: 0;
display: flex;
.image {
margin: auto;
width: 100%;
}
}
}
.niu-x-tabs__bar {
position: absolute;
bottom: 0;
left: 0;
width: 50rpx;
height: 2px;
background-color: $themeColor;
&.capsule {
bottom: auto;
top: 50%;
transform: translateY(-50%);
z-index: 0;
height:65%;
border-radius: 50rpx;
}
}
}
}
</style>

66
uni_modules/niu-ui/components/niu-upload/addicon.vue

@ -0,0 +1,66 @@
<template>
<view @click="$emit('click')" :class="['block', className]" :style="[myStyle]"></view>
</template>
<script>
export default {
props: {
width: {
type: String | Number,
default: ""
},
height: {
type: String | Number,
default: ""
},
className: {
type: String,
default: ""
},
},
computed: {
myStyle() {
let width = typeof this.width === "number"?this.width+"rpx":this.width;
let height = typeof this.height === "number"?this.height+"rpx":this.height;
return {
width: width,
height: height,
}
}
},
}
</script>
<style lang="scss" scoped>
.block {
$border: 1px;
box-sizing: border-box;
width: 100rpx;
height: 100rpx;
border: #{$border} solid #e1e1e1;
position: relative;
border-radius: 10rpx;
&::before {
content: "";
width: #{$border};
height: 50%;
background-color: #e1e1e1;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
&::after {
content: "";
width: 50%;
height: #{$border};
background-color: #e1e1e1;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
}
</style>

116
uni_modules/niu-ui/components/niu-upload/niu-upload.vue

@ -0,0 +1,116 @@
<template>
<u-upload ref="uUpload" name="imgs" @on-uploaded="uploadFinished"
:limitType="imagetype" :max-count="9" :action="action" width="140rpx" height="140rpx" custom-btn :auto-upload="false" >
<template slot="addBtn">
<addicon width="140rpx" height="140rpx"></addicon>
</template>
</u-upload>
</template>
<script>
import addicon from "./addicon.vue"
export default {
components:{
addicon
},
data() {
return {
imagetype:['png', 'jpg', 'jpeg'],
action: this.uploadurl,
}
},
methods: {
formatImages(){
const images = this.$refs.uUpload.lists;
let imgs = images.filter(image=>{
return image.response&&image.response.code==0&&image.response.data.length;
}).map(image=>{
return image.response.data[0].name
})
return imgs
},
bindCallback(fn){
this.uploadScuccess = fn.bind(this,"success")
this.uploadError = fn.bind(this,"failed")
},
clear(){
this.$refs.uUpload.clear()
},
getImages(){
return this.formatImages()
},
checkAndUpload(fn){
let isComplete = this.isComplete()
if(fn){
this.bindCallback(fn)
}
if(isComplete===0){
this.uploadScuccess(this.getImages())
return true
}
if(isComplete===1){
this.uploadImages()
return true
}
if(isComplete===2){
this.reUploadImages()
return true
}
if(isComplete===3){
this.uploadScuccess(this.getImages())
return true
}
return false
},
reUploadImages(fn) {
if(fn){
this.bindCallback(fn)
}
this.$refs.uUpload.reUpload()
},
uploadImages(fn) {
if(fn){
this.bindCallback(fn)
}
this.$refs.uUpload.upload()
},
isComplete(){
if(!this.$refs.uUpload.lists.length){
return 0 //
}
let res = this.$refs.uUpload.lists.filter(val => {
return val.progress == 100;
})
if(!res.length){
//
return 1
}
if(res.length&&res.length!==this.$refs.uUpload.lists.length){
//
return 2
}
return 3
},
uploadFinished() {
let res = this.$refs.uUpload.lists.filter(val => {
return val.progress == 100;
})
let len = this.$refs.uUpload.lists.length;
let picture=[];
for(let i=0;i<res.length;i++){
picture.push(res[i].response.data[0].name)
}
if(picture.length!=this.$refs.uUpload.lists.length){
this.uploadError(picture, len)
}else{
this.uploadScuccess(picture, len)
}
},
uploadScuccess(){},
uploadError() {},
},
}
</script>
<style>
</style>

159
uni_modules/niu-ui/components/niu-year/niu-year.vue

@ -0,0 +1,159 @@
<template>
<view class="niu-calendar">
<view class="niu-calendar-wrapper">
<view class="niu-calendar-title">
<view class="niu-calendar-left">
当前选中: {{curYear}}
</view>
<view class="niu-calendar-right" @click="today">
回到今年
</view>
</view>
<view class="niu-calendar-content">
<view class="niu-calendar-list">
<view class="niu-calendar-item" :class="[curYear==year?'show':'', tagToday&&todayYear==year?'today':'', year<minYear?'disabled':'', year>maxYear?'disabled':'']" @click="choosYear(year)" v-for="(year,index) in years">
<text>{{year}}</text>
<text v-if="tagToday&&todayYear==year">今年</text>
</view>
</view>
<view class="niu-calendar-pages">
<view class="niu-calendar-page" @click="lastPage">
上页
</view>
<view class="niu-calendar-page" @click="nextPage">
下页
</view>
</view>
</view>
<view class="button" @click="confrim">
确认
</view>
</view>
</view>
</template>
<script>
import { createYears } from "./util.js"
let curYear = new Date().getFullYear();
let last = [-11,12]
export default {
props: {
minYear: {
type: Number,
default: 1990
},
maxYear: {
type: Number,
default: 2100
},
/**
* 突出今年
*/
tagToday: {
type: Boolean,
default: true
}
},
data() {
return {
years: createYears(last),
todayYear: curYear,
curYear: curYear,
}
},
methods: {
confrim(){
this.$emit("confrim", this.curYear)
},
choosYear(year){
if(year<this.minYear||year>this.maxYear){
uni.showToast({
icon: "none",
title: "该年份无法选择"
})
return
}
this.curYear = year
},
today(){
let array = [-11,12]
this.years = createYears(array)
},
lastPage() {
let array = [last[0]-24,last[1]-24]
this.years = createYears(array)
last = array;
},
nextPage() {
let array = [last[0]+24,last[1]+24]
this.years = createYears(array)
last = array;
}
},
}
</script>
<style lang="scss" scoped>
.niu-calendar{
overflow: hidden;
}
.niu-calendar-title{
margin: 0 20rpx;
line-height: 1;
padding: 20rpx 0;
display: flex;
.niu-calendar-right{
flex: 1;
width: 0;
text-align: right;
}
}
.niu-calendar-pages{
display: flex;
.niu-calendar-page{
flex: 1;
text-align: center;
padding: 20rpx 0;
}
}
.niu-calendar-list{
display: flex;
align-items: center;
flex-wrap: wrap;
.niu-calendar-item{
flex-shrink: 0;
width: calc(100% / 6);
height: 80rpx;
margin: 0 0;
padding: 20rpx 0;
text-align: center;
display: flex;
justify-content: center;
flex-direction: column;
&.disabled{
color: #999;
}
&.today{
color: red;
font-weight: bold;
}
&.show{
background-color: #1AAD19;
border-radius: 10rpx;
color: white;
font-weight: bold;
}
}
}
.button{
margin: 20rpx;
background-color: #1AAD19;
border-radius: 10rpx;
text-align: center;
line-height: 1;
padding: 20rpx 0;
font-weight: bold;
color: white;
font-size: 30rpx;
}
</style>

15
uni_modules/niu-ui/components/niu-year/util.js

@ -0,0 +1,15 @@
export function createYears(interval = [0, 0], map){
let year = new Date().getFullYear()
if(interval[0]>interval[1]){
console.warn("区间不正确")
return
}
const fewYears = [...new Array(interval[1]-interval[0]+1)].map((v,i)=>{
if(map){
return map(year+interval[0]+i);
}else{
return year+interval[0]+i
}
})
return fewYears;
}

20
uni_modules/niu-ui/extensions/toast.js

@ -0,0 +1,20 @@
function toast(msg) {
uni.showToast({
icon: 'none',
title: msg
})
}
toast.success = function(msg) {
uni.showToast({
icon: 'success',
title: msg
})
}
toast.fail = function(msg) {
uni.showToast({
icon: "none",
title: msg
})
}
export default toast

14
uni_modules/niu-ui/index.js

@ -1,13 +1,15 @@
import * as util from "./util"
import toast from "./extensions/toast"
export default {
install(Vue) {
const obs = Vue.observable({
$Top: 0,
setTop(value) {
this.$Top = value;
},
const navbarData = Vue.observable({
top: 0
})
Vue.prototype.$n = {
$obs: obs
navbar: navbarData,
util: util,
toast: toast
}
}
}

2255
uni_modules/niu-ui/plugins/fuse.js

File diff suppressed because it is too large

14
uni_modules/niu-ui/util/index.js

@ -0,0 +1,14 @@
export function getParent(name, context) {
let parent = context.$parent;
// 通过while历遍,这里主要是为了H5需要多层解析的问题
while (parent) {
// 父组件
if (parent.$options && parent.$options.name !== name) {
// 如果组件的name不相等,继续上一级寻找
parent = parent.$parent;
} else {
return parent;
}
}
return false;
}
Loading…
Cancel
Save