大庆小程序正式服提交

master
e 3 years ago
parent a30bb3da6a
commit 408720618c
  1. 0
      .gitignore
  2. 11
      .hbuilderx/launch.json
  3. 21
      App.vue
  4. 184
      colorui/animation.css
  5. 69
      colorui/components/cu-custom.vue
  6. 1226
      colorui/icon.css
  7. 3927
      colorui/main.css
  8. 354
      components/cuihai-combox/cuihai-combox.vue
  9. 87
      components/customer-list/customer-list.vue
  10. 166
      components/evan-form-item/evan-form-item.vue
  11. 168
      components/evan-form/evan-form.vue
  12. 148
      components/evan-form/utils.js
  13. 110
      components/index-list/index-list.vue
  14. 450
      components/linkMan/linkMan.vue
  15. 200
      components/ly-tree/components/ly-checkbox.vue
  16. 436
      components/ly-tree/ly-tree-node.vue
  17. 607
      components/ly-tree/ly-tree.vue
  18. 538
      components/ly-tree/model/node.js
  19. 419
      components/ly-tree/model/tree-store.js
  20. 115
      components/ly-tree/tool/util.js
  21. 184
      components/me-tabs/me-tabs.vue
  22. 179
      components/me-video/me-video.vue
  23. 47
      components/mescroll-diy/beibei/components/mescroll-down.css
  24. 39
      components/mescroll-diy/beibei/components/mescroll-down.vue
  25. 330
      components/mescroll-diy/beibei/mescroll-body.vue
  26. 29
      components/mescroll-diy/beibei/mescroll-uni-option.js
  27. 406
      components/mescroll-diy/beibei/mescroll-uni.vue
  28. 44
      components/mescroll-diy/xinlang/components/mescroll-down.css
  29. 53
      components/mescroll-diy/xinlang/components/mescroll-down.vue
  30. 32
      components/mescroll-diy/xinlang/components/mescroll-up.css
  31. 40
      components/mescroll-diy/xinlang/components/mescroll-up.vue
  32. 350
      components/mescroll-diy/xinlang/mescroll-body.vue
  33. 36
      components/mescroll-diy/xinlang/mescroll-uni-option.js
  34. 430
      components/mescroll-diy/xinlang/mescroll-uni.vue
  35. 55
      components/mescroll-uni/components/mescroll-down.css
  36. 47
      components/mescroll-uni/components/mescroll-down.vue
  37. 90
      components/mescroll-uni/components/mescroll-empty.vue
  38. 83
      components/mescroll-uni/components/mescroll-top.vue
  39. 47
      components/mescroll-uni/components/mescroll-up.css
  40. 39
      components/mescroll-uni/components/mescroll-up.vue
  41. 19
      components/mescroll-uni/mescroll-body.css
  42. 348
      components/mescroll-uni/mescroll-body.vue
  43. 65
      components/mescroll-uni/mescroll-mixins.js
  44. 36
      components/mescroll-uni/mescroll-uni-option.js
  45. 36
      components/mescroll-uni/mescroll-uni.css
  46. 799
      components/mescroll-uni/mescroll-uni.js
  47. 424
      components/mescroll-uni/mescroll-uni.vue
  48. 48
      components/mescroll-uni/mixins/mescroll-comp.js
  49. 59
      components/mescroll-uni/mixins/mescroll-more-item.js
  50. 74
      components/mescroll-uni/mixins/mescroll-more.js
  51. 109
      components/mescroll-uni/wxs/mixins.js
  52. 92
      components/mescroll-uni/wxs/renderjs.js
  53. 268
      components/mescroll-uni/wxs/wxs.wxs
  54. 310
      components/mix-tree/mix-tree.vue
  55. 324
      components/multiple-select/multiple-select.vue
  56. 824
      components/mx-datepicker/mx-datepicker.vue
  57. 216
      components/pretty-uploadFile/pretty-uploadFile.css
  58. 237
      components/pretty-uploadFile/pretty-uploadFile.vue
  59. 133
      components/ss-upload-image/ss-upload-image.vue
  60. 134
      components/timeline/timeline.vue
  61. 161
      components/touch-list/touch-list.vue
  62. 202
      components/uni-combox/uni-combox.vue
  63. 316
      components/uni-data-checkbox/clientdb.js
  64. 847
      components/uni-data-checkbox/uni-data-checkbox.vue
  65. 364
      components/uni-datetime-picker/uni-datetime-picker.vue
  66. 56
      components/uni-easyinput/common.js
  67. 402
      components/uni-easyinput/uni-easyinput.vue
  68. 440
      components/uni-forms-item/uni-forms-item.vue
  69. 420
      components/uni-forms/uni-forms.vue
  70. 442
      components/uni-forms/validate.js
  71. 127
      components/uni-group/uni-group.vue
  72. 421
      components/uni-icon/uni-icon.vue
  73. 132
      components/uni-icons/icons.js
  74. 67
      components/uni-icons/uni-icons.vue
  75. BIN
      components/uni-icons/uni.ttf
  76. 210
      components/uni-pagination/uni-pagination.vue
  77. 324
      components/uni-picker/uni-picker.vue
  78. 38
      components/utils/openpdf.js
  79. 101
      components/utils/request.js
  80. 167
      components/utils/uploadimage.js
  81. 347
      guaranteePages/pages/Information/Information.vue
  82. 1188
      guaranteePages/pages/InformationSee/InformationSee.vue
  83. 1557
      guaranteePages/pages/addApplication/addApplication.vue
  84. 361
      guaranteePages/pages/application/application.vue
  85. 341
      guaranteePages/pages/asset/asset.vue
  86. 1267
      guaranteePages/pages/assetSee/assetSee.vue
  87. 289
      guaranteePages/pages/assignAB/assignAB.vue
  88. 251
      guaranteePages/pages/assignCommissioner/assignCommissioner.vue
  89. 237
      guaranteePages/pages/assignInformation/assignInformation.vue
  90. 236
      guaranteePages/pages/assignRegulation/assignRegulation.vue
  91. 342
      guaranteePages/pages/investigation/investigation.vue
  92. 1329
      guaranteePages/pages/investigationSee/investigationSee.vue
  93. 228
      guaranteePages/pages/letter/letter.vue
  94. 199
      guaranteePages/pages/letterSee/letterSee.vue
  95. 223
      guaranteePages/pages/notice/notice.vue
  96. 184
      guaranteePages/pages/noticeSee/noticeSee.vue
  97. 231
      guaranteePages/pages/payment/payment.vue
  98. 257
      guaranteePages/pages/paymentSee/paymentSee.vue
  99. 338
      guaranteePages/pages/regulation/regulation.vue
  100. 1201
      guaranteePages/pages/regulationSee/regulationSee.vue
  101. Some files were not shown because too many files have changed in this diff Show More

@ -0,0 +1,11 @@
{ // launch.json configurations app-plus/h5/mp-weixin/mp-baidu/mp-alipay/mp-qq/mp-toutiao/mp-360/
// launchtypelocalremote, localremote
"version": "0.0",
"configurations": [{
"type": "uniCloud",
"default": {
"launchtype": "remote"
}
}
]
}

@ -0,0 +1,21 @@
<script>
export default {
onLaunch: function() {
console.log('App Launch')
},
onShow: function() {
// console.log('App Show')
},
onHide: function() {
// console.log('App Hide')
}
}
</script>
<style>
@import "colorui/main.css";
@import "colorui/icon.css";
/*每个页面公共css */
@import "static/css/main.css";
</style>

@ -0,0 +1,184 @@
/*
Animation 微动画
基于ColorUI组建库的动画模块 by 文晓港 2019年3月26日19:52:28
*/
/* css 滤镜 控制黑白底色gif的 */
.gif-black{
mix-blend-mode: screen;
}
.gif-white{
mix-blend-mode: multiply;
}
/* Animation css */
[class*=animation-] {
animation-duration: .5s;
animation-timing-function: ease-out;
animation-fill-mode: both
}
.animation-fade {
animation-name: fade;
animation-duration: .8s;
animation-timing-function: linear
}
.animation-scale-up {
animation-name: scale-up
}
.animation-scale-down {
animation-name: scale-down
}
.animation-slide-top {
animation-name: slide-top
}
.animation-slide-bottom {
animation-name: slide-bottom
}
.animation-slide-left {
animation-name: slide-left
}
.animation-slide-right {
animation-name: slide-right
}
.animation-shake {
animation-name: shake
}
.animation-reverse {
animation-direction: reverse
}
@keyframes fade {
0% {
opacity: 0
}
100% {
opacity: 1
}
}
@keyframes scale-up {
0% {
opacity: 0;
transform: scale(.2)
}
100% {
opacity: 1;
transform: scale(1)
}
}
@keyframes scale-down {
0% {
opacity: 0;
transform: scale(1.8)
}
100% {
opacity: 1;
transform: scale(1)
}
}
@keyframes slide-top {
0% {
opacity: 0;
transform: translateY(-100%)
}
100% {
opacity: 1;
transform: translateY(0)
}
}
@keyframes slide-bottom {
0% {
opacity: 0;
transform: translateY(100%)
}
100% {
opacity: 1;
transform: translateY(0)
}
}
@keyframes shake {
0%,
100% {
transform: translateX(0)
}
10% {
transform: translateX(-9px)
}
20% {
transform: translateX(8px)
}
30% {
transform: translateX(-7px)
}
40% {
transform: translateX(6px)
}
50% {
transform: translateX(-5px)
}
60% {
transform: translateX(4px)
}
70% {
transform: translateX(-3px)
}
80% {
transform: translateX(2px)
}
90% {
transform: translateX(-1px)
}
}
@keyframes slide-left {
0% {
opacity: 0;
transform: translateX(-100%)
}
100% {
opacity: 1;
transform: translateX(0)
}
}
@keyframes slide-right {
0% {
opacity: 0;
transform: translateX(100%)
}
100% {
opacity: 1;
transform: translateX(0)
}
}

@ -0,0 +1,69 @@
<template>
<view>
<view class="cu-custom" :style="[{height:CustomBar + 'px'}]">
<view class="cu-bar fixed" :style="style" :class="[bgImage!=''?'none-bg text-white bg-img':'',bgColor]">
<view class="action" @tap="BackPage" v-if="isBack">
<text class="cuIcon-back"></text>
<slot name="backText"></slot>
</view>
<view class="content" :style="[{top:StatusBar + 'px'}]">
<slot name="content"></slot>
</view>
<slot name="right"></slot>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
StatusBar: this.StatusBar,
CustomBar: this.CustomBar
};
},
name: 'cu-custom',
computed: {
style() {
var StatusBar= this.StatusBar;
var CustomBar= this.CustomBar;
var bgImage = this.bgImage;
var style = `height:${CustomBar}px;padding-top:${StatusBar}px;`;
if (this.bgImage) {
style = `${style}background-image:url(${bgImage});`;
}
return style
}
},
props: {
bgColor: {
type: String,
default: ''
},
isBack: {
type: [Boolean, String],
default: false
},
bgImage: {
type: String,
default: ''
},
},
methods: {
BackPage() {
if (getCurrentPages().length < 2 && 'undefined' !== typeof __wxConfig) {
let url = '/' + __wxConfig.pages[0]
return uni.redirectTo({url})
}
uni.navigateBack({
delta: 1
});
}
}
}
</script>
<style>
</style>

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

@ -0,0 +1,354 @@
<template>
<view class="uni-combox">
<view v-if="label" class="uni-combox__label" :style="labelStyle">
<text>{{label}}</text>
</view>
<view class="uni-combox__input-box">
<input class="uni-combox__input" type="text" :placeholder="placeholder" :disabled="isDisabled" v-model="inputVal"
@input="onInput" @focus="onFocus" @blur="onBlur" />
<icon type="clear" size="16" @tap="clearInputValue" v-if="!isDisabled" />
<image class="uni-combox__input-arrow" src="../../static/cuihai-combox/arrow_down.png" style="width: 30rpx;height: 30rpx;" @click="toggleSelector"></image>
<!-- <uni-icons class="uni-combox__input-arrow" type="arrowdown" size="14" @click="toggleSelector"></uni-icons> -->
<view class="uni-combox__selector" v-if="showSelector">
<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
<text>{{emptyTips}}</text>
</view>
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index" @click="onSelectorClick(index)"
:style="isCheck(index)?'color:'+selectColor+';background-color:'+selectBgColor+';':'color:'+color+';'">
<text>{{isJSON?item[keyName]:item}}</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
/**
* Combox 组合输入框
* @description 组合输入框一般用于既可以输入也可以选择的场景
* @property {String} label 左侧文字
* @property {String} labelWidth 左侧内容宽度
* @property {String} placeholder 输入框占位符
* @property {Array} candidates 候选项列表
* @property {String} emptyTips 筛选结果为空时显示的文字
* @property {String} value 单选时组合框的初始值
* @property {Array} initValue 多选时组合框的初始值(下标集合)
* @property {String} keyName json数组显示的字段值
* @property {Boolean} isJSON 是否是json数组默认不是
* @property {Boolean} isDisabled 是否是禁用输入默认不禁用
* @property {Boolean} isCheckBox 是否是多选默认不是多选时不能输入查询
* @property {String} color 默认字体颜色默认#000000
* @property {String} selectColor 选中字体颜色默认#0081ff
* @property {String} selectBgColor 选中背景颜色默认#e8e8e8
*/
export default {
name: 'comboxSearch',
props: {
label: {
type: String,
default: ''
},
labelWidth: {
type: String,
default: 'auto'
},
placeholder: {
type: String,
default: ''
},
candidates: {
type: Array,
default () {
return []
}
},
emptyTips: {
type: String,
default: '无匹配项'
},
value: {
type: String,
default: ''
},
initValue: {
type: Array,
default: null
},
keyName: {
type: String,
default: ''
},
isJSON: {
type: Boolean,
default: false
},
isDisabled: {
type: Boolean,
default: false
},
isCheckBox: {
type: Boolean,
default: false
},
color: {
default: "#000000",
type: String
},
selectColor: {
default: "#0081ff",
type: String
},
selectBgColor: {
default: "#e8e8e8",
type: String
}
},
data() {
return {
showSelector: false,
inputVal: '',
arrays: [],
gid: `sm-org-dropDown-${(new Date()).getTime()}${Math.random()}`
}
},
computed: {
labelStyle() {
if (this.labelWidth === 'auto') {
return {}
}
return {
width: this.labelWidth
}
},
filterCandidates() {
if (!this.isDisabled) {
if (this.isJSON) {
let index = 0;
return this.candidates.filter((item) => {
item.index = index;
index++;
return item[this.keyName].indexOf(this.inputVal) > -1
})
} else {
return this.candidates.filter((item) => {
return item.indexOf(this.inputVal) > -1
})
}
} else {
if (this.isJSON) {
let index = 0;
return this.candidates.filter((item) => {
item.index = index;
index++;
return item[this.keyName] != undefined;
})
} else {
return this.candidates
}
}
},
filterCandidatesLength() {
return this.filterCandidates.length
}
},
created() {
if (this.initValue != null) {
this.arrays = this.initValue;
this.inputVal = this.getInpuevals();
}
//created
//uni.$onsm-org-dropDown-show广
uni.$on('sm-org-dropDown-show', (targetId) => {
//广gid
if (this.showSelector && this.gid != targetId) {
this.showSelector = false
}
})
},
//
beforeDestroy() {
uni.$off('sm-org-dropDown-show')
},
watch: {
value: {
handler(newVal) {
this.inputVal = newVal
},
immediate: true
}
},
methods: {
toggleSelector() {
this.showSelector = !this.showSelector
if (this.showSelector) {
uni.$emit('sm-org-dropDown-show', this.gid)
}
},
onFocus() {
this.showSelector = true;
uni.$emit('sm-org-dropDown-show', this.gid)
},
onBlur() {
/* setTimeout(() => {
this.showSelector = false;
}, 50) */
},
onSelectorClick(index) {
if (this.isCheckBox) {
let flag = this.arrays.indexOf(index);
if (flag != -1) {
let x = (this.arrays || []).findIndex((item) => item === index)
this.arrays.splice(x, 1);
} else {
this.arrays.push(index);
}
this.inputVal = this.getInpuevals();
if (this.isJSON) {
this.$emit('getValue', this.arrays);
} else {
this.$emit('getValue', this.trimSpace(this.inputVal.split(" ")));
}
} else {
this.showSelector = false
if (this.isJSON) {
this.$emit('getValue', this.filterCandidates[index].index);
this.inputVal = this.filterCandidates[index][this.keyName];
} else {
this.$emit('getValue', this.filterCandidates[index]);
this.inputVal = this.filterCandidates[index]
}
}
},
trimSpace(array) {
for (var i = 0; i < array.length; i++) {
if (array[i] == "") {
array.splice(i, 1);
i = i - 1;
}
}
return array;
},
onInput() {
setTimeout(() => {
this.$emit('input', this.inputVal)
})
},
clearInputValue() {
this.inputVal = "";
this.showSelector = false;
},
getInpuevals() {
if (this.arrays.length == 0) {
this.inputVal = "";
} else {
this.arrays.sort(function(a, b) {
return a - b
})
this.inputVal = "";
if (this.isJSON) {
this.arrays.forEach(v => {
this.inputVal += this.candidates[v][this.keyName] + " ";
});
} else {
this.arrays.forEach(v => {
this.inputVal += this.candidates[v] + " ";
});
}
}
return this.inputVal;
},
isCheck(index) {
return this.arrays.indexOf(index) != -1;
}
}
}
</script>
<style scoped>
.uni-combox {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
height: 40px;
flex-direction: row;
align-items: center;
/* border-bottom: solid 1px #DDDDDD; */
}
.uni-combox__label {
font-size: 16px;
line-height: 22px;
padding-right: 10px;
color: #999999;
}
.uni-combox__input-box {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
}
.uni-combox__input {
flex: 1;
font-size: 16px;
height: 22px;
line-height: 22px;
}
.uni-combox__input-arrow {
padding: 10px;
}
.uni-combox__selector {
box-sizing: border-box;
position: absolute;
top: 42px;
left: 0;
width: 100%;
background-color: #FFFFFF;
border-radius: 6px;
box-shadow: #DDDDDD 4px 4px 8px, #DDDDDD -4px -4px 8px;
z-index: 2;
}
.uni-combox__selector-scroll {
max-height: 200px;
box-sizing: border-box;
}
.uni-combox__selector::before {
content: '';
position: absolute;
width: 0;
height: 0;
border-bottom: solid 6px #FFFFFF;
border-right: solid 6px transparent;
border-left: solid 6px transparent;
left: 50%;
top: -6px;
margin-left: -6px;
}
.uni-combox__selector-empty,
.uni-combox__selector-item {
/* #ifdef APP-NVUE */
display: flex;
/* #endif */
line-height: 36px;
font-size: 14px;
text-align: center;
border-bottom: solid 1px #DDDDDD;
/* margin: 0px 10px; */
}
.uni-combox__selector-empty:last-child,
.uni-combox__selector-item:last-child {
border-bottom: none;
}
</style>

@ -0,0 +1,87 @@
<!-- 商品列表组件 <good-list :list="xx"></good-list> -->
<template>
<view class="good-list">
<view class="charge" v-for="(item,index) in list" :key="index" :id="'index-' + item.name" :data-index="item.name">
<view class="charge-title flex-between">
<text>{{item.name}}</text>
<text class="charge-status">{{item.status}}</text>
</view>
<view class="charge-text">
<text>{{item.IDNumber}}</text>
</view>
</view>
</view>
</template>
<script>
export default {
props:{
list: {
type: Array,
default(){
return []
}
}
}
}
</script>
<style lang="scss" scoped>
.good-list{
background-color: #fff;
.good-li{
display: flex;
align-items: center;
padding: 20upx;
border-bottom: 1upx solid #eee;
.good-img{
width: 160upx;
height: 160upx;
margin-right: 20rpx;
}
.flex-item{
flex: 1;
.good-name{
font-size: 26upx;
line-height: 40upx;
height: 80upx;
margin-bottom: 20upx;
overflow: hidden;
}
.good-price{
font-size: 26upx;
color: red;
}
.good-sold{
font-size: 24upx;
margin-left: 16upx;
color: gray;
}
}
}
}
.charge{
margin: 0 50rpx;
border-radius: 20rpx;
padding: 0 30rpx;
box-shadow: 0 0 16rpx #ccc;
margin-top: 40rpx;
.charge-title{
padding: 16rpx 0;
border-bottom: 4rpx solid #F2F2F2;
color: #000;
.charge-status{
color: #ccc;
}
}
.charge-text{
padding: 16rpx 0;
color: #707070;
}
}
</style>

@ -0,0 +1,166 @@
<template>
<view>
<slot name="formItem" v-if="$slots.formItem"></slot>
<view v-else class="evan-form-item-container" :class="'evan-form-item-container--'+mLabelPosition" :style="{borderWidth:border?'1rpx':0}">
<view v-if="label" class="evan-form-item-container__label" :class="{showAsteriskRect:hasRequiredAsterisk,isRequired:showRequiredAsterisk}"
:style="mLabelStyle">{{label}}</view>
<view class="evan-form-item-container__main" :style="mContentStyle">
<slot></slot>
</view>
</view>
</view>
</template>
<script>
export default {
name: 'EvanFormItem',
props: {
labelStyle: Object,
label: String,
contentStyle: {
type: Object,
default: () => {
return {}
}
},
prop: String,
border: {
type: Boolean,
default: true
},
labelPosition: {
validator: function(value) {
if (!value) {
return true
}
return ['top', 'left'].indexOf(value) !== -1
},
default: ''
},
required: {
type: Boolean,
default: false
},
message: {
type: String,
default: ''
},
rules: {
type: [Object, Array],
default: null
}
},
inject: ['evanForm'],
computed: {
mLabelStyle() {
const parent = this.getParent()
let labelStyle = Object.assign({}, (parent.labelStyle || {}), (this.labelStyle || {}))
let arr = Object.keys(labelStyle).map((key) => `${key}:${labelStyle[key]}`)
return arr.join(';')
},
mContentStyle() {
let contentStyle = Object.assign({}, this.contentStyle || {})
let arr = Object.keys(contentStyle).map((key) => `${key}:${contentStyle[key]}`)
return arr.join(';')
},
mLabelPosition() {
if (this.labelPosition) {
return this.labelPosition
}
const parent = this.getParent()
if (parent) {
return parent.labelPosition
}
return 'left'
},
// *
hasRequiredAsterisk() {
const parent = this.getParent()
if (parent) {
return parent.hasRequiredAsterisk
}
return false
},
// formItem*
showRequiredAsterisk() {
const parent = this.getParent()
if (parent && parent.hideRequiredAsterisk) {
return false
}
const rules = this.getRules()
if (rules && rules.length > 0) {
if (rules.find((rule) => rule.required === true)) {
return true
}
}
return this.required
}
},
methods: {
// EvanForm
getParent() {
return this.evanForm
},
getRules() {
let form = this.getParent()
const formRules = form.mRules && form.mRules[this.prop] ? form.mRules[this.prop] : [];
const selfRules = this.rules
const requiredRules = this.required ? {
required: true,
message: this.message || `${this.label}必填`
} : []
return [].concat(selfRules || formRules || []).concat(requiredRules)
}
},
created() {
this.evanForm.$emit('evan.form.addField', this)
},
beforeDestroy() {
this.evanForm.$emit('evan.form.removeField', this)
}
}
</script>
<style lang="scss">
.evan-form-item-container {
&__label {
font-size: 28rpx;
color: #666;
line-height: 40rpx;
padding: 25rpx 0;
display: inline-block;
&.showAsteriskRect::before {
content: '';
color: #F56C6C;
width: 20rpx;
display: inline-block;
}
&.isRequired::before {
content: '*';
}
}
&__main {
flex: 1;
min-height: 90rpx;
display: flex;
align-items: center;
overflow: hidden;
}
&--left {
display: flex;
flex-direction: row;
align-items: flex-start;
}
&--top {
.evan-form-item-container__label {
padding-bottom: 10rpx;
}
}
}
</style>

@ -0,0 +1,168 @@
<template>
<view class="evan-form-container">
<slot></slot>
</view>
</template>
<script>
import utils from './utils.js'
export default {
name: 'EvanForm',
props: {
labelStyle: {
type: Object,
default: () => {
return {}
}
},
model: Object,
hideRequiredAsterisk: {
type: Boolean,
default: false
},
showMessage: {
type: Boolean,
default: true
},
labelPosition: {
validator: function(value) {
return ['top', 'left'].indexOf(value) !== -1
},
default: 'left'
},
rules: {
type: Object,
default: () => {
return {}
}
}
},
provide() {
return {
evanForm: this
}
},
computed: {
// form*label*
hasRequiredAsterisk() {
if (this.hideRequiredAsterisk) {
return false
}
if (this.mRules) {
const values = Object.values(this.mRules)
if (values && values.length > 0) {
for (let i = 0; i < values.length; i++) {
const value = values[i]
if (Array.isArray(value) && value.length > 0) {
const requiredItem = value.find((v) => v.required === true)
if (requiredItem) {
return true
}
} else {
if (value && value.required) {
return true
}
}
}
}
}
return this.childHasRequired
}
},
watch: {
rules: {
immediate: true,
deep: true,
handler(value) {
this.mRules = value || {}
}
}
},
data() {
return {
mRules: {},
fields: [],
childHasRequired: false
}
},
methods: {
setRules(rules) {
this.mRules = rules || {}
},
async validate(callback) {
const rules = this.getRules()
if (typeof callback === 'function') {
utils.validate(this.model, rules, callback, {
showMessage: this.showMessage
})
} else {
return await utils.validate(this.model, rules, callback, {
showMessage: this.showMessage
})
}
},
async validateField(props, callback) {
const rules = this.getRules()
if (typeof callback === 'function') {
utils.validateField(this.model, rules, props, callback, {
showMessage: this.showMessage
})
} else {
return await utils.validateField(this.model, rules, props, callback, {
showMessage: this.showMessage
})
}
},
getRules() {
const rules = {}
this.fields.forEach((field) => {
if (field.prop) {
const requiredRules = field.required ? {
required: true,
message: field.message || `${field.label}必填`
} : []
const formRules = this.mRules && this.mRules[field.prop] ? this.mRules[field.prop] : []
rules[field.prop] = [].concat(field.rules || formRules || []).concat(requiredRules)
}
})
return rules
}
},
created() {
this.$on('evan.form.addField', (field) => {
// push field
if (field.prop) {
this.fields.push({
rules: field.rules,
prop: field.prop,
required: field.required,
label: field.label,
message: field.message,
_uid: field._uid
})
if (!this.childHasRequired) {
if (field.required) {
this.childHasRequired = field.required
return
}
if (field.rules) {
const fieldRules = [].concat(field.rules)
fieldRules.forEach((item) => {
if (item.required) {
this.childHasRequired = true
}
})
}
}
}
})
this.$on('evan.form.removeField', (field) => {
this.fields.splice(this.fields.findIndex((item) => item._uid === field._uid), 1)
})
}
}
</script>
<style lang="scss">
.evan-form-container {}
</style>

@ -0,0 +1,148 @@
import AsyncValidator from 'async-validator'
const utils = {
validate: (model, rules, callback, options) => {
const initOptions = {
showMessage: true
}
options = Object.assign({}, initOptions, options || {})
let promise = null;
if (typeof callback !== 'function') {
promise = new Promise((resolve, reject) => {
callback = function(valid) {
valid ? resolve(valid) : reject(valid)
}
})
}
// 如果需要验证的fields为空,调用验证时立刻返回callback
if (!rules || (Array.isArray(rules) && rules.length === 0) || (typeof rules === 'object' && Object.keys(rules).length ===
0)) {
callback(true, null);
if(promise){
return promise
}
return
}
let errors = []
const props = Object.keys(rules)
let count = 0
for (let i in props) {
const prop = props[i]
const value = utils.getValueByProp(model, prop)
utils.validateItem(rules, prop, value, (err) => {
if (err && err.length > 0) {
errors = errors.concat(err)
}
// 处理异步校验,等所有校验都结束时再callback
count++
if (count === props.length) {
if (errors.length > 0) {
if (options.showMessage) {
utils.showToast(errors[0].message)
}
callback(false, errors)
} else {
callback(true, null)
}
}
})
}
if (promise) {
return promise
}
},
validateField: (model, rules, props, callback, options) => {
const initOptions = {
showMessage: true
}
options = Object.assign({}, initOptions, options || {})
let promise = null;
if (typeof callback !== 'function') {
promise = new Promise((resolve, reject) => {
callback = function(valid) {
valid ? resolve(valid) : reject(valid)
}
})
}
props = [].concat(props)
if (props.length === 0) {
return
}
let errors = []
let count = 0
for (let i in props) {
const prop = props[i]
const value = utils.getValueByProp(model, prop)
utils.validateItem(rules, prop, value, (err) => {
if (err && err.length > 0) {
errors = errors.concat(err)
}
// 处理异步校验,等所有校验都结束时再callback
count++
if (count === props.length) {
if (errors.length > 0) {
if (options.showMessage) {
utils.showToast(errors[0].message)
}
callback(false, errors)
} else {
callback(true, null)
}
}
})
}
if (promise) {
return promise
}
},
validateItem(rules, prop, value, callback) {
if (!rules || JSON.stringify(rules) === '{}') {
if (callback instanceof Function) {
callback();
}
return true;
}
const propRules = [].concat(rules[prop] || []);
propRules.forEach((rule) => {
if (rule.pattern) {
rule.pattern = new RegExp(rule.pattern)
}
})
const descriptor = {
[prop]: propRules
};
const validator = new AsyncValidator(descriptor);
const model = {
[prop]: value
};
validator.validate(model, {
firstFields: true
}, (errors) => {
callback(errors);
});
},
getValueByProp: (obj, prop) => {
let tempObj = obj;
prop = prop.replace(/\[(\w+)\]/g, '.$1').replace(/^\./, '');
let keyArr = prop.split('.');
let i = 0;
for (let len = keyArr.length; i < len - 1; ++i) {
if (!tempObj) break;
let key = keyArr[i];
if (key in tempObj) {
tempObj = tempObj[key];
} else {
break;
}
}
return tempObj ? (typeof tempObj[keyArr[i]] === 'string' ? tempObj[keyArr[i]].trim() : tempObj[keyArr[i]]) :
null
},
showToast: (message) => {
uni.showToast({
title: message,
icon: 'none'
})
}
}
export default utils

@ -0,0 +1,110 @@
<!-- 商品列表组件 <good-list :list="xx"></good-list> -->
<template>
<view class="good-list">
<view class="charge" v-for="(item,index) in list" :key="index" :id="'index-' + item.name" :data-index="item.name">
<view class="charge-title flex-between">
<text>{{item.name}}的贷款申请</text>
</view>
<view class="charge-text">
<view>
<text>业务编号</text>
<text class="mgl30">{{item.IDNumber}}</text>
</view>
<view>
<text>所在部门</text>
<text class="mgl30">{{item.department}}</text>
</view>
<view>
<text>客户名称</text>
<text class="mgl30">{{item.cusName}}</text>
</view>
<view class="flex-between">
<text class="time-text">{{item.time}}</text>
<text class="status-text" :style="'background-color:'+item.status.bgColor+';color:'+item.status.textColor">{{item.status.text}}</text>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
props:{
list: {
type: Array,
default(){
return []
}
}
}
}
</script>
<style lang="scss" scoped>
.good-list{
background-color: #fff;
.good-li{
display: flex;
align-items: center;
padding: 20upx;
border-bottom: 1upx solid #eee;
.good-img{
width: 160upx;
height: 160upx;
margin-right: 20rpx;
}
.flex-item{
flex: 1;
.good-name{
font-size: 26upx;
line-height: 40upx;
height: 80upx;
margin-bottom: 20upx;
overflow: hidden;
}
.good-price{
font-size: 26upx;
color: red;
}
.good-sold{
font-size: 24upx;
margin-left: 16upx;
color: gray;
}
}
}
}
.charge{
margin: 0 50rpx;
border-radius: 20rpx;
padding: 0 30rpx;
box-shadow: 0 0 16rpx #ccc;
margin-top: 40rpx;
.charge-title{
padding: 16rpx 0;
border-bottom: 4rpx solid #F2F2F2;
color: #000;
font-weight: bold;
.charge-status{
color: #ccc;
}
}
.charge-text{
padding: 16rpx 0;
color: #707070;
.time-text{
color: #ccc;
}
.status-text{
font-size: 28rpx;
padding: 8rpx 12rpx;
border-radius: 10rpx;
}
}
}
</style>

@ -0,0 +1,450 @@
<template>
<!-- 展示关联人组件 -->
<view>
<!-- 仅处于企业和是否有关联人的状态下展示由父级空值隐藏 -->
<view>
<block v-for="(item,index) in list" :key="index">
<view class="bottom-border mab15">
<view class="left-border"><text class="mgl10">关联人{{index+1}}</text></view>
</view>
<uni-forms style="padding-bottom: 0;">
<uni-forms-item style="padding-bottom: 0;" required name="type" label="关联人类型">
<uni-data-checkbox v-model="item.type" :localdata="linkTypeList"></uni-data-checkbox>
</uni-forms-item>
</uni-forms>
<!-- 个人关联人 -->
<uni-forms class="from-position" v-if="item.type == 0" :value="item" ref="manItem" validate-trigger="bind" err-show-type="toast">
<uni-forms-item name="relatedName" required label="关联人名称">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.relatedName" placeholder="请输入关联人名称"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="phone" required label="联系电话">
<uni-easyinput :disabled="handle" type="number" maxlength="11" :inputBorder="true" v-model="item.phone" placeholder="请输入联系电话"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="maritalStatus" required label="婚姻状况">
<picker :disabled="handle" :class="handle=='watch'||handle=='audit'?'disabled':''" @change="relatedMaritalChange($event,item)" :value="item.maritalStatusVal" :range="maritalList" :range-key="'name'">
<view :style="handle?'background-color:#eee;':''" class="picker-view flex-between">
<text >{{item.maritalStatusVal!==""? maritalList[item.maritalStatusVal].name : '请选择'}}</text>
<text class="cuIcon-unfold lg text-gray"></text>
</view>
</picker>
</uni-forms-item>
<uni-forms-item name="idCard" required label="身份证号码">
<uni-easyinput :disabled="handle" type="idcard" :inputBorder="true" v-model="item.idCard" placeholder="请输入身份证号码"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="age" required label="年龄">
<uni-easyinput :disabled="handle" type="number" maxlength="2" :inputBorder="true" v-model="item.age" placeholder="请输入年龄"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="gender" required label="性别">
<uni-data-checkbox :disabled="handle" v-model="item.gender" :localdata="genders"></uni-data-checkbox>
</uni-forms-item>
<uni-forms-item name="address" required label="联系地址">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.address" placeholder="请输入联系地址"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="relatedEducation(index)" required label="学历">
<picker :disabled="handle" :class="handle=='watch'||handle=='audit'?'disabled':''" @change="educationChange($event,index)" :value="item.educationVal" :range="educationList" :range-key="'name'">
<view :style="handle?'background-color:#eee;':''" class="picker-view flex-between">
<text>{{item.educationVal!=='' ? educationList[item.educationVal].name : '请选择'}}</text>
<text class="cuIcon-unfold lg text-gray"></text>
</view>
</picker>
</uni-forms-item>
<uni-forms-item name="employer" required label="工作单位">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.employer" placeholder="请输入工作单位"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="position" required label="职务">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.position" placeholder="请输入职务"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="relationship" required label="关联关系">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.relationship" placeholder="请输入关联关系"></uni-easyinput>
</uni-forms-item>
</uni-forms>
<!-- 企业关联人 enterprise-->
<uni-forms class="from-position" v-if="item.type == 1" :value="item" ref="enterprise" validate-trigger="bind" err-show-type="toast">
<uni-forms-item name="relatedName" required label="关联人名称">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.relatedName" placeholder="请输入关联人名称"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="address" required label="联系住址">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.address" placeholder="请输入联系住址"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="socialUnifiedCode" required label="社会统一代码">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.socialUnifiedCode" placeholder="请输入社会统一代码"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="registeredCapital" required label="注册资金(万元)">
<uni-easyinput :disabled="handle" type="digit" :inputBorder="true" v-model="item.registeredCapital" placeholder="请输入注册资金"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="registeredTime" required label="注册时间">
<picker :disabled="handle" :class="handle?'disabled':''" mode="date" :value="item.registeredTime" @change="relatedtimeChange($event,item)">
<view class="picker-view flex-between">
<text>{{item.registeredTime ? item.registeredTime : '请选择'}}</text>
<text class="cuIcon-unfold lg text-gray"></text>
</view>
</picker>
</uni-forms-item>
<uni-forms-item name="industry" required label="所属行业">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.industry" placeholder="请输入所属行业"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="legalPersonName" required label="法人姓名">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.legalPersonName" placeholder="请输入法人姓名"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="phone" required label="联系电话">
<uni-easyinput :disabled="handle" type="number" maxlength="11" :inputBorder="true" v-model="item.phone" placeholder="请输入联系电话"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="relationship" required label="关联关系">
<uni-easyinput :disabled="handle" type="text" :inputBorder="true" v-model="item.relationship" placeholder="请输入关联关系"></uni-easyinput>
</uni-forms-item>
<uni-forms-item name="shareholdersSituation" required label="股东情况">
<uni-easyinput :disabled="handle" type="textarea" :inputBorder="true" v-model="item.shareholdersSituation" placeholder="请输入关联关系"></uni-easyinput>
</uni-forms-item>
</uni-forms>
<!-- 企业和个人共用可进行保存和删除 -->
<view class="foot-btn btn-rig pad-bt">
<button v-if="!handle" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="submitForm(item,index)">保存</button>
<button v-if="!handle" class="mini-btn round" type="warn" size="mini" @click="deleteContact(index,item)">删除</button>
</view>
</block>
</view>
<!-- 末尾的新增关联人点击生成一个新的关联人表单无删除按钮有新增按钮新增完毕重新请求关联人列表 -->
<!-- 且清除已有的值 -->
<!-- 仅企业+ 关联人展示 -->
<button v-if="!handle" class="cu-btn block bg-blue margin-tb-sm lg round" @click="addContact">新增关联人</button>
</view>
</template>
<script>
export default {
name:"linkMan",
props:{
// ,/false
handle:{
type: Boolean,
default: false
},
//
companyId:{},
},
data() {
return {
//
linkTypeList: [
{
text: '个人',
value: '0',
// disabled: true
},
{
text: '企业',
value: '1',
// disabled: false
},
],
//-
genders: [
{
text: '男',
value: 1,
},
{
text: '女',
value: 0,
},
],
//
maritalList: [
{
name: '未婚',
value: 0
},
{
name: '已婚',
value: 1
},
{
name: '离异',
value: 2
},
{
name: '再婚',
value: 3
},
],
//
educationList: [
{
name: '本科',
value: 0
},
{
name: '大专',
value: 1
},
{
name: '高职',
value: 2
},
{
name: '中专',
value: 3
},
{
name: '其他',
value: 4
},
],
//
//
list: [
{ //
type: '0',
relatedName: '',//
relationship: '',//
phone: '',//
address: '',//
//
maritalStatusVal: '',
maritalStatus: '',//
idCard: '',//
age: '',//
gender: 1,//
educationVal: '',
education: '',//
employer: '',//
position: '',//
//
socialUnifiedCode: '',//
registeredCapital: '',//
registeredTime: '',//
industry: '',//
legalPersonName: '',//
shareholdersSituation: ''//
}
]
};
},
created() {
console.log(this.companyId)
if(this.companyId) this.getLinkmanList()
console.log(this.handle,'当前操作状态')
if(this.handle){
this.linkTypeList.map(e=>{
e.disabled = true
})
this.genders.map(e=>{
e.disabled = true
})
this.maritalList.map(e=>{
e.disabled = true
})
this.educationList.map(e=>{
e.disabled = true
})
}
// this.companyId
},
watch:{
// trueCheckBox
handle:function(val){
console.log(val,'handlue-0-0linmk')
if(val){
this.linkTypeList.map(e=>{
e.disabled = true
})
this.genders.map(e=>{
e.disabled = true
})
this.maritalList.map(e=>{
e.disabled = true
})
this.educationList.map(e=>{
e.disabled = true
})
}
},
// id
companyId:function(val,old){
console.log(val,'当前的id值',old)
if(val!==''||val!==null||val!==undefined){
this.getLinkmanList()
}
}
},
onReady() {
},
mounted() {},
methods:{
//
relatedtimeChange(e,item) {
item.registeredTime = e.detail.value
console.log(item.registeredTime ,' 当前时间的值')
},
//
relatedMaritalChange(e,item) {
item.maritalStatusVal = e.detail.value
item.maritalStatus = this.maritalList[e.detail.value].value
},
//
legalGenderChange(e,index) {
this.list[index].legalGender = e.detail.value
},
//
educationChange(e,index) {
this.list[index].educationVal = e.detail.value
this.list[index].education = this.educationList[e.detail.value].value
},
//
submitForm(item,index) {
// 0
let obj // obj
if(!this.companyId) return uni.showToast({title: '请先选择客户!',icon:'none'})
//
if (!item.relatedName)return uni.showToast({title:'请输入关联人名称',icon:"none"})
if (!item.relationship)return uni.showToast({title:'请输入关联关系',icon:"none"})
if (!item.phone)return uni.showToast({title:'请输入联系电话',icon:"none"})
if (!item.address)return uni.showToast({title:'请输入联系地址',icon:"none"})
obj = {
type:item.type,
relatedName:item.relatedName,
relationship:item.relationship,
phone:item.phone,
address:item.address,
relatedCompanyId:this.companyId
}
//
if(item.type==0){
if (!item.maritalStatusVal)return uni.showToast({title:'请输入婚姻状况',icon:"none"})
if (!item.idCard)return uni.showToast({title:'请输入身份证号码',icon:"none"})
if (!item.age)return uni.showToast({title:'请输入年龄',icon:"none"})
if (item.gender!=0&&item.gender!=1)return uni.showToast({title:'请选择性别',icon:"none"})
if (!item.educationVal)return uni.showToast({title:'请选择学历',icon:"none"})
if (!item.employer)return uni.showToast({title:'请输入工作单位',icon:"none"})
if (!item.position)return uni.showToast({title:'请输入职务',icon:"none"})
obj.maritalStatus = item.maritalStatus
obj.idCard = item.idCard
obj.age = item.age
obj.gender = item.gender
obj.education = item.education
obj.employer = item.employer
obj.position = item.position
}else{
if (!item.socialUnifiedCode)return uni.showToast({title:'请输入社会统一代码',icon:"none"})
if (!item.registeredCapital)return uni.showToast({title:'请输入注册资金',icon:"none"})
if (!item.registeredTime)return uni.showToast({title:'请选择注册时间',icon:"none"})
if (!item.industry)return uni.showToast({title:'请输入所属行业',icon:"none"})
if (!item.legalPersonName)return uni.showToast({title:'请输入法人姓名',icon:"none"})
if (!item.shareholdersSituation)return uni.showToast({title:'请输入股东情况',icon:"none"})
obj.socialUnifiedCode = item.socialUnifiedCode
obj.registeredCapital = item.registeredCapital
obj.registeredTime = item.registeredTime
obj.industry = item.industry
obj.legalPersonName = item.legalPersonName
obj.shareholdersSituation = item.shareholdersSituation
}
// id--
if(item.id){
obj.id = item.id
//
this.$http.post('/api-crms/crms-company-personal/updateCompanyPersonal',obj).then(res=>{
uni.showToast({title: '修改成功'})
console.log('修改关联人的操作')
this.getLinkmanList()
})
}
else{ //
console.log(obj,'新增操作')
this.$http.post('/api-crms/crms-company-personal/insertCompanyPersonal',obj).then(res => {
uni.showToast({title: '添加成功'})
// item.id = res.data[0].id // ,
this.list.splice(list[this.list.length-1],1)//
console.log('新增关联人操作')
this.getLinkmanList()//
}).catch(function (error) {});
}
},
//
addContact(){
// console.log('')
// if(!this.businessApply.companyId) return uni.showToast({title: '',icon:'none'})
// console.log('---')
// if(!this.enterpriseForm.isExistRelated) return uni.showToast({title: ''})
//
let CONTACT_INFO = {
type: 0,
relatedName: '',
phone: '',
maritalStatusVal: '',
maritalStatus: '',
idCard: '',
age: '',
gender: '',
address: '',
educationVal: '',
education: '',
employer: '',
position: '',
relationship: '',
socialUnifiedCode: '',
registeredCapital: '',
registeredTime: '',
industry: '',
legalPersonName: '',
shareholdersSituation: ''
}
this.list.push({...CONTACT_INFO})
},
//
deleteContact(index,item){
let _this = this
uni.showModal({
title: '提示',
content: '是否删除该关联人',
success: function (res) {
if (res.confirm) {
if(item.id){
let arr = []
arr.push(item.id)
_this.$http.post('/api-crms/crms-company-personal/deleteCompanyPersonal',arr).then(res => {
_this.getLinkmanList()
}).catch(function (error) {});
}
_this.list.splice(index,1)
_this.total = _this.total-1
uni.showToast({title: '删除成功'})
} else if (res.cancel) {}
}
});
},
//
getLinkmanList(){
console.log('关联人列表接口',this.companyId)
if(!this.companyId) return uni.showToast({title: '请先选择客户!',icon:'none'})
this.$http.get("/api-crms/crms-company-personal/companyPersonalList",{
customerId:this.companyId,
page:1,
size:999 //
}).then(res=>{
console.log(res,'读取关联人list')
this.list = []
if(res.data.list&&res.data.list.length!==0){
this.list = res.data.list
this.list.map(e=>{
e.educationVal = +e.education
e.maritalStatusVal = +e.maritalStatus
})
console.log(this.list,'关联人的list')
}
})
},
},
destroyed() {}
}
</script>
<style lang="scss">
.from-position{
position: relative;
top: -30rpx;
}
</style>

@ -0,0 +1,200 @@
<template>
<text :class="classObj.wrapper" @click.stop="handleClick">
<text :class="[classObj.input, {'is-indeterminate': indeterminate, 'is-checked': checked, 'is-disabled': disabled}]">
<text :class="classObj.inner"></text>
</text>
</text>
</template>
<script>
export default {
data() {
return {
classObj: {}
}
},
props: {
type: {
type: String,
validator(t) {
return t === 'radio' || t === 'checkbox'
}
},
checked: Boolean,
disabled: Boolean,
indeterminate: Boolean
},
created() {
this.classObj = {
wrapper: `ly-${this.type}`,
input: `ly-${this.type}__input`,
inner: `ly-${this.type}__inner`
}
},
methods: {
handleClick() {
this.$emit('check', this.checked);
}
}
}
</script>
<style>
/* lyRadio/lyCheckbox-start */
.ly-checkbox,
.ly-radio {
color: #606266;
font-weight: 500;
font-size: 28rpx;
cursor: pointer;
user-select: none;
padding-right: 16rpx
}
.ly-checkbox__input,
.ly-radio__input {
cursor: pointer;
outline: 0;
line-height: 1;
vertical-align: middle
}
.ly-checkbox__input.is-disabled .ly-checkbox__inner,
.ly-radio__input.is-disabled .ly-radio__inner {
background-color: #edf2fc;
border-color: #DCDFE6;
cursor: not-allowed
}
.ly-checkbox__input.is-disabled .ly-checkbox__inner::after,
.ly-radio__input.is-disabled .ly-radio__inner::after {
cursor: not-allowed;
border-color: #C0C4CC
}
.ly-checkbox__input.is-disabled .ly-checkbox__inner+.ly-checkbox__label,
.ly-radio__input.is-disabled .ly-radio__inner+.ly-radio__label {
cursor: not-allowed
}
.ly-checkbox__input.is-disabled.is-checked .ly-checkbox__inner,
.ly-radio__input.is-disabled.is-checked .ly-radio__inner {
background-color: #F2F6FC;
border-color: #DCDFE6
}
.ly-checkbox__input.is-disabled.is-checked .ly-checkbox__inner::after,
.ly-radio__input.is-disabled.is-checked .ly-radio__inner::after {
border-color: #C0C4CC
}
.ly-checkbox__input.is-disabled.is-indeterminate .ly-checkbox__inner {
background-color: #F2F6FC;
border-color: #DCDFE6
}
.ly-checkbox__input.is-disabled.is-indeterminate .ly-checkbox__inner::before {
background-color: #C0C4CC;
border-color: #C0C4CC
}
.ly-checkbox__input.is-checked .ly-checkbox__inner,
.ly-radio__input.is-checked .ly-radio__inner,
.ly-checkbox__input.is-indeterminate .ly-checkbox__inner {
background-color: #409EFF;
border-color: #409EFF
}
.ly-checkbox__input.is-disabled+text.ly-checkbox__label,
.ly-radio__input.is-disabled+text.ly-radio__label {
color: #C0C4CC;
cursor: not-allowed
}
.ly-checkbox__input.is-checked .ly-checkbox__inner::after,
.ly-radio__input.is-checked .ly-radio__inner::after {
-webkit-transform: rotate(45deg) scaleY(1);
transform: rotate(45deg) scaleY(1)
}
.ly-checkbox__input.is-checked+.ly-checkbox__label,
.ly-radio__input.is-checked+.ly-radio__label {
color: #409EFF
}
.ly-checkbox__input.is-focus .ly-checkbox__inner,
.ly-radio__input.is-focus .ly-radio__inner {
border-color: #409EFF
}
.ly-checkbox__input.is-indeterminate .ly-checkbox__inner::before {
content: '';
position: absolute;
display: block;
background-color: #FFF;
height: 6rpx;
-webkit-transform: scale(.5);
transform: scale(.5);
left: 0;
right: 0;
top: 10rpx
}
.ly-checkbox__input.is-indeterminate .ly-checkbox__inner::after {
display: none
}
.ly-checkbox__inner,
.ly-radio__inner {
display: inline-block;
position: relative;
border: 2rpx solid #DCDFE6;
border-radius: 4rpx;
-webkit-box-sizing: border-box;
box-sizing: border-box;
width: 28rpx;
height: 28rpx;
background-color: #FFF;
z-index: 1;
-webkit-transition: border-color .25s cubic-bezier(.71, -.46, .29, 1.46), background-color .25s cubic-bezier(.71, -.46, .29, 1.46);
transition: border-color .25s cubic-bezier(.71, -.46, .29, 1.46), background-color .25s cubic-bezier(.71, -.46, .29, 1.46)
}
.ly-radio__inner {
border-radius: 50%;
width: 34rpx !important;
height: 34rpx !important;
}
.ly-checkbox__inner::after,
.ly-radio__inner::after {
-webkit-box-sizing: content-box;
box-sizing: content-box;
content: "";
border: 2rpx solid #FFF;
border-left: 0;
border-top: 0;
height: 14rpx;
left: 10rpx;
position: absolute;
top: 2rpx;
-webkit-transform: rotate(45deg) scaleY(0);
transform: rotate(45deg) scaleY(0);
width: 6rpx;
-webkit-transition: -webkit-transform .15s ease-in .05s;
transition: -webkit-transform .15s ease-in .05s;
transition: transform .15s ease-in .05s;
transition: transform .15s ease-in .05s, -webkit-transform .15s ease-in .05s;
-webkit-transform-origin: center;
transform-origin: center
}
.ly-radio__inner::after {
left: 12rpx !important;
top: 6rpx !important;
}
/* lyRadio/lyCheckbox-end */
</style>

@ -0,0 +1,436 @@
<template>
<view ref="node"
name="LyTreeNode"
v-show="node.visible"
class="ly-tree-node"
:class="{
'is-expanded': expanded,
'is-hidden': !node.visible,
'is-checked': !node.disabled && node.checked
}"
role="treeitem"
@tap.stop="handleClick" >
<view class="ly-tree-node__content"
:class="{
'is-current': node.isCurrent && highlightCurrent
}"
:style="{
'padding-left': (node.level - 1) * indent + 'px'
}">
<!-- 左边三角图标 -->
<text
@tap.stop="handleExpandIconClick"
:class="[
{
'is-leaf': node.isLeaf,
expanded: !node.isLeaf && node.expanded
},
'ly-tree-node__expand-icon',
iconClass ? iconClass : 'ly-iconfont ly-icon-caret-right'
]">
</text>
<!-- 勾选 -->
<ly-checkbox v-if="checkboxVisible || radioVisible"
:type="checkboxVisible ? 'checkbox' : 'radio'"
:checked="node.checked"
:indeterminate="node.indeterminate"
:disabled="!!node.disabled"
@check="handleCheckChange(!node.checked)"/>
<!-- 文字左边三角的右边写在本行 -->
<text v-if="node.loading"
class="ly-tree-node__loading-icon ly-iconfont ly-icon-loading">
</text>
<template v-if="node.icon && node.icon.length > 0">
<image
v-if="node.icon.indexOf('/') !== -1"
class="ly-tree-node__icon"
mode="widthFix"
:src="node.icon"
@error="handleImageError"
>
</image>
<text v-else
class="ly-tree-node__icon"
:class="node.icon">
</text>
</template>
<!-- 文字所在 -->
<view class="ly-tree-node__label df">
{{node.label}}
<!-- <image v-if="node.account" style="width: 20rpx;height: 20rpx;" src="../../static/img/aboutUs.png" width="15rpx" height="15rpx" mode=""></image> -->
</view>
</view>
<view v-if="!renderAfterExpand || childNodeRendered"
v-show="expanded"
class="ly-tree-node__children"
role="group">
<ly-tree-node v-for="cNodeId in node.childNodesId"
:nodeId="cNodeId"
:render-after-expand="renderAfterExpand"
:show-checkbox="showCheckbox"
:show-radio="showRadio"
:check-only-leaf="checkOnlyLeaf"
:key="getNodeKey(cNodeId)"
:indent="indent"
:icon-class="iconClass">
</ly-tree-node>
</view>
</view>
</template>
<script>
import {getNodeKey} from './tool/util.js';
import lyCheckbox from './components/ly-checkbox.vue';
export default {
name: 'LyTreeNode',
componentName: 'LyTreeNode',
components: {
lyCheckbox
},
props: {
nodeId: [Number, String],
renderAfterExpand: {
type: Boolean,
default: true
},
checkOnlyLeaf: {
type: Boolean,
default: false
},
showCheckbox: {
type: Boolean,
default: false
},
showRadio: {
type: Boolean,
default: false
},
indent: Number,
iconClass: String
},
data() {
return {
node: {
indeterminate: false,
checked: false,
expanded: false
},
expanded: false,
childNodeRendered: false,
oldChecked: null,
oldIndeterminate: null,
highlightCurrent: false
};
},
inject: ['tree'],
computed: {
checkboxVisible() {
if (this.checkOnlyLeaf) {
return this.showCheckbox && this.node.isLeaf;
}
return this.showCheckbox;
},
radioVisible() {
if (this.checkOnlyLeaf) {
return this.showRadio && this.node.isLeaf;
}
return this.showRadio;
}
},
watch: {
'node.indeterminate'(val) {
this.handleSelectChange(this.node.checked, val);
},
'node.checked'(val) {
this.handleSelectChange(val, this.node.indeterminate);
},
'node.expanded'(val) {
this.$nextTick(() => this.expanded = val);
if (val) {
this.childNodeRendered = true;
}
}
},
methods: {
getNodeKey(nodeId) {
let node = this.tree.store.root.getChildNodes([nodeId])[0];
return getNodeKey(this.tree.nodeKey, node.data);
},
handleSelectChange(checked, indeterminate) {
if (this.oldChecked !== checked && this.oldIndeterminate !== indeterminate) {
if (this.checkOnlyLeaf && !this.node.isLeaf) return;
if (this.checkboxVisible) {
const allNodes = this.tree.store._getAllNodes();
this.tree.$emit('check-change', {
checked,
indeterminate,
node: this.node,
data: this.node.data,
checkedall: allNodes.every(item => item.checked)
});
} else {
this.tree.$emit('radio-change', {
checked,
node: this.node,
data: this.node.data,
checkedall: false
});
}
}
if (!this.expanded && this.tree.expandOnCheckNode && checked) {
this.handleExpandIconClick();
}
this.oldChecked = checked;
this.indeterminate = indeterminate;
},
handleClick() {
this.tree.store.setCurrentNode(this.node);
this.tree.$emit('current-change', {
node: this.node,
data: this.tree.store.currentNode ? this.tree.store.currentNode.data : null,
currentNode: this.tree.store.currentNode
});
this.tree.currentNode = this.node;
if (this.tree.expandOnClickNode) {
this.handleExpandIconClick();
}
if (this.tree.checkOnClickNode && !this.node.disabled) {
(this.checkboxVisible || this.radioVisible) && this.handleCheckChange(!this.node.checked);
}
this.tree.$emit('node-click', this.node);
},
handleExpandIconClick() {
if (this.node.isLeaf) return;
if (this.expanded) {
this.tree.$emit('node-collapse', this.node);
this.node.collapse();
} else {
this.node.expand();
this.tree.$emit('node-expand', this.node);
if (this.tree.accordion) {
uni.$emit(`${this.tree.elId}-tree-node-expand`, this.node);
}
}
},
handleCheckChange(checked) {
if (this.node.disabled) return;
if (this.checkboxVisible) {
this.node.setChecked(checked, !(this.tree.checkStrictly || this.checkOnlyLeaf));
} else {
this.node.setRadioChecked(checked);
}
this.$nextTick(() => {
this.tree.$emit('check', {
node: this.node,
data: this.node.data,
checkedNodes: this.tree.store.getCheckedNodes(),
checkedKeys: this.tree.store.getCheckedKeys(),
halfCheckedNodes: this.tree.store.getHalfCheckedNodes(),
halfCheckedKeys: this.tree.store.getHalfCheckedKeys()
});
});
},
handleImageError() {
this.node.icon = this.tree.defaultNodeIcon;
}
},
created() {
if (!this.tree) {
throw new Error('Can not find node\'s tree.');
}
this.node = this.tree.store.nodesMap[this.nodeId];
this.highlightCurrent = this.tree.highlightCurrent;
if (this.node.expanded) {
this.expanded = true;
this.childNodeRendered = true;
}
const props = this.tree.props || {};
const childrenKey = props['children'] || 'children';
this.$watch(`node.data.${childrenKey}`, () => {
this.node.updateChildren();
});
if (this.tree.accordion) {
uni.$on(`${this.tree.elId}-tree-node-expand`, node => {
if (this.node.id !== node.id && this.node.level === node.level) {
this.node.collapse();
}
});
}
},
beforeDestroy() {
this.$parent = null;
}
};
</script>
<style>
.ly-tree-node {
white-space: nowrap;
outline: 0
}
.ly-tree-node__content {
display: flex;
align-items: center;
height: 70rpx;
}
.ly-tree-node__content.is-current {
background-color: #F5F7FA;
}
.ly-tree-node__content>.ly-tree-node__expand-icon {
padding: 12rpx;
}
.ly-tree-node__checkbox {
display: flex;
margin-right: 16rpx;
width: 40rpx;
height: 40rpx;
}
.ly-tree-node__checkbox>image {
width: 40rpx;
height: 40rpx;
}
.ly-tree-node__expand-icon {
color: #C0C4CC;
font-size: 28rpx;
-webkit-transform: rotate(0);
transform: rotate(0);
-webkit-transition: -webkit-transform .3s ease-in-out;
transition: -webkit-transform .3s ease-in-out;
transition: transform .3s ease-in-out;
transition: transform .3s ease-in-out, -webkit-transform .3s ease-in-out
}
.ly-tree-node__expand-icon.expanded {
-webkit-transform: rotate(90deg);
transform: rotate(90deg)
}
.ly-tree-node__expand-icon.is-leaf {
color: transparent;
}
.ly-tree-node__icon {
width: 34rpx;
height: 34rpx;
overflow: hidden;
margin-right: 16rpx;
}
.ly-tree-node__label {
font-size: 28rpx
}
.ly-tree-node__loading-icon {
margin-right: 16rpx;
font-size: 28rpx;
color: #C0C4CC;
-webkit-animation: rotating 2s linear infinite;
animation: rotating 2s linear infinite
}
.ly-tree-node>.ly-tree-node__children {
overflow: hidden;
background-color: transparent
}
.ly-tree-node>.ly-tree-node__children.collapse-transition {
transition: height .3s ease-in-out;
}
.ly-tree-node.is-expanded>.ly-tree-node__children {
display: block
}
.ly-tree-node_collapse {
overflow: hidden;
padding-top: 0;
padding-bottom: 0;
}
/* lyTree-end */
/* iconfont-start */
@font-face {
font-family: "ly-iconfont";
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAPsAAsAAAAACKwAAAOeAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDBgqFDIQPATYCJAMMCwgABCAFhG0HQBtfB8gekiSCdAwUAKgCFMA5Hj7H0PeTlABUr57PVyGqugqzSWJnNwWoWJjx/9rUr4TPL1ZSQpU2mycqwoRwIN3p+MkqMqyEW+OtMBLPSUBb8v//XtWMKTavxYIUsT/Wy1qbQzkBDOYEKGB7dVpPyVqgCnJNwvMvhZl10nMCtQbFoPVhY8ZDncJfF4grbqpQ13AqE52hWqgcOFrEQ6hWnW5VfMCD7Pfjn4WoI6nI/K0bl0MNGPBz0qcflVqYnvCA4vNDPUXGPFCIw8HgtsqiOK9SrW2smm6sVITElWlpISMdVBn8wyMJopLfXg+myZ48KCrSkvj9g37U1ItbXYke4APwXxK3N4TuehyBfmM0I3zbNdt7uk3VnjPtzX0rnIl7z7bZvb/thHohsu9QuykKo+Cws4nL7LsPmI3n2qN9B9upZEIKd4hu0NCKi0rt7fNtdl+I1N25hOJMDQK6odS123tROR7Pg8toEhDaF+kR0TYjxW6M58F5+ZNQOxmZHtE2g+IYjxjlNy/yIRQpCmrgq5R4/3jx8PvT8Ha8d3/xiLnt4EGyaDnznzRv8vpyZ+9TFHf/ntX9e59A+b6+fPHd5+dy0wYHVvHOroWbnWe879O9DnL53bN/gUHuwm28b/n8i/V3ry4E3IoXNqS6Rvs0LhJxeNVjoUkM3LKosU+0a6rh45FVvLt+2oz7Zd53b4QOy7/9snDXHbqVu+A+f8r7PnM2H8kXrWm5c8/vLu7LqRee7HW637mz3kHc5U/RCXf25d7G8tkdgEfwIpzpkknGpaMw3ww55q9Mn9OQNyua/wB/49OOWydn4eL/6roCfjx6FMmcxfJStYRKfd3UwoHiML4rF4uMSK+SvYTuNxMHrpl8yd3Q6v32cAeo/KFaowBJlQHIqo3zi3geKtRZhErVlqDWnOGn67QRKkWpwaw1AkKza5A0egFZszf8In4HFTp9h0rNUQm1NqP1lXUmgyuDBVUlNYi2gHA98FnokUreOZaac1xV1JlMMZGKEs+QdCLVrgynPhUcO0pzzYyUjDAReGSYeBl13YCEIrCpLhOWlGE+mWRD35TQAw8UawRKJVEGQrMAwekCPpaMlpTOz49FmeZwqcREX1t3Ikoo4dMTaQmpBfzhRn9R30uZXTKXKUOSmLSKEQIeYhjqKZcrcIzhMLLRrJMSrA35UF4yGMaWGhPHm733dwJq+Z/NkSJHUXemCirjgpuWrHMD1eC+mQUAAAA=') format('woff2');
}
.ly-iconfont {
font-family: "ly-iconfont" !important;
font-size: 30rpx;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.ly-icon-caret-right:before {
content: "\e8ee";
}
.ly-icon-loading:before {
content: "\e657";
}
/* iconfont-end */
/* animate-start */
@keyframes rotating {
0% {
-webkit-transform: rotateZ(0);
transform: rotateZ(0)
}
100% {
-webkit-transform: rotateZ(360deg);
transform: rotateZ(360deg)
}
}
/* animate-end */
.df{
display: flex;
justify-content: space-between;
/* .image{
width: 15rpx;
height: 15rpx;
} */
}
</style>

@ -0,0 +1,607 @@
<template>
<view>
<template v-if="showLoading">
<view class="ly-loader ly-flex-center">
<view class="ly-loader-inner">加载中...</view>
</view>
</template>
<template v-else>
<view v-if="isEmpty || !visible"
class="ly-empty">
{{emptyText}}
</view>
<view
:key="updateKey"
class="ly-tree"
:class="{'is-empty': isEmpty || !visible}"
role="tree"
name="LyTreeExpand">
<ly-tree-node
v-for="nodeId in childNodesId"
:nodeId="nodeId"
:render-after-expand="renderAfterExpand"
:show-checkbox="showCheckbox"
:show-radio="showRadio"
:check-only-leaf="checkOnlyLeaf"
:key="getNodeKey(nodeId)"
:indent="indent"
:icon-class="iconClass">
<text>我是值</text>
</ly-tree-node>
</view>
</template>
</view>
</template>
<script>
import Vue from 'vue'
import TreeStore from './model/tree-store.js';
import {getNodeKey} from './tool/util.js';
import LyTreeNode from './ly-tree-node.vue';
export default {
name: 'LyTree',
componentName: 'LyTree',
components: {
LyTreeNode
},
data() {
return {
updateKey: new Date().getTime(), //
elId: `ly_${Math.ceil(Math.random() * 10e5).toString(36)}`,
visible: true,
store: {
ready: false
},
currentNode: null,
childNodesId: []
};
},
provide() {
return {
tree: this
}
},
props: {
//
treeData: Array,
// loading
ready: {
type: Boolean,
default: true
},
//
emptyText: {
type: String,
default: '暂无数据'
},
//
renderAfterExpand: {
type: Boolean,
default: true
},
//
nodeKey: String,
// false
checkStrictly: Boolean,
//
defaultExpandAll: Boolean,
//
toggleExpendAll: Boolean,
// true false
expandOnClickNode: {
type: Boolean,
default: true
},
//
expandOnCheckNode: {
type: Boolean,
default: true
},
// false
checkOnClickNode: Boolean,
checkDescendants: {
type: Boolean,
default: false
},
//
autoExpandParent: {
type: Boolean,
default: true
},
// key
defaultCheckedKeys: Array,
// key
defaultExpandedKeys: Array,
//
expandCurrentNodeParent: Boolean,
//
currentNodeKey: [String, Number],
// /
checkOnlyLeaf: {
type: Boolean,
default: false
},
//
showCheckbox: {
type: Boolean,
default: false
},
//
showRadio: {
type: Boolean,
default: false
},
//
props: {
type: [Object, Function],
default () {
return {
children: 'children', //
label: 'label', //
disabled: 'disabled' //
};
}
},
// load 使
lazy: {
type: Boolean,
default: false
},
// false
highlightCurrent: Boolean,
// lazy true
load: Function,
// true false
filterNodeMethod: Function,
//
childVisibleForFilterNode: {
type: Boolean,
default: false
},
//
accordion: Boolean,
//
indent: {
type: Number,
default: 18
},
//
iconClass: String,
// true,props
showNodeIcon: {
type: Boolean,
default: false
},
//
defaultNodeIcon: {
type: String,
default: 'https://img-cdn-qiniu.dcloud.net.cn/uniapp/doc/github.svg'
},
// nodeparent
isInjectParentInNode: {
type: Boolean,
default: false
}
},
computed: {
isEmpty() {
if (this.store.root) {
const childNodes = this.store.root.getChildNodes(this.childNodesId);
return !childNodes || childNodes.length === 0 || childNodes.every(({visible}) => !visible);
}
return true;
},
showLoading() {
return !(this.store.ready && this.ready);
}
},
watch: {
toggleExpendAll(newVal) {
this.store.toggleExpendAll(newVal);
},
defaultCheckedKeys(newVal) {
this.store.setDefaultCheckedKey(newVal);
},
defaultExpandedKeys(newVal) {
this.store.defaultExpandedKeys = newVal;
this.store.setDefaultExpandedKeys(newVal);
},
checkStrictly(newVal) {
this.store.checkStrictly = newVal || this.checkOnlyLeaf;
},
'store.root.childNodesId'(newVal) {
this.childNodesId = newVal;
},
'store.root.visible'(newVal) {
this.visible = newVal;
},
childNodesId(){
this.$nextTick(() => {
this.$emit('ly-tree-render-completed');
});
},
treeData: {
handler(newVal) {
this.updateKey = new Date().getTime();
this.store.setData(newVal);
},
deep: true
}
},
methods: {
/*
* @description 对树节点进行筛选操作
* @method filter
* @param {all} value filter-node-method 中作为第一个参数
* @param {Object} data 搜索指定节点的节点数据不传代表搜索所有节点假如要搜索A节点下面的数据那么nodeData代表treeData中A节点的数据
*/
filter(value, data) {
if (!this.filterNodeMethod) throw new Error('[Tree] filterNodeMethod is required when filter');
this.store.filter(value, data);
},
/*
* @description 获取节点的唯一标识符
* @method getNodeKey
* @param {String, Number} nodeId
* @return {String, Number} 匹配到的数据中的某一项数据
*/
getNodeKey(nodeId) {
let node = this.store.root.getChildNodes([nodeId])[0];
return getNodeKey(this.nodeKey, node.data);
},
/*
* @description 获取节点路径
* @method getNodePath
* @param {Object} data 节点数据
* @return {Array} 路径数组
*/
getNodePath(data) {
return this.store.getNodePath(data);
},
/*
* @description 若节点可被选择 show-checkbox true则返回目前被选中的节点所组成的数组
* @method getCheckedNodes
* @param {Boolean} leafOnly 是否只是叶子节点默认false
* @param {Boolean} includeHalfChecked 是否包含半选节点默认false
* @return {Array} 目前被选中的节点所组成的数组
*/
getCheckedNodes(leafOnly, includeHalfChecked) {
return this.store.getCheckedNodes(leafOnly, includeHalfChecked);
},
/*
* @description 若节点可被选择 show-checkbox true则返回目前被选中的节点的 key 所组成的数组
* @method getCheckedKeys
* @param {Boolean} leafOnly 是否只是叶子节点默认false,若为 true 则仅返回被选中的叶子节点的 keys
* @param {Boolean} includeHalfChecked 是否返回indeterminate为true的节点默认false
* @return {Array} 目前被选中的节点所组成的数组
*/
getCheckedKeys(leafOnly, includeHalfChecked) {
return this.store.getCheckedKeys(leafOnly, includeHalfChecked);
},
/*
* @description 获取当前被选中节点的 data若没有节点被选中则返回 null
* @method getCurrentNode
* @return {Object} 当前被选中节点的 data若没有节点被选中则返回 null
*/
getCurrentNode() {
const currentNode = this.store.getCurrentNode();
return currentNode ? currentNode.data : null;
},
/*
* @description 获取当前被选中节点的 key若没有节点被选中则返回 null
* @method getCurrentKey
* @return {all} 当前被选中节点的 key 若没有节点被选中则返回 null
*/
getCurrentKey() {
const currentNode = this.getCurrentNode();
return currentNode ? currentNode[this.nodeKey] : null;
},
/*
* @description 设置全选/取消全选
* @method setCheckAll
* @param {Boolean} isCheckAll 选中状态,默认为true
*/
setCheckAll(isCheckAll = true) {
if (this.showRadio) throw new Error('You set the "show-radio" property, so you cannot select all nodes');
if (!this.showCheckbox) console.warn('You have not set the property "show-checkbox". Please check your settings');
this.store.setCheckAll(isCheckAll);
},
/*
* @description 设置目前勾选的节点
* @method setCheckedNodes
* @param {Array} nodes 接收勾选节点数据的数组
* @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态默认值为 false
*/
setCheckedNodes(nodes, leafOnly) {
this.store.setCheckedNodes(nodes, leafOnly);
},
/*
* @description 通过 keys 设置目前勾选的节点
* @method setCheckedKeys
* @param {Array} keys 勾选节点的 key 的数组
* @param {Boolean} leafOnly 是否只是叶子节点, 若为 true 则仅设置叶子节点的选中状态默认值为 false
*/
setCheckedKeys(keys, leafOnly) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCheckedKeys');
this.store.setCheckedKeys(keys, leafOnly);
},
/*
* @description 通过 key / data 设置某个节点的勾选状态
* @method setChecked
* @param {all} data 勾选节点的 key 或者 data
* @param {Boolean} checked 节点是否选中
* @param {Boolean} deep 是否设置子节点 默认为 false
*/
setChecked(data, checked, deep) {
this.store.setChecked(data, checked, deep);
},
/*
* @description 若节点可被选择 show-checkbox true则返回目前半选中的节点所组成的数组
* @method getHalfCheckedNodes
* @return {Array} 目前半选中的节点所组成的数组
*/
getHalfCheckedNodes() {
return this.store.getHalfCheckedNodes();
},
/*
* @description 若节点可被选择 show-checkbox true则返回目前半选中的节点的 key 所组成的数组
* @method getHalfCheckedKeys
* @return {Array} 目前半选中的节点的 key 所组成的数组
*/
getHalfCheckedKeys() {
return this.store.getHalfCheckedKeys();
},
/*
* @description 通过 node 设置某个节点的当前选中状态
* @method setCurrentNode
* @param {Object} node 待被选节点的 node
*/
setCurrentNode(node) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentNode');
this.store.setUserCurrentNode(node);
},
/*
* @description 通过 key 设置某个节点的当前选中状态
* @method setCurrentKey
* @param {all} key 待被选节点的 key若为 null 则取消当前高亮的节点
*/
setCurrentKey(key) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in setCurrentKey');
this.store.setCurrentNodeKey(key);
},
/*
* @description 根据 data 或者 key 拿到 Tree 组件中的 node
* @method getNode
* @param {all} data 要获得 node key 或者 data
*/
getNode(data) {
return this.store.getNode(data);
},
/*
* @description 删除 Tree 中的一个节点
* @method remove
* @param {all} data 要删除的节点的 data 或者 node
*/
remove(data) {
this.store.remove(data);
},
/*
* @description Tree 中的一个节点追加一个子节点
* @method append
* @param {Object} data 要追加的子节点的 data
* @param {Object} parentNode 子节点的 parent datakey 或者 node
*/
append(data, parentNode) {
this.store.append(data, parentNode);
},
/*
* @description Tree 的一个节点的前面增加一个节点
* @method insertBefore
* @param {Object} data 要增加的节点的 data
* @param {all} refNode 要增加的节点的后一个节点的 datakey 或者 node
*/
insertBefore(data, refNode) {
this.store.insertBefore(data, refNode);
},
/*
* @description Tree 的一个节点的后面增加一个节点
* @method insertAfter
* @param {Object} data 要增加的节点的 data
* @param {all} refNode 要增加的节点的前一个节点的 datakey 或者 node
*/
insertAfter(data, refNode) {
this.store.insertAfter(data, refNode);
},
/*
* @description 通过 keys 设置节点子元素
* @method updateKeyChildren
* @param {String, Number} key 节点 key
* @param {Object} data 节点数据的数组
*/
updateKeyChildren(key, data) {
if (!this.nodeKey) throw new Error('[Tree] nodeKey is required in updateKeyChild');
this.store.updateChildren(key, data);
}
},
created() {
this.isTree = true;
let props = this.props;
if (typeof this.props === 'function') props = this.props();
if (typeof props !== 'object') throw new Error('props must be of object type.');
this.store = new TreeStore({
key: this.nodeKey,
data: this.treeData,
lazy: this.lazy,
props: props,
load: this.load,
showCheckbox: this.showCheckbox,
showRadio: this.showRadio,
currentNodeKey: this.currentNodeKey,
checkStrictly: this.checkStrictly || this.checkOnlyLeaf,
checkDescendants: this.checkDescendants,
expandOnCheckNode: this.expandOnCheckNode,
defaultCheckedKeys: this.defaultCheckedKeys,
defaultExpandedKeys: this.defaultExpandedKeys,
expandCurrentNodeParent: this.expandCurrentNodeParent,
autoExpandParent: this.autoExpandParent,
defaultExpandAll: this.defaultExpandAll,
filterNodeMethod: this.filterNodeMethod,
childVisibleForFilterNode: this.childVisibleForFilterNode,
showNodeIcon: this.showNodeIcon,
isInjectParentInNode: this.isInjectParentInNode
});
this.childNodesId = this.store.root.childNodesId;
},
beforeDestroy() {
if (this.accordion) {
uni.$off(`${this.elId}-tree-node-expand`)
}
}
};
</script>
<style>
.ly-tree {
position: relative;
cursor: default;
background: #FFF;
color: #606266;
padding: 30rpx;
}
.ly-tree.is-empty {
background: transparent;
}
/* lyEmpty-start */
.ly-empty {
width: 100%;
display: flex;
justify-content: center;
margin-top: 100rpx;
}
/* lyEmpty-end */
/* lyLoader-start */
.ly-loader {
margin-top: 100rpx;
display: flex;
align-items: center;
justify-content: center;
}
.ly-loader-inner,
.ly-loader-inner:before,
.ly-loader-inner:after {
background: #efefef;
animation: load 1s infinite ease-in-out;
width: .5em;
height: 1em;
}
.ly-loader-inner:before,
.ly-loader-inner:after {
position: absolute;
top: 0;
content: '';
}
.ly-loader-inner:before {
left: -1em;
}
.ly-loader-inner {
text-indent: -9999em;
position: relative;
font-size: 22rpx;
animation-delay: 0.16s;
}
.ly-loader-inner:after {
left: 1em;
animation-delay: 0.32s;
}
/* lyLoader-end */
@keyframes load {
0%,
80%,
100% {
box-shadow: 0 0 #efefef;
height: 1em;
}
40% {
box-shadow: 0 -1.5em #efefef;
height: 1.5em;
}
}
</style>

@ -0,0 +1,538 @@
import {
markNodeData,
objectAssign,
arrayFindIndex,
getChildState,
reInitChecked,
getPropertyFromData,
isNull,
NODE_KEY
} from '../tool/util';
const getStore = function(store) {
let thisStore = store;
return function() {
return thisStore;
}
}
let nodeIdSeed = 0;
export default class Node {
constructor(options) {
this.time = new Date().getTime();
this.id = nodeIdSeed++;
this.text = null;
this.checked = false;
this.indeterminate = false;
this.data = null;
this.expanded = false;
this.parentId = null;
this.visible = true;
this.isCurrent = false;
for (let name in options) {
if (options.hasOwnProperty(name)) {
if (name === 'store') {
this.store = getStore(options[name]);
} else {
this[name] = options[name];
}
}
}
if (!this.store()) {
throw new Error('[Node]store is required!');
}
// internal
this.level = 0;
this.loaded = false;
this.childNodesId = [];
this.loading = false;
this.label = getPropertyFromData(this, 'label');
this.key = this._getKey();
this.disabled = getPropertyFromData(this, 'disabled');
this.nextSibling = null;
this.previousSibling = null;
this.icon = '';
this._handleParentAndLevel();
this._handleProps();
this._handleExpand();
this._handleCurrent();
if (this.store().lazy) {
this.store()._initDefaultCheckedNode(this);
}
this.updateLeafState();
}
_getKey() {
if (!this.data || Array.isArray(this.data)) return null;
if (typeof this.data === 'object') {
const nodeKey = this.store().key;
const key = this.data[nodeKey];
if (typeof key === 'undefined') {
throw new Error(`您配置的node-key为"${nodeKey}",但数据中并未找到对应"${nodeKey}"属性的值,请检查node-key的配置是否合理`)
}
return key;
}
throw new Error('不合法的data数据');
}
_handleParentAndLevel() {
if (this.parentId !== null) {
let parent = this.getParent(this.parentId);
if (this.store().isInjectParentInNode) {
this.parent = parent;
}
// 由于这里做了修改,默认第一个对象不会被注册到nodesMap中,所以找不到parent会报错,所以默认parent的level是0
if (!parent) {
parent = {
level: 0
}
} else {
const parentChildNodes = parent.getChildNodes(parent.childNodesId);
const index = parent.childNodesId.indexOf(this.key);
this.nextSibling = index > -1 ? parentChildNodes[index + 1] : null;
this.previousSibling = index > 0 ? parentChildNodes[index - 1] : null;
}
this.level = parent.level + 1;
}
}
_handleProps() {
const props = this.store().props;
if (this.store().showNodeIcon) {
if (props && typeof props.icon !== 'undefined') {
this.icon = getPropertyFromData(this, 'icon');
} else {
console.warn('请配置props属性中的"icon"字段')
}
}
this.store().registerNode(this);
if (props && typeof props.isLeaf !== 'undefined') {
const isLeaf = getPropertyFromData(this, 'isLeaf');
if (typeof isLeaf === 'boolean') {
this.isLeafByUser = isLeaf;
}
}
}
_handleExpand() {
if (this.store().lazy !== true && this.data) {
this.setData(this.data);
if (this.store().defaultExpandAll) {
this.expanded = true;
}
} else if (this.level > 0 && this.store().lazy && this.store().defaultExpandAll) {
this.expand();
}
if (!Array.isArray(this.data)) {
markNodeData(this, this.data);
}
if (!this.data) return;
const defaultExpandedKeys = this.store().defaultExpandedKeys;
const key = this.store().key;
if (key && defaultExpandedKeys && defaultExpandedKeys.indexOf(this.key) !== -1) {
this.expand(null, this.store().autoExpandparent);
}
}
_handleCurrent() {
const key = this.store().key;
if (key && this.store().currentNodeKey !== undefined && this.key === this.store().currentNodeKey) {
this.store().currentNode = this;
this.store().currentNode.isCurrent = true;
}
}
destroyStore() {
getStore(null)
}
setData(data) {
if (!Array.isArray(data)) {
markNodeData(this, data);
}
this.data = data;
this.childNodesId = [];
let children;
if (this.level === 0 && Array.isArray(this.data)) {
children = this.data;
} else {
children = getPropertyFromData(this, 'children') || [];
}
for (let i = 0, j = children.length; i < j; i++) {
this.insertChild({
data: children[i]
});
}
}
contains(target, deep = true) {
const walk = function(parent) {
const children = parent.getChildNodes(parent.childNodesId) || [];
let result = false;
for (let i = 0, j = children.length; i < j; i++) {
const child = children[i];
if (child === target || (deep && walk(child))) {
result = true;
break;
}
}
return result;
};
return walk(this);
}
remove() {
if (this.parentId !== null) {
const parent = this.getParent(this.parentId);
parent.removeChild(this);
}
}
insertChild(child, index, batch) {
if (!child) throw new Error('insertChild error: child is required.');
if (!(child instanceof Node)) {
if (!batch) {
const children = this.getChildren(true);
if (children.indexOf(child.data) === -1) {
if (typeof index === 'undefined' || index < 0) {
children.push(child.data);
} else {
children.splice(index, 0, child.data);
}
}
}
objectAssign(child, {
parentId: isNull(this.key) ? '' : this.key,
store: this.store()
});
child = new Node(child);
}
child.level = this.level + 1;
if (typeof index === 'undefined' || index < 0) {
this.childNodesId.push(child.key);
} else {
this.childNodesId.splice(index, 0, child.key);
}
this.updateLeafState();
}
insertBefore(child, ref) {
let index;
if (ref) {
index = this.childNodesId.indexOf(ref.id);
}
this.insertChild(child, index);
}
insertAfter(child, ref) {
let index;
if (ref) {
index = this.childNodesId.indexOf(ref.id);
if (index !== -1) index += 1;
}
this.insertChild(child, index);
}
removeChild(child) {
const children = this.getChildren() || [];
const dataIndex = children.indexOf(child.data);
if (dataIndex > -1) {
children.splice(dataIndex, 1);
}
const index = this.childNodesId.indexOf(child.key);
if (index > -1) {
this.store() && this.store().deregisterNode(child);
child.parentId = null;
this.childNodesId.splice(index, 1);
}
this.updateLeafState();
}
removeChildByData(data) {
let targetNode = null;
for (let i = 0; i < this.childNodesId.length; i++) {
let node = this.getChildNodes(this.childNodesId);
if (node[i].data === data) {
targetNode = node[i];
break;
}
}
if (targetNode) {
this.removeChild(targetNode);
}
}
// 为了避免APP端parent嵌套结构导致报错,这里parent需要从nodesMap中获取
getParent(parentId) {
try {
if (!parentId.toString()) return null;
return this.store().nodesMap[parentId];
} catch (error) {
return null;
}
}
// 为了避免APP端childNodes嵌套结构导致报错,这里childNodes需要从nodesMap中获取
getChildNodes(childNodesId) {
let childNodes = [];
if (childNodesId.length === 0) return childNodes;
childNodesId.forEach((key) => {
childNodes.push(this.store().nodesMap[key]);
})
return childNodes;
}
expand(callback, expandparent) {
const done = () => {
if (expandparent) {
let parent = this.getParent(this.parentId);
while (parent && parent.level > 0) {
parent.expanded = true;
parent = this.getParent(parent.parentId);
}
}
this.expanded = true;
if (callback) callback();
};
if (this.shouldLoadData()) {
this.loadData(function(data) {
if (Array.isArray(data)) {
if (this.checked) {
this.setChecked(true, true);
} else if (!this.store().checkStrictly) {
reInitChecked(this);
}
done();
}
});
} else {
done();
}
}
doCreateChildren(array, defaultProps = {}) {
array.forEach((item) => {
this.insertChild(objectAssign({
data: item
}, defaultProps), undefined, true);
});
}
collapse() {
this.expanded = false;
}
shouldLoadData() {
return this.store().lazy === true && this.store().load && !this.loaded;
}
updateLeafState() {
if (this.store().lazy === true && this.loaded !== true && typeof this.isLeafByUser !== 'undefined') {
this.isLeaf = this.isLeafByUser;
return;
}
const childNodesId = this.childNodesId;
if (!this.store().lazy || (this.store().lazy === true && this.loaded === true)) {
this.isLeaf = !childNodesId || childNodesId.length === 0;
return;
}
this.isLeaf = false;
}
setChecked(value, deep, recursion, passValue) {
this.indeterminate = value === 'half';
this.checked = value === true;
if (this.checked && this.store().expandOnCheckNode) {
this.expand(null, true)
}
if (this.store().checkStrictly) return;
if (this.store().showRadio) return;
if (!(this.shouldLoadData() && !this.store().checkDescendants)) {
let childNodes = this.getChildNodes(this.childNodesId);
let {
all,
allWithoutDisable
} = getChildState(childNodes);
if (!this.isLeaf && (!all && allWithoutDisable)) {
this.checked = false;
value = false;
}
const handleDescendants = () => {
if (deep) {
let childNodes = this.getChildNodes(this.childNodesId)
for (let i = 0, j = childNodes.length; i < j; i++) {
const child = childNodes[i];
passValue = passValue || value !== false;
const isCheck = child.disabled ? child.checked : passValue;
child.setChecked(isCheck, deep, true, passValue);
}
const {
half,
all
} = getChildState(childNodes);
if (!all) {
this.checked = all;
this.indeterminate = half;
}
}
};
if (this.shouldLoadData()) {
this.loadData(() => {
handleDescendants();
reInitChecked(this);
}, {
checked: value !== false
});
return;
} else {
handleDescendants();
}
}
if (!this.parentId) return;
let parent = this.getParent(this.parentId);
if (parent && parent.level === 0) return;
if (!recursion) {
reInitChecked(parent);
}
}
setRadioChecked(value) {
const allNodes = this.store()._getAllNodes().sort((a, b) => b.level - a.level);
allNodes.forEach(node => node.setChecked(false, false));
this.checked = value === true;
}
getChildren(forceInit = false) {
if (this.level === 0) return this.data;
const data = this.data;
if (!data) return null;
const props = this.store().props;
let children = 'children';
if (props) {
children = props.children || 'children';
}
if (data[children] === undefined) {
data[children] = null;
}
if (forceInit && !data[children]) {
data[children] = [];
}
return data[children];
}
updateChildren() {
let childNodes = this.getChildNodes(this.childNodesId);
const newData = this.getChildren() || [];
const oldData = childNodes.map((node) => node.data);
const newDataMap = {};
const newNodes = [];
newData.forEach((item, index) => {
const key = item[NODE_KEY];
const isNodeExists = !!key && arrayFindIndex(oldData, data => data[NODE_KEY] === key) >= 0;
if (isNodeExists) {
newDataMap[key] = {
index,
data: item
};
} else {
newNodes.push({
index,
data: item
});
}
});
if (!this.store().lazy) {
oldData.forEach((item) => {
if (!newDataMap[item[NODE_KEY]]) this.removeChildByData(item);
});
}
newNodes.forEach(({
index,
data
}) => {
this.insertChild({
data
}, index);
});
this.updateLeafState();
}
loadData(callback, defaultProps = {}) {
if (this.store().lazy === true &&
this.store().load && !this.loaded &&
(!this.loading || Object.keys(defaultProps).length)
) {
this.loading = true;
const resolve = (children) => {
this.loaded = true;
this.loading = false;
this.childNodesId = [];
this.doCreateChildren(children, defaultProps);
this.updateLeafState();
callback && callback.call(this,children);
};
this.store().load(this, resolve);
} else {
callback && callback.call(this);
}
}
}

@ -0,0 +1,419 @@
import Node from './node';
import {
getNodeKey,
getPropertyFromData
} from '../tool/util';
export default class TreeStore {
constructor(options) {
this.ready = false;
this.currentNode = null;
this.currentNodeKey = null;
Object.assign(this, options);
if (!this.key) {
throw new Error('[Tree] nodeKey is required');
}
this.nodesMap = {};
this.root = new Node({
data: this.data,
store: this
});
if (this.lazy && this.load) {
const loadFn = this.load;
loadFn(this.root, (data) => {
this.root.doCreateChildren(data);
this._initDefaultCheckedNodes();
this.ready = true;
});
} else {
this._initDefaultCheckedNodes();
this.ready = true;
}
}
filter(value, data) {
const filterNodeMethod = this.filterNodeMethod;
const lazy = this.lazy;
const _self = this;
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(node.childNodesId);
childNodes.forEach((child) => {
if (data && typeof data === 'object') {
let nodePath = _self.getNodePath(child.data);
if (!nodePath.some(pathItem => pathItem[_self.key] === data[_self.key])) {
child.visible = false;
traverse(child);
return;
}
}
if (_self.childVisibleForFilterNode) {
let parent = child.getParent(child.parentId);
child.visible = filterNodeMethod.call(child, value, child.data, child) || (parent && parent.visible);
} else {
child.visible = filterNodeMethod.call(child, value, child.data, child);
}
traverse(child);
});
if (!node.visible && childNodes.length) {
let allHidden = true;
allHidden = !childNodes.some(child => child.visible);
if (node.root) {
node.root.visible = allHidden === false;
} else {
node.visible = allHidden === false;
}
}
if (!value) return;
if (node.visible && !node.isLeaf && !lazy) node.expand();
};
traverse(this);
}
setData(newVal) {
const instanceChanged = newVal !== this.root.data;
if (instanceChanged) {
this.root.setData(newVal);
this._initDefaultCheckedNodes();
} else {
this.root.updateChildren();
}
}
getNode(data) {
if (data instanceof Node) return data;
const key = typeof data !== 'object' ? data : getNodeKey(this.key, data);
if (!key) return null;
return this.nodesMap[key] || null;
}
insertBefore(data, refData) {
const refNode = this.getNode(refData);
let parent = refNode.getParent(refNode.parentId);
parent.insertBefore({
data
}, refNode);
}
insertAfter(data, refData) {
const refNode = this.getNode(refData);
let parent = refNode.getParent(refNode.parentId);
parent.insertAfter({
data
}, refNode);
}
remove(data) {
const node = this.getNode(data);
if (node && node.parentId !== null) {
let parent = node.getParent(node.parentId);
if (node === this.currentNode) {
this.currentNode = null;
}
parent.removeChild(node);
}
}
append(data, parentData) {
const parentNode = parentData ? this.getNode(parentData) : this.root;
if (parentNode) {
parentNode.insertChild({
data
});
}
}
_initDefaultCheckedNodes() {
const defaultCheckedKeys = this.defaultCheckedKeys || [];
const nodesMap = this.nodesMap;
let checkedKeyfromData = [];
let totalCheckedKeys = []
for (let key in nodesMap) {
let checked = getPropertyFromData(nodesMap[key], 'checked') || false;
checked && checkedKeyfromData.push(key);
}
totalCheckedKeys = Array.from(new Set([...defaultCheckedKeys, ...checkedKeyfromData]));
totalCheckedKeys.forEach((checkedKey) => {
const node = nodesMap[checkedKey];
if (node) {
node.setChecked(true, !this.checkStrictly);
}
});
}
_initDefaultCheckedNode(node) {
const defaultCheckedKeys = this.defaultCheckedKeys || [];
if (defaultCheckedKeys.indexOf(node.key) !== -1) {
node.setChecked(true, !this.checkStrictly);
}
}
toggleExpendAll(isExpandAll) {
const allNodes = this._getAllNodes();
allNodes.forEach(item => {
const node = this.getNode(item.key);
if (node) isExpandAll ? node.expand() : node.collapse();
});
}
setCheckAll(isCkeckAll) {
const allNodes = this._getAllNodes();
allNodes.forEach(item => {
item.setChecked(isCkeckAll, false);
});
}
setDefaultCheckedKey(newVal) {
if (newVal !== this.defaultCheckedKeys) {
this.defaultCheckedKeys = newVal;
this._initDefaultCheckedNodes();
}
}
registerNode(node) {
const key = this.key;
if (!key || !node || !node.data) return;
const nodeKey = node.key;
if (nodeKey !== undefined) this.nodesMap[node.key] = node;
}
deregisterNode(node) {
const key = this.key;
if (!key || !node || !node.data) return;
let childNodes = node.getChildNodes(node.childNodesId);
childNodes.forEach(child => {
this.deregisterNode(child);
});
delete this.nodesMap[node.key];
}
getNodePath(data) {
if (!this.key) throw new Error('[Tree] nodeKey is required in getNodePath');
const node = this.getNode(data);
if (!node) return [];
const path = [node.data];
let parent = node.getParent(node.parentId);
while (parent && parent !== this.root) {
path.push(parent.data);
parent = parent.getParent(parent.parentId);
}
return path.reverse();
}
getCheckedNodes(leafOnly = false, includeHalfChecked = false) {
const checkedNodes = [];
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(node.childNodesId);
childNodes.forEach((child) => {
if ((child.checked || (includeHalfChecked && child.indeterminate)) && (!leafOnly || (leafOnly && child.isLeaf))) {
checkedNodes.push(child.data);
}
traverse(child);
});
};
traverse(this);
return checkedNodes;
}
getCheckedKeys(leafOnly = false, includeHalfChecked = false) {
return this.getCheckedNodes(leafOnly, includeHalfChecked).map((data) => (data || {})[this.key]);
}
getHalfCheckedNodes() {
const nodes = [];
const traverse = function(node) {
const childNodes = node.root ? node.root.getChildNodes(node.root.childNodesId) : node.getChildNodes(node.childNodesId);
childNodes.forEach((child) => {
if (child.indeterminate) {
nodes.push(child.data);
}
traverse(child);
});
};
traverse(this);
return nodes;
}
getHalfCheckedKeys() {
return this.getHalfCheckedNodes().map((data) => (data || {})[this.key]);
}
_getAllNodes() {
const allNodes = [];
const nodesMap = this.nodesMap;
for (let nodeKey in nodesMap) {
if (nodesMap.hasOwnProperty(nodeKey)) {
allNodes.push(nodesMap[nodeKey]);
}
}
return allNodes;
}
updateChildren(key, data) {
const node = this.nodesMap[key];
if (!node) return;
const childNodes = node.getChildNodes(node.childNodesId);
for (let i = childNodes.length - 1; i >= 0; i--) {
const child = childNodes[i];
this.remove(child.data);
}
for (let i = 0, j = data.length; i < j; i++) {
const child = data[i];
this.append(child, node.data);
}
}
_setCheckedKeys(key, leafOnly = false, checkedKeys) {
const allNodes = this._getAllNodes().sort((a, b) => b.level - a.level);
const cache = Object.create(null);
const keys = Object.keys(checkedKeys);
allNodes.forEach(node => node.setChecked(false, false));
for (let i = 0, j = allNodes.length; i < j; i++) {
const node = allNodes[i];
let nodeKey = node.data[key];
if (typeof nodeKey === 'undefined') continue;
nodeKey = nodeKey.toString();
let checked = keys.indexOf(nodeKey) > -1;
if (!checked) {
if (node.checked && !cache[nodeKey]) {
node.setChecked(false, false);
}
continue;
}
let parent = node.getParent(node.parentId);
while (parent && parent.level > 0) {
cache[parent.data[key]] = true;
parent = parent.getParent(parent.parentId);
}
if (node.isLeaf || this.checkStrictly) {
node.setChecked(true, false);
continue;
}
node.setChecked(true, true);
if (leafOnly) {
node.setChecked(false, false);
const traverse = function(node) {
const childNodes = node.getChildNodes(node.childNodesId);
childNodes.forEach((child) => {
if (!child.isLeaf) {
child.setChecked(false, false);
}
traverse(child);
});
};
traverse(node);
}
}
}
setCheckedNodes(array, leafOnly = false) {
const key = this.key;
const checkedKeys = {};
array.forEach((item) => {
checkedKeys[(item || {})[key]] = true;
});
this._setCheckedKeys(key, leafOnly, checkedKeys);
}
setCheckedKeys(keys, leafOnly = false) {
this.defaultCheckedKeys = keys;
const key = this.key;
const checkedKeys = {};
keys.forEach((key) => {
checkedKeys[key] = true;
});
this._setCheckedKeys(key, leafOnly, checkedKeys);
}
setDefaultExpandedKeys(keys) {
keys = keys || [];
this.defaultExpandedKeys = keys;
keys.forEach((key) => {
const node = this.getNode(key);
if (node) node.expand(null, this.autoExpandParent);
});
}
setChecked(data, checked, deep) {
const node = this.getNode(data);
if (node) {
node.setChecked(!!checked, deep);
}
}
getCurrentNode() {
return this.currentNode;
}
setCurrentNode(currentNode) {
const prevCurrentNode = this.currentNode;
if (prevCurrentNode) {
prevCurrentNode.isCurrent = false;
}
this.currentNode = currentNode;
this.currentNode.isCurrent = true;
this.expandCurrentNodeParent && this.currentNode.expand(null, true)
}
setUserCurrentNode(node) {
const key = node[this.key];
const currNode = this.nodesMap[key];
this.setCurrentNode(currNode);
}
setCurrentNodeKey(key) {
if (key === null || key === undefined) {
this.currentNode && (this.currentNode.isCurrent = false);
this.currentNode = null;
return;
}
const node = this.getNode(key);
if (node) {
this.setCurrentNode(node);
}
}
};

@ -0,0 +1,115 @@
export const NODE_KEY = '$treeNodeId';
export const markNodeData = function(node, data) {
if (!data || data[NODE_KEY]) return;
Object.defineProperty(data, NODE_KEY, {
value: node.id,
enumerable: false,
configurable: false,
writable: false
});
};
export const getNodeKey = function(key, data) {
if (!data) return null;
if (!key) return data[NODE_KEY];
return data[key];
};
export const objectAssign = function(target) {
for (let i = 1, j = arguments.length; i < j; i++) {
let source = arguments[i] || {};
for (let prop in source) {
if (source.hasOwnProperty(prop)) {
let value = source[prop];
if (value !== undefined) {
target[prop] = value;
}
}
}
}
return target;
};
// TODO: use native Array.find, Array.findIndex when IE support is dropped
export const arrayFindIndex = function(arr, pred) {
for (let i = 0; i !== arr.length; ++i) {
if (pred(arr[i])) {
return i;
}
}
return -1;
};
export const getChildState = function(node) {
let all = true;
let none = true;
let allWithoutDisable = true;
for (let i = 0, j = node.length; i < j; i++) {
const n = node[i];
if (n.checked !== true || n.indeterminate) {
all = false;
if (!n.disabled) {
allWithoutDisable = false;
}
}
if (n.checked !== false || n.indeterminate) {
none = false;
}
}
return {
all,
none,
allWithoutDisable,
half: !all && !none
};
};
export const reInitChecked = function(node) {
if (!node || node.childNodesId.length === 0) return;
let childNodes = node.getChildNodes(node.childNodesId);
const {
all,
none,
half
} = getChildState(childNodes);
if (all) {
node.checked = true;
node.indeterminate = false;
} else if (half) {
node.checked = false;
node.indeterminate = true;
} else if (none) {
node.checked = false;
node.indeterminate = false;
}
let parent = node.getParent(node.parentId);
if (!parent || parent.level === 0) return;
if (!node.store().checkStrictly) {
reInitChecked(parent);
}
};
export const getPropertyFromData = function(node, prop) {
const props = node.store().props;
const data = node.data || {};
const config = props[prop];
if (typeof config === 'function') {
return config(data, node);
} else if (typeof config === 'string') {
return data[config];
} else if (typeof config === 'undefined') {
const dataProp = data[prop];
return dataProp === undefined ? '' : dataProp;
}
};
export const isNull = function(v) {
return v === undefined || v === null || v === '';
}

@ -0,0 +1,184 @@
<!-- tab组件: <me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs> -->
<template>
<view class="me-tabs" :class="{'tabs-fixed': fixed}" :style="{height: tabHeightVal}">
<scroll-view v-if="tabs.length" :id="viewId" :scroll-left="scrollLeft" scroll-x scroll-with-animation :scroll-animation-duration="300">
<view class="tabs-item" :class="{'tabs-flex':!isScroll, 'tabs-scroll':isScroll}">
<!-- tab -->
<view class="tab-item" :style="{width: tabWidthVal, height: tabHeightVal, 'line-height':tabHeightVal}" v-for="(tab, i) in tabs" :class="{'active': value===i}" :key="i" @click="tabClick(i)">
{{getTabName(tab)}}
</view>
<!-- 下划线 -->
<view class="tabs-line" :style="{left:lineLeft}"></view>
</view>
</scroll-view>
</view>
</template>
<script>
export default {
props:{
tabs: { // : ['', ''] [{name:''}, {name:''}]
type: Array,
default(){
return []
}
},
nameKey: { // name
type: String,
default: 'name'
},
value: { // (使v-model: 1.propsvalue; 2.input)
type: [String, Number],
default: 0
},
fixed: Boolean, // ,false
tabWidth: Number, // tab,,flex; ,使flex,tab,(rpx)
height: { // ,rpx
type: Number,
default: 90
}
},
data() {
return {
viewId: 'id_' + Math.random().toString(36).substr(2,16),
scrollLeft: 0
}
},
computed: {
isScroll(){
return this.tabWidth && this.tabs.length // tabWidth,
},
tabHeightPx(){
return uni.upx2px(this.height)
},
tabHeightVal(){
return this.tabHeightPx+'px'
},
tabWidthPx(){
return uni.upx2px(this.tabWidth)
},
tabWidthVal(){
return this.isScroll ? this.tabWidthPx+'px' : ''
},
lineLeft() {
if (this.isScroll) {
return this.tabWidthPx * this.value + this.tabWidthPx/2 + 'px' // px (rpxiOS)
} else{
return 100/this.tabs.length*(this.value + 1) - 100/(this.tabs.length*2) + '%'
}
}
},
watch: {
tabs() {
this.warpWidth = null; //
this.scrollCenter(); //
},
value() {
this.scrollCenter(); //
}
},
methods: {
getTabName(tab){
return typeof tab === "object" ? tab[this.nameKey] : tab
},
tabClick(i){
if(this.value!=i){
this.$emit("input",i);
this.$emit("change",i);
}
},
async scrollCenter(){
if(!this.isScroll) return;
if(!this.warpWidth){ // tabs
let rect = await this.initWarpRect()
this.warpWidth = rect ? rect.width : uni.getSystemInfoSync().windowWidth; // ,
}
let tabLeft = this.tabWidthPx * this.value + this.tabWidthPx/2; // tab
let diff = tabLeft - this.warpWidth/2 // tabs,
this.scrollLeft = diff;
// #ifdef MP-TOUTIAO
this.scrollTimer && clearTimeout(this.scrollTimer)
this.scrollTimer = setTimeout(()=>{ // ,scrollLeft,tab
this.scrollLeft = Math.ceil(diff)
},400)
// #endif
},
initWarpRect(){
return new Promise(resolve=>{
setTimeout(()=>{ // dom, 使$nextclick
let query = uni.createSelectorQuery();
// #ifndef MP-ALIPAY
query = query.in(this) // in(this),in(this),
// #endif
query.select('#'+this.viewId).boundingClientRect(data => {
resolve(data)
}).exec();
},20)
})
}
},
mounted() {
this.scrollCenter() //
}
}
</script>
<style lang="scss">
.me-tabs{
position: relative;
font-size: 28rpx;
color: #707070;
box-sizing: border-box;
overflow-y: hidden;
background-color: #fff;
&.tabs-fixed{
z-index: 990;
position: fixed;
top: var(--window-top);
left: 0;
width: 100%;
}
.tabs-item{
position: relative;
white-space: nowrap;
padding-bottom: 30rpx; // ,me-tabsoverflow-y: hidden,
box-sizing: border-box;
.tab-item{
position: relative;
text-align: center;
box-sizing: border-box;
&.active{
color: #00B9FF;
}
}
}
// item
.tabs-flex{
display: flex;
.tab-item{
flex: 1;
}
}
// item,
.tabs-scroll{
.tab-item{
display: inline-block;
}
}
// tab线
.tabs-line{
z-index: 1;
position: absolute;
bottom: 30rpx; // .tabs-itempadding-bottom,
width: 132rpx;
height: 8rpx;
transform: translateX(-50%);
border-radius: 4rpx;
transition: left .3s;
background: #00B9FF;
}
}
</style>

@ -0,0 +1,179 @@
<!-- 视频组件: <me-video src="视频地址" poster="封面图"></me-video>
video标签在APP端是原生组件, 真机APP端下拉时会渲染不及时, 出现悬浮错位现象;
me-video组件, 未播放时自动展示image封面, 播放时才显示video, 提高性能; 如果播放中执行下拉,会自动显示封面, 避免视频下拉悬浮错位;
-->
<template>
<view class="me-video" :style="{width:width, height:height}">
<!-- 播放的时候才渲染video标签 -->
<video v-if="showVideo" ref="videoRef" class="video" :class="{'full-play': fullplay&&!autoplay, 'mescroll-dowload': mescrollDownLoad}" :src="src" autoplay :loop="loop" @click="videoClick" x5-playsinline="true" x5-video-player-type="h5" playsinline="true" webkit-playsinline="true" x5-video-player-fullscreen="false"></video>
<!-- 播放按钮 -->
<view v-else class="btn-play"> <view class="triangle"></view> </view>
<!-- 封面 -->
<image v-if="(!showVideo || mescrollDownLoad) && poster" class="poster" :src="poster" @click="play()" mode="aspectFit"></image>
</view>
</template>
<script>
export default {
props: {
src: String, //
poster: String, //
autoplay: { //
type: Boolean,
default(){
return false
}
},
fullplay: { //
type: Boolean,
default(){
return false
}
},
loop: { //
type: Boolean,
default(){
return true // Android广
}
},
width: { // (,: '100%', '300px', '300rpx')
type: String,
default: "100%"
},
height: { // (,: '100%', '300px', '300rpx')
type: String,
default: "225px"
},
mescroll: { // mescroll,APP,.APP
type: Object,
default(){
return {}
}
}
},
data() {
return {
showVideo: this.autoplay //
}
},
computed: {
// (,, APP)
mescrollDownLoad() {
// #ifdef APP-PLUS
return this.mescroll.downLoadType
// #endif
// #ifndef APP-PLUS
return false
// #endif
}
},
watch: {
autoplay(val) {
if(val) this.play()
}
},
methods: {
//
play(){
this.showVideo = true
this.wxAutoPlay()
},
//
videoClick(){
// 退
if(this.fullplay) this.showVideo = false
},
//
wxAutoPlay(){
// #ifdef H5
//
if(navigator.userAgent.toLowerCase().match(/MicroMessenger/i) == 'micromessenger'){
// iOS
let head = document.getElementsByTagName("head")[0]
let wxscript = document.createElement("script");
wxscript.type = "text/javascript"
wxscript.src = "https://res.wx.qq.com/open/js/jweixin-1.6.0.js"
head.appendChild(wxscript)
let vm = this
let doPlay = function(){
vm.$refs.videoRef && vm.$refs.videoRef.play()
}
wxscript.onload = function(){
window.wx.config({
debug: !1,
appId: "",
timestamp: 1,
nonceStr: "",
signature: "",
jsApiList: []
})
window.wx.ready(doPlay)
}
// Android
document.addEventListener("WeixinJSBridgeReady", doPlay, false);
//
setTimeout(()=>{
doPlay()
},20)
}
// #endif
}
}
}
</script>
<style lang="scss">
.me-video{
position: relative;
background-color: #000;
overflow: hidden;
//
.btn-play{
z-index: 9;
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
width: 100rpx;
height: 100rpx;
border-radius: 50%;
background-color: rgba(0,0,0,.75);
pointer-events: none;
.triangle{
position: absolute;
left: 50%;
top: 50%;
transform: translate(-25%, -50%);
width: 0;
height: 0;
border-top: 16rpx solid transparent;
border-left: 24rpx solid #fff;
border-bottom: 16rpx solid transparent;
}
}
//
.poster{
width: 100%;
height: 100%;
vertical-align: bottom;
}
// ()
.video{
z-index: 8;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
//
&.full-play{
z-index: 999;
position: fixed;
}
//
&.mescroll-dowload{
display: none;
}
}
}
</style>

@ -0,0 +1,47 @@
/*下拉刷新--标语*/
.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)}
}

@ -0,0 +1,39 @@
<!-- 下拉刷新区域 -->
<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>

@ -0,0 +1,330 @@
<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 MescrollEmpty from '../../mescroll-uni/components/mescroll-empty.vue';
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
import GlobalOption from './mescroll-uni-option.js';
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
export default {
mixins: [WxsMixin],
components: {
MescrollEmpty,
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, //
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);
}
}
};
MeScroll.extend(diyOption, GlobalOption); //
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption, true); // true,body
// 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;
}
}
};
</script>
<style>
@import "../../mescroll-uni/mescroll-body.css";
@import "../../mescroll-uni/components/mescroll-down.css";
@import "../../mescroll-uni/components/mescroll-up.css";
@import "./components/mescroll-down.css";
</style>

@ -0,0 +1,29 @@
// 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的配置参数也可以写,这里只展示了常用的配置:
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
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 )
tip: '~ 暂无相关数据 ~' // 提示
}
}
}
export default GlobalOption

@ -0,0 +1,406 @@
<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 MescrollEmpty from '../../mescroll-uni/components/mescroll-empty.vue';
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
import GlobalOption from './mescroll-uni-option.js';
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
export default {
mixins: [WxsMixin],
components: {
MescrollEmpty,
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, //
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
}
},
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(){
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()
}
}
}
MeScroll.extend(diyOption, GlobalOption); //
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption);
vm.mescroll.viewId = vm.viewId; // id
// 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;
}
},
mounted() {
//
this.setClientHeight()
}
}
</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>

@ -0,0 +1,44 @@
/*下拉刷新--上下箭头*/
.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);
}
}

@ -0,0 +1,53 @@
<!-- 下拉刷新区域 -->
<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>

@ -0,0 +1,32 @@
/*上拉加载--旋转进度条*/
.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);
}
}

@ -0,0 +1,40 @@
<!-- 上拉加载区域 -->
<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>

@ -0,0 +1,350 @@
<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 GlobalOption from './mescroll-uni-option.js';
import MescrollEmpty from '../../mescroll-uni/components/mescroll-empty.vue';
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
export default {
mixins: [WxsMixin],
components: {
MescrollEmpty,
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, //
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);
}
}
};
MeScroll.extend(diyOption, GlobalOption); //
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption, true); // true,body
// 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;
}
}
};
</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>

@ -0,0 +1,36 @@
// 全局配置
// mescroll-body 和 mescroll-uni 通用
const GlobalOption = {
down: {
// 其他down的配置参数也可以写,这里只展示了常用的配置:
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
textSuccess: '加载成功', // 加载成功的文本
textErr: '加载失败', // 加载失败的文本
beforeEndDelay: 0, // 延时结束的时长 (此处设置为0)
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
},
up: {
// 其他up的配置参数也可以写,这里只展示了常用的配置:
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
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 )
tip: '~ 空空如也 ~' // 提示
}
}
}
export default GlobalOption

@ -0,0 +1,430 @@
<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 GlobalOption from './mescroll-uni-option.js';
import MescrollEmpty from '../../mescroll-uni/components/mescroll-empty.vue';
import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
export default {
mixins: [WxsMixin],
components: {
MescrollEmpty,
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, //
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
}
},
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(){
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()
}
}
}
MeScroll.extend(diyOption, GlobalOption); //
let myOption = JSON.parse(JSON.stringify({
'down': vm.down,
'up': vm.up
})) // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption);
vm.mescroll.viewId = vm.viewId; // id
// 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;
}
},
mounted() {
//
this.setClientHeight()
}
}
</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>

@ -0,0 +1,55 @@
/* 下拉刷新区域 */
.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);
}
}

@ -0,0 +1,47 @@
<!-- 下拉刷新区域 -->
<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>

@ -0,0 +1,90 @@
<!--空布局
可作为独立的组件, 不使用mescroll的页面也能单独引入, 以便APP全局统一管理:
import MescrollEmpty from '@/components/mescroll-uni/components/mescroll-empty.vue';
<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
-->
<template>
<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
<view v-if="tip" class="empty-tip">{{ tip }}</view>
<view v-if="option.btnText" class="empty-btn" @click="emptyClick">{{ option.btnText }}</view>
</view>
</template>
<script>
//
import GlobalOption from './../mescroll-uni-option.js';
export default {
props: {
// empty: GlobalOption.up.empty
option: {
type: Object,
default() {
return {};
}
}
},
// 使computed,option
computed: {
//
icon() {
return this.option.icon == null ? GlobalOption.up.empty.icon : this.option.icon; // 使,
},
//
tip() {
return this.option.tip == null ? GlobalOption.up.empty.tip : this.option.tip; // 使,
}
},
methods: {
//
emptyClick() {
this.$emit('emptyclick');
}
}
};
</script>
<style>
/* 无任何数据的空布局 */
.mescroll-empty {
box-sizing: border-box;
width: 100%;
padding: 100rpx 50rpx;
text-align: center;
}
.mescroll-empty.empty-fixed {
z-index: 99;
position: absolute; /*transform会使fixed失效,最终会降级为absolute */
top: 100rpx;
left: 0;
}
.mescroll-empty .empty-icon {
width: 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>

@ -0,0 +1,83 @@
<!-- 回到顶部的按钮 -->
<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>

@ -0,0 +1,47 @@
/* 上拉加载区域 */
.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);
}
}

@ -0,0 +1,39 @@
<!-- 上拉加载区域 -->
<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>

@ -0,0 +1,19 @@
.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);
}
}

@ -0,0 +1,348 @@
<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="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
<!-- #endif -->
<!-- app, h5使用renderjs -->
<!-- #ifdef APP-PLUS || H5 -->
<script module="renderBiz" lang="renderjs">
import renderBiz from './wxs/renderjs.js';
export default {
mixins: [renderBiz]
}
</script>
<!-- #endif -->
<script>
// mescroll-uni.js,
import MeScroll from './mescroll-uni.js';
//
import GlobalOption from './mescroll-uni-option.js';
//
import MescrollEmpty from './components/mescroll-empty.vue';
//
import MescrollTop from './components/mescroll-top.vue';
// wxs(renderjs)mixins
import WxsMixin from './wxs/mixins.js';
export default {
mixins: [WxsMixin],
components: {
MescrollEmpty,
MescrollTop
},
data() {
return {
mescroll: {optDown:{},optUp:{}}, // mescroll
downHight: 0, //:
downRate: 0, // (inOffset: rate<1; outOffset: rate>=1)
downLoadType: 0, // : 0(loading), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
upLoadType: 0, // 0loading1loading2,END3,END
isShowEmpty: false, //
isShowToTop: false, //
windowHeight: 0, // 使
windowBottom: 0, // 使
statusBarHeight: 0 //
};
},
props: {
down: Object, //
up: Object, //
top: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
topbar: [Boolean, String], // top, false (使:,, ,,,)
bottom: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
safearea: Boolean, // bottom, false (iPhoneX使)
height: [String, Number], // mescroll,windowHeight,使
bottombar:{ // TabBar(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 '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);
}
}
};
MeScroll.extend(diyOption, GlobalOption); //
let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption, true); // true,body
// 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;
}
}
};
</script>
<style>
@import "./mescroll-body.css";
@import "./components/mescroll-down.css";
@import './components/mescroll-up.css';
</style>

@ -0,0 +1,65 @@
// mescroll-body 和 mescroll-uni 通用
// import MescrollUni from "./mescroll-uni.vue";
// import MescrollBody from "./mescroll-body.vue";
const MescrollMixin = {
// components: { // 非H5端无法通过mixin注册组件, 只能在main.js中注册全局组件或具体界面中注册
// MescrollUni,
// MescrollBody
// },
data() {
return {
mescroll: null //mescroll实例对象
}
},
// 注册系统自带的下拉刷新 (配置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;

@ -0,0 +1,36 @@
// 全局配置
// mescroll-body 和 mescroll-uni 通用
const GlobalOption = {
down: {
// 其他down的配置参数也可以写,这里只展示了常用的配置:
textInOffset: '下拉刷新', // 下拉的距离在offset范围内的提示文本
textOutOffset: '释放更新', // 下拉的距离大于offset范围的提示文本
textLoading: '加载中 ...', // 加载中的提示文本
textSuccess: '加载成功', // 加载成功的文本
textErr: '加载失败', // 加载失败的文本
beforeEndDelay: 100, // 延时结束的时长 (显示加载成功/失败的时长)
offset: 80, // 在列表顶部,下拉大于80px,松手即可触发下拉刷新的回调
native: false // 是否使用系统自带的下拉刷新; 默认false; 仅在mescroll-body生效 (值为true时,还需在pages配置enablePullDownRefresh:true;详请参考mescroll-native的案例)
},
up: {
// 其他up的配置参数也可以写,这里只展示了常用的配置:
textLoading: '加载中 ...', // 加载中的提示文本
textNoMore: '-- END --', // 没有更多数据的提示文本
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 )
tip: '~ 空空如也 ~' // 提示
}
}
}
export default GlobalOption

@ -0,0 +1,36 @@
.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);
}
}

@ -0,0 +1,799 @@
/* mescroll
* version 1.3.3
* 2020-09-15 wenju
* https://www.mescroll.com
*/
export default function MeScroll(options, isScrollBody) {
let me = this;
me.version = '1.3.3'; // 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: 100, // 延时结束的时长 (显示加载成功/失败的时长)
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()
}

@ -0,0 +1,424 @@
<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 MescrollEmpty from './components/mescroll-empty.vue';
//
import MescrollTop from './components/mescroll-top.vue';
// wxs(renderjs)mixins
import WxsMixin from './wxs/mixins.js';
export default {
mixins: [WxsMixin],
components: {
MescrollEmpty,
MescrollTop
},
data() {
return {
mescroll: {optDown:{},optUp:{}}, // mescroll
viewId: 'id_' + Math.random().toString(36).substr(2,16), // 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 //
}
},
props: {
down: Object, //
up: Object, //
top: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
topbar: [Boolean, String], // top, false (使:,, ,,,)
bottom: [String, Number], // (20, "20rpx", "20px", "20%", rpx, windowHeight)
safearea: Boolean, // bottom, false (iPhoneX使)
fixed: { // fixedmescroll, true
type: Boolean,
default: true
},
height: [String, Number], // mescroll, ,使fixed. (20, "20rpx", "20px", "20%", rpx, windowHeight)
bottombar:{ // TabBar(H5tab)
type: Boolean,
default: true
}
},
computed: {
// 使fixed (height,使)
isFixed(){
return !this.height && this.fixed
},
// mescroll
scrollHeight(){
if (this.isFixed) {
return "auto"
} else if(this.height){
return this.toPx(this.height) + 'px'
}else{
return "100%"
}
},
// (px)
numTop() {
return this.toPx(this.top)
},
fixedTop() {
return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
},
padTop() {
return !this.isFixed ? this.numTop + 'px' : 0
},
// (px)
numBottom() {
return this.toPx(this.bottom)
},
fixedBottom() {
return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
},
padBottom() {
return !this.isFixed ? this.numBottom + 'px' : 0
},
//
isDownReset(){
return this.downLoadType===3 || this.downLoadType===4
},
//
transition() {
return this.isDownReset ? 'transform 300ms' : '';
},
translateY() {
return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform使fixed,fixedmescroll
},
//
scrollable(){
return this.downLoadType===0 || this.isDownReset
},
//
isDownLoading(){
return this.downLoadType === 3
},
//
downRotate(){
return 'rotate(' + 360 * this.downRate + 'deg)'
},
//
downText(){
if(!this.mescroll) return ""; //
switch (this.downLoadType){
case 1: return this.mescroll.optDown.textInOffset;
case 2: return this.mescroll.optDown.textOutOffset;
case 3: return this.mescroll.optDown.textLoading;
case 4: return this.mescroll.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()
}
}
}
MeScroll.extend(diyOption, GlobalOption); //
let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // ,props
MeScroll.extend(myOption, diyOption); //
// MeScroll
vm.mescroll = new MeScroll(myOption);
vm.mescroll.viewId = vm.viewId; // id
// 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;
}
},
mounted() {
//
this.setClientHeight()
}
}
</script>
<style>
@import "./mescroll-uni.css";
@import "./components/mescroll-down.css";
@import './components/mescroll-up.css';
</style>

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

@ -0,0 +1,59 @@
/**
* 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.isInit = true; // 标记为true
this.mescroll && this.mescroll.triggerDownScroll();
}
}
},
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.isInit = true; // 标记为true
this.mescroll.triggerDownScroll();
}
},
}
}
export default MescrollMoreItemMixin;

@ -0,0 +1,74 @@
/**
* mescroll-body写在子组件时, 需通过mescroll的mixins补充子组件缺少的生命周期
*/
const MescrollMoreMixin = {
data() {
return {
tabIndex: 0, // 当前tab下标
mescroll: {
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;

@ -0,0 +1,109 @@
// 定义在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;

@ -0,0 +1,92 @@
// 使用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;

@ -0,0 +1,268 @@
// 使用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
}

@ -0,0 +1,310 @@
<template>
<view class="content">
<view class="mix-tree-list">
<block v-for="(item, index) in treeList" :key="index">
<view
class="mix-tree-item flex-between"
:style="[{
paddingLeft: item.rank*15 + 'px',
zIndex: item.rank*-1 +50
}]"
:class="{
border: treeParams.border === true,
show: item.show,
last: item.lastRank,
showchild: item.showChild
}"
@click.stop="treeItemTap(item, index)"
>
<view class="">
<image class="mix-tree-icon" :src="item.lastRank ? treeParams.lastIcon : item.showChild ? treeParams.currentIcon : treeParams.defaultIcon"></image>
{{item.name}}
</view>
<view v-if="item.account" @click="changeRund(item)" class="">
<image class="img-round" :src="item.checked?checkedRund:uncheckRund" mode=""></image>
<!-- <image class="img-round" src="" mode=""></image> -->
</view>
</view>
</block>
</view>
</view>
</template>
<script>
export default {
props: {
list: { //
type: Array,
default(){
return [];
}
},
params: {
type: Object,
default(){
return {}
}
},
fliter:{//
type:String,
},
multiple:{//
type:Boolean,
default(){
return false
}
},
limit:{//
type:Number,
default(){// 99
return 99
}
},
unChecked:{//
type:String,
default(){
return ''
}
}
},
data() {
return {
treeList: [],
treeParams: {
defaultIcon: '../../static/img/defaultIcon.png',
currentIcon: '../../static/img/currentIcon.png',
lastIcon: '',
border: false
},
bSearch:'',
aSearch:'',
colonData:'',
checkedRund:'../../static/img/btn_pre.png',//
uncheckRund:'../../static/img/btn_per_un.png',//
idsList:[],// ids
cloneData:[],//
nameList:[],//
}
},
watch: {
list(list){
// console.log('')
this.treeList = []// list
this.treeParams = Object.assign(this.treeParams, this.params);
this.renderTreeList(list);
this.$forceUpdate()
},
fliter(val,old){
if(!val){//
// ids
this.cloneData.forEach(e=>{
if(this.idsList.includes(e.id)){
e.checked = true
}
})
this.treeList = this.cloneData
}else{//
this.cloneData.forEach(e=>{
if(this.idsList.includes(e.id)){
e.checked = true
}
})
this.treeList = this.cloneData
let arrData = JSON.parse(JSON.stringify(this.treeList))
this.treeList = []
arrData.forEach(e=>{//
if (e.name.includes(val)){
e.show = true
e.showChild = true
this.treeList.push(e)
}
})
this.$forceUpdate()
// this.treeList = [...new Set(this.treeList)]
}
},
//
//
unChecked(val){
if(!val||val=='') return
this.changeChecked(val)
}
},
methods: {
// --
changeChecked(name){
this.treeList.forEach(e=>{
if(e.name == name){
e.checked = false
// id
if(this.nameList.indexOf(name)!==-1){
this.nameList.splice(this.nameList.indexOf(name),1)//
}
if(this.idsList.indexOf(e.id)!==-1){
this.idsList.splice(this.idsList.indexOf(e.id),1)
}
}
})
this.$emit('checkedRund',this.nameList,this.idsList)//
this.$emit('delstr','')//
},
//
renderTreeList(list=[], rank=0, parentId=[]){
// itempush
// itemchildren
list.forEach(item=>{
this.treeList.push({
id: item.id,
name: item.name,
parentId, // id
rank, //
showChild: false, //
show: rank === 0 ,//
//
account: item.account?item.account:false,//
// ,
checked:this.checkedIDS?this.checkedIDS.map(e=>{if(e===item.id){return true }else{return false}}):false
})
if(Array.isArray(item.children) && item.children.length > 0){
let parents = [...parentId];
parents.push(item.id);
this.renderTreeList(item.children, rank+1, parents);
}else{
this.treeList[this.treeList.length-1].lastRank = true;
}
})
this.cloneData = JSON.parse(JSON.stringify(this.treeList))//
},
//
treeItemTap(item){
let list = this.treeList;
let id = item.id;
if(item.lastRank === true){
//
this.$emit('treeItemClick', item);
return;
}
item.showChild = !item.showChild;
list.forEach(childItem=>{
if(item.showChild === false){
//
if(!childItem.parentId.includes(id)){
return;
}
if(childItem.lastRank !== true){
childItem.showChild = false;
}
childItem.show = false;
}else{
if(childItem.parentId[childItem.parentId.length-1] === id){
childItem.show = true;
}
}
})
},
//
changeRund(item){
if(this.multiple){//
//
if(this.idsList.includes(item.id)){// id
item.checked = false
//
this.idsList.splice(this.idsList.indexOf(item.id),1)
this.nameList.splice(this.nameList.indexOf(item.name),1)
}else{
if(this.limit){//
if( this.idsList.length>=this.limit){
this.idsList.splice(this.limit,1)
this.nameList.splice(this.limit,1)
return uni.showToast({
title:`请不要选择超过${this.limit }个!`,
icon:'none'
})
}
}
item.checked = !item.checked
if(item.checked){// push
this.idsList.push(item.id)
this.nameList.push(item.name)
}else{//
this.idsList.splice(this.idsList.indexOf(item.id),1)
this.nameList.splice(this.nameList.indexOf(item.name),1)
}
}
//
}else{//
item.checked = !item.checked//
if(item.checked){// push
this.idsList.push(item.id)
this.nameList.push(item.name)
}else{//
this.idsList.splice(this.idsList.indexOf(item.id),1)
this.nameList.splice(this.nameList.indexOf(item.name),1)
}
// console.log('',this.idsList,this.treeList)
if(this.idsList.length>1){
this.treeList.forEach(e=>{
if(e.id === this.idsList[0]){
e.checked = false// false
}
})
this.nameList.splice(0,1)//
this.idsList.splice(0,1)//
// this.$emit('checkedRund',item,this.idsList)//
}
}
this.$emit('checkedRund',this.nameList,this.idsList)//
return
},
}
}
</script>
<style>
.mix-tree-list{
display: flex;
flex-direction: column;
padding-left: 30upx;
}
.mix-tree-item{
display: flex;
align-items: center;
font-size: 30upx;
color: #333;
height: 0;
opacity: 0;
transition: .2s;
position: relative;
}
.mix-tree-item.border{
border-bottom: 2rpx solid #eee;
}
.mix-tree-item.show{
height: 80upx;
opacity: 1;
}
.mix-tree-icon{
width: 26upx;
height: 26upx;
margin-right: 8upx;
opacity: .9;
}
.mix-tree-item.showchild:before{
transform: rotate(90deg);
}
.mix-tree-item.last:before{
opacity: 0;
}
.img-round{
width: 25rpx;
height: 25rpx;
margin-right: 100rpx;
}
</style>

@ -0,0 +1,324 @@
<template>
<view class="select-container" v-show="show" @touchmove.stop.prevent>
<view class="mask" :class="activeClass ? 'mask-show' : ''" @tap="onCancel(true)"></view>
<view class="select-box" :class="activeClass ? 'select-box-show' : ''">
<view class="header">
<text class="cancel" @tap="onCancel">{{cancelText}}</text>
<view class="all" @tap="onAllToggle" v-if="allShow">
<text :class="isAll ? 'all-active' : ''">全选</text>
</view>
<text class="confirm" @tap="onConfirm">{{confirmText}}</text>
</view>
<view class="body-warp">
<scroll-view class="body" scroll-y="true">
<slot v-if="!data.length" name="tips">
<view class="empty-tips">暂无数据~</view>
</slot>
<view
class="select-item"
:class="[item.disabled ? 'disabled' : '',selectedArr[index] ? 'selected' : '']"
v-for="(item,index) in data"
:key="item[valueName]"
@tap="onSelected(index)"
>
<view class="label">{{item[labelName]}}</view>
<text v-show="selectedArr[index]" class="selected-icon"></text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<!-- 多选组件 -->
<script>
export default {
model: {
prop: "value",
event: ["input"]
},
data() {
return {
show: false, //
activeClass: false, //
selectedArr: [], //
selectedArrOld: [] //
};
},
onShow() {
this.show = this.value;
},
computed: {
//
isAll() {
let wipeDisabledList = this.returnWipeDisabledList();
if (!wipeDisabledList.length) return false;
return !wipeDisabledList.includes(false);
}
},
props: {
// --
value: {
type: Boolean,
default: false
},
//
cancelText: {
type: String,
default: "取消"
},
//
confirmText: {
type: String,
default: "确认"
},
// labelkey
labelName: {
type: String,
default: "label"
},
// valuekey
valueName: {
type: String,
default: "value"
},
//
maskCloseAble: {
type: Boolean,
default: true
},
//
allShow: {
type: Boolean,
default: true
},
//
mode: {
type: String,
default: "multiple"
},
//
defaultSelected: {
type: Array,
default: function() {
return [];
}
},
//
data: {
type: Array,
required: true,
default: () => {
return [];
}
}
},
watch: {
async value(newVal) {
this.show = newVal;
await this.$nextTick();
this.activeClass = newVal;
if (newVal) {
this.selectedArrOld = JSON.parse(JSON.stringify(this.selectedArr));
}
},
show(newVal) {
this.$emit("input", newVal);
this.$emit("change", newVal);
},
data: {
//
handler(list) {
this.selectedArr = list.map(el => false);
this.setItemActiveState();
},
deep: true,
immediate: true
},
defaultSelected: {
handler() {
this.setItemActiveState();
},
deep: true,
immediate: true
}
},
methods: {
//
setItemActiveState() {
if (this.data.length && this.defaultSelected.length) {
this.data.forEach((item, i) => {
for (let n = 0; n < this.defaultSelected.length; n++) {
if (
!item.disabled &&
item[this.valueName] === this.defaultSelected[n]
) {
this.selectedArr.splice(i, 1, true);
break;
}
}
});
}
},
/**
* 选择事件
* @index {Number} 点击下标
*/
onSelected(index) {
if (this.data[index].disabled) return;
let index2Active = this.selectedArr[index];
this.selectedArr.splice(index, 1, !index2Active);
},
//
onCancel(isMask) {
if (!isMask || this.maskCloseAble) {
this.show = false;
this.selectedArr = JSON.parse(JSON.stringify(this.selectedArrOld));
} else {
return;
}
this.$emit("cancel");
},
// disabled
returnWipeDisabledList() {
let arr = [];
this.selectedArr.forEach((el, index) => {
if (!this.data[index].disabled) arr.push(el);
});
return arr;
},
// /
onAllToggle() {
let wipeDisabledList = this.returnWipeDisabledList();
// disabledfalse
if (wipeDisabledList.includes(false)) {
this.selectedArr.forEach((el, index) => {
if (!this.data[index].disabled)
this.selectedArr.splice(index, 1, true);
});
} else {
this.selectedArr.forEach((el, index) => {
if (!this.data[index].disabled)
el = this.selectedArr.splice(index, 1, false);
});
}
},
//
onConfirm() {
this.show = false;
let selectedData = [];
this.selectedArr.forEach((el, index) => {
if (el) {
selectedData.push(this.data[index]);
}
});
if (this.mode === "multiple") {
this.$emit("confirm", selectedData);
} else {
let backData = selectedData[0] || {};
this.$emit("confirm", backData);
}
}
}
};
</script>
<style lang="scss" scoped>
.select-container {
width: 100vw;
height: 100vh;
position: fixed;
left: 0;
top: 0;
z-index: 999;
$paddingLR: 18rpx;
.mask {
width: 100%;
height: 100%;
background-color: $uni-bg-color-mask;
opacity: 0;
transition: opacity 0.3s;
&.mask-show {
opacity: 1;
}
}
//
.select-box {
width: 100%;
position: absolute;
bottom: 0;
left: 0;
transform: translate3d(0px, 100%, 0px);
background-color: $uni-bg-color;
transition: all 0.3s;
&.select-box-show {
transform: translateZ(0);
}
.header {
display: flex;
box-sizing: border-box;
width: 100%;
justify-content: space-between;
border-bottom: 1px solid $uni-border-color;
line-height: 76rpx;
font-size: 30rpx;
padding: 0 $paddingLR;
.cancel {
color: $uni-text-color-grey;
}
.all {
color: $uni-color-success;
.all-active {
&::after {
display: inline-block;
content: "✔";
padding-left: 8rpx;
}
}
}
.confirm {
color: $uni-color-primary;
}
}
.body-warp {
width: 100%;
height: 30vh;
box-sizing: border-box;
padding: 20rpx $paddingLR;
}
.body {
width: 100%;
height: 100%;
overflow-y: auto;
.empty-tips {
margin-top: 25%;
text-align: center;
font-size: 26rpx;
color: $uni-color-error;
}
.select-item {
display: flex;
font-size: 26rpx;
line-height: 58rpx;
color: #303133;
position: relative;
transition: all 0.3s;
&.selected {
color: $uni-color-primary;
}
&.disabled {
color: $uni-text-color-disable;
}
> .label {
flex: 1;
text-align: center;
}
> .selected-icon {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
}
}
}
}
}
</style>

@ -0,0 +1,824 @@
<template>
<view v-if="isShow" class="picker">
<!-- 日期选择器 -->
<view v-if="type!='time'" class="picker-modal">
<view class="picker-modal-header">
<view class="picker-icon picker-icon-zuozuo" :hover-stay-time="100" hover-class="picker-icon-active" @click="onSetYear('-1')"></view>
<view class="picker-icon picker-icon-zuo" :hover-stay-time="100" hover-class="picker-icon-active" @click="onSetMonth('-1')"></view>
<text class="picker-modal-header-title">{{title}}</text>
<view class="picker-icon picker-icon-you" :hover-stay-time="100" hover-class="picker-icon-active" @click="onSetMonth('+1')"></view>
<view class="picker-icon picker-icon-youyou" :hover-stay-time="100" hover-class="picker-icon-active" @click="onSetYear('+1')"></view>
</view>
<swiper class="picker-modal-body" :circular="true" :duration="200" :skip-hidden-item-layout="true" :current="calendarIndex" @change="onSwiperChange">
<swiper-item class="picker-calendar" v-for="(calendar,calendarIndex2) in calendars" :key="calendarIndex2">
<view class="picker-calendar-view" v-for="(week,index) in weeks" :key="index - 7">
<view class="picker-calendar-view-item">{{week}}</view>
</view>
<view class="picker-calendar-view" v-for="(date,dateIndex) in calendar" :key="dateIndex" @click="onSelectDate(date)">
<!-- 背景样式 -->
<view v-show="date.bgStyle.type" :class="'picker-calendar-view-'+date.bgStyle.type" :style="{background: date.bgStyle.background}"></view>
<!-- 正常和选中样式 -->
<view class="picker-calendar-view-item" :style="{opacity: date.statusStyle.opacity, color: date.statusStyle.color, background: date.statusStyle.background}">
<text>{{date.title}}</text>
</view>
<!-- 小圆点样式 -->
<view class="picker-calendar-view-dot" :style="{opacity: date.dotStyle.opacity, background: date.dotStyle.background}"></view>
<!-- 信息样式 -->
<view v-show="date.tips" class="picker-calendar-view-tips">{{date.tips}}</view>
</view>
</swiper-item>
</swiper>
<view class="picker-modal-footer">
<view class="picker-modal-footer-info">
<block v-if="isMultiSelect">
<view class="picker-display">
<text>{{beginText}}日期</text>
<text class="picker-display-text">{{BeginTitle}}</text>
<view v-if="isContainTime" class="picker-display-link" :hover-stay-time="100" hover-class="picker-display-link-active"
:style="{color}" @click="onShowTimePicker('begin')">{{BeginTimeTitle}}</view>
</view>
<view class="picker-display">
<text>{{endText}}日期</text>
<text class="picker-display-text">{{EndTitle}}</text>
<view v-if="isContainTime" class="picker-display-link" :hover-stay-time="100" hover-class="picker-display-link-active"
:style="{color}" @click="onShowTimePicker('end')">{{EndTimeTitle}}</view>
</view>
</block>
<block v-else>
<view class="picker-display">
<text>当前选择</text>
<text class="picker-display-text">{{BeginTitle}}</text>
<view v-if="isContainTime" class="picker-display-link" :hover-stay-time="100" hover-class="picker-display-link-active"
:style="{color}" @click="onShowTimePicker('begin')">{{BeginTimeTitle}}</view>
</view>
</block>
</view>
<view class="picker-modal-footer-btn">
<view class="picker-btn" :hover-stay-time="100" hover-class="picker-btn-active" @click="onReset">重置</view>
<view class="picker-btn" :hover-stay-time="100" hover-class="picker-btn-active" @click="onCancel">取消</view>
<view class="picker-btn" :style="{color}" :hover-stay-time="100" hover-class="picker-btn-active" @click="onConfirm">确定</view>
</view>
</view>
</view>
<!-- 时间选择器 -->
<view v-if="showTimePicker" class="picker">
<view class="picker-modal picker-time">
<view class="picker-modal-header">
<text class="picker-modal-header-title">选择日期</text>
</view>
<picker-view class="picker-modal-time" indicator-class="picker-modal-time-item" :value="timeValue" @change="onTimeChange">
<picker-view-column>
<view v-for="(v,i) in 24" :key="i">{{i<10?'0'+i:i}}</view>
</picker-view-column>
<picker-view-column>
<view v-for="(v,i) in 60" :key="i">{{i<10?'0'+i:i}}</view>
</picker-view-column>
<picker-view-column v-if="showSeconds">
<view v-for="(v,i) in 60" :key="i">{{i<10?'0'+i:i}}</view>
</picker-view-column>
</picker-view>
<view class="picker-modal-footer">
<view class="picker-modal-footer-info">
<view class="picker-display">
<text>当前选择</text>
<text class="picker-display-text">{{PickerTimeTitle}}</text>
</view>
</view>
<view class="picker-modal-footer-btn">
<view class="picker-btn" :hover-stay-time="100" hover-class="picker-btn-active" @click="onCancelTime">取消</view>
<view class="picker-btn" :style="{color}" :hover-stay-time="100" hover-class="picker-btn-active" @click="onConfirmTime">确定</view>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
/**
* 工具函数库
*/
const DateTools = {
/**
* 获取公历节日
* @param date Date对象
*/
getHoliday(date) {
let holidays = {
'0101': '元旦',
'0214': '情人',
'0308': '妇女',
'0312': '植树',
'0401': '愚人',
'0501': '劳动',
'0504': '青年',
'0601': '儿童',
'0701': '建党',
'0801': '建军',
'0903': '抗日',
'0910': '教师',
'1001': '国庆',
'1031': '万圣',
'1224': '平安',
'1225': '圣诞'
};
let value = this.format(date, 'mmdd');
if (holidays[value]) return holidays[value];
return false;
},
/**
* 解析标准日期格式
* @param s 日期字符串
* @return 返回Date对象
*/
parse: s => new Date(s.replace(/(年|月|-)/g, '/').replace(/(日)/g, '')),
/**
* 比较日期是否为同一天
* @param a Date对象
* @param b Date对象
* @return Boolean
*/
isSameDay: (a, b) => a.getMonth() == b.getMonth() && a.getFullYear() == b.getFullYear() && a.getDate() == b.getDate(),
/**
* 格式化Date对象
* @param d 日期对象
* @param f 格式字符串
* @return 返回格式化后的字符串
*/
format(d, f) {
var o = {
"m+": d.getMonth() + 1,
"d+": d.getDate(),
"h+": d.getHours(),
"i+": d.getMinutes(),
"s+": d.getSeconds(),
"q+": Math.floor((d.getMonth() + 3) / 3),
};
if (/(y+)/.test(f))
f = f.replace(RegExp.$1, (d.getFullYear() + "").substr(4 - RegExp.$1.length));
for (var k in o)
if (new RegExp("(" + k + ")").test(f))
f = f.replace(RegExp.$1, (RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
return f;
},
/**
* 用于format格式化后的反解析
* @param s 日期字符串
* @param f 格式字符串
* @return 返回Date对象
*/
inverse(s, f) {
var o = {
"y": '',
"m": '',
"d": '',
"h": '',
"i": '',
"s": '',
};
let d = new Date();
if (s.length != f.length) return d;
for (let i in f)
if (o[f[i]] != undefined) o[f[i]] += s[i];
if (o.y) d.setFullYear(o.y.length < 4 ? (d.getFullYear() + '').substr(0, 4 - o.y.length) + o.y : o.y);
o.m && d.setMonth(o.m - 1, 1);
o.d && d.setDate(o.d - 0);
o.h && d.setHours(o.h - 0);
o.i && d.setMinutes(o.i - 0);
o.s && d.setSeconds(o.s - 0);
return d;
},
/**
* 获取日历数组42
* @param date 日期对象或日期字符串
* @param proc 处理日历(和forEach类似)传递一个数组中的item
* @return Array
*/
getCalendar(date, proc) {
let it = new Date(date),
calendars = [];
it.setDate(1);
it.setDate(it.getDate() - ((it.getDay() == 0 ? 7 : it.getDay()) - 1)); //
for (let i = 0; i < 42; i++) {
let tmp = {
dateObj: new Date(it),
title: it.getDate(),
isOtherMonth: it.getMonth() < date.getMonth() || it.getMonth() > date.getMonth()
};
calendars.push(Object.assign(tmp, proc ? proc(tmp) : {}));
it.setDate(it.getDate() + 1);
}
return calendars;
},
/**
* 获取日期到指定的月份1号(不改变原来的date对象)
* @param d Date对象
* @param v 指定的月份
* @return Date对象
*/
getDateToMonth(d, v) {
let n = new Date(d);
n.setMonth(v, 1);
return n;
},
/**
* 把时间数组转为时间字符串
* @param t Array[,,]
* @param showSecinds 是否显示秒
* @return 字符串 :[:]
*/
formatTimeArray(t, s) {
let r = [...t];
if (!s) r.length = 2;
r.forEach((v, k) => r[k] = ('0' + v).slice(-2));
return r.join(':');
}
};
export default {
props: {
//
color: {
type: String,
default: '#409eff'
},
// typedatetimetime
showSeconds: {
type: Boolean,
default: false
},
//
value: [String, Array],
//date time datetime range rangetime
type: {
type: String,
default: 'range'
},
//
show: {
type: Boolean,
default: false
},
//
format: {
type: String,
default: ''
},
//
showHoliday: {
type: Boolean,
default: true
},
//
showTips: {
type: Boolean,
default: false
},
// type
beginText: {
type: String,
default: '开始'
},
// type
endText: {
type: String,
default: '结束'
}
},
data() {
return {
isShow: false, //
isMultiSelect: false, //
isContainTime: false, //
date: {}, //
weeks: ["一", "二", "三", "四", "五", "六", "日"],
title: '初始化', //
calendars: [[],[],[]], //
calendarIndex: 1, //
checkeds: [], //
showTimePicker: false, //
timeValue: [0, 0, 0], //
timeType: 'begin', //
beginTime: [0, 0, 0], //
endTime: [0, 0, 0], //
};
},
methods: {
//
setValue(value) {
this.date = new Date();
this.checkeds = [];
this.isMultiSelect = this.type.indexOf('range') >= 0;
this.isContainTime = this.type.indexOf('time') >= 0;
//Date
let parseDateStr = (str) => (this.format ? DateTools.inverse(str, this.format) : DateTools.parse(str));
if (value) {
if (this.isMultiSelect) {
Array.isArray(value) && value.forEach((dateStr, index) => {
let date = parseDateStr(dateStr);
let time = [date.getHours(), date.getMinutes(), date.getSeconds()];
if (index == 0) this.beginTime = time;
else this.endTime = time;
this.checkeds.push(date);
});
} else {
if (this.type == 'time') {
let date = parseDateStr('2019/1/1 ' + value);
this.beginTime = [date.getHours(), date.getMinutes(), date.getSeconds()];
this.onShowTimePicker('begin');
} else {
this.checkeds.push(parseDateStr(value));
if (this.isContainTime) this.beginTime = [
this.checkeds[0].getHours(),
this.checkeds[0].getMinutes(),
this.checkeds[0].getSeconds()
];
}
}
if (this.checkeds.length) this.date = new Date(this.checkeds[0]);
} else {
if (this.isContainTime) {
this.beginTime = [this.date.getHours(), this.date.getMinutes(), this.date.getSeconds()];
if (this.isMultiSelect) this.endTime = [...this.beginTime];
}
this.checkeds.push(new Date(this.date));
}
if (this.type != 'time') this.refreshCalendars(true);
else this.onShowTimePicker('begin');
},
//
onSetYear(value) {
this.date.setFullYear(this.date.getFullYear() + parseInt(value));
this.refreshCalendars(true);
},
//
onSetMonth(value) {
this.date.setMonth(this.date.getMonth() + parseInt(value));
this.refreshCalendars(true);
},
//
onTimeChange(e) {
this.timeValue = e.detail.value;
},
//
onShowTimePicker(type) {
this.showTimePicker = true;
this.timeType = type;
this.timeValue = type == 'begin' ? [...this.beginTime] : [...this.endTime];
},
//
procCalendar(item) {
//
item.statusStyle = {
opacity: 1,
color: item.isOtherMonth ? '#ddd' : '#000',
background: 'transparent'
};
item.bgStyle = {
type: '',
background: 'transparent'
};
item.dotStyle = {
opacity: 1,
background: 'transparent'
};
item.tips = "";
//
if (DateTools.isSameDay(new Date(), item.dateObj)) {
item.statusStyle.color = this.color;
if (item.isOtherMonth) item.statusStyle.opacity = 0.3;
}
//
this.checkeds.forEach(date => {
if (DateTools.isSameDay(date, item.dateObj)) {
item.statusStyle.background = this.color;
item.statusStyle.color = '#fff';
item.statusStyle.opacity = 1;
if (this.isMultiSelect && this.showTips) item.tips = this.beginText;
}
});
//
if (item.statusStyle.background != this.color) {
let holiday = this.showHoliday ? DateTools.getHoliday(item.dateObj) : false;
if (holiday || DateTools.isSameDay(new Date(), item.dateObj)) {
item.title = holiday || item.title;
item.dotStyle.background = this.color;
if (item.isOtherMonth) item.dotStyle.opacity = 0.2;
}
} else {
item.title = item.dateObj.getDate();
}
//
if (this.checkeds.length == 2) {
if (DateTools.isSameDay(this.checkeds[0], item.dateObj)) { //
item.bgStyle.type = 'bgbegin';
}
if (DateTools.isSameDay(this.checkeds[1], item.dateObj)) { //
if (this.isMultiSelect && this.showTips) item.tips = item.bgStyle.type ? this.beginText + ' / ' + this.endText : this.endText;
if (!item.bgStyle.type) { //
item.bgStyle.type = 'bgend';
} else {
item.bgStyle.type = '';
}
}
if (!item.bgStyle.type && (+item.dateObj > +this.checkeds[0] && +item.dateObj < +this.checkeds[1])) { //
item.bgStyle.type = 'bg';
item.statusStyle.color = this.color;
}
if (item.bgStyle.type) {
item.bgStyle.background = this.color;
item.dotStyle.opacity = 1;
item.statusStyle.opacity = 1;
}
}
},
//
refreshCalendars(refresh = false) {
let date = new Date(this.date);
let before = DateTools.getDateToMonth(date, date.getMonth() - 1);
let after = DateTools.getDateToMonth(date, date.getMonth() + 1);
if (this.calendarIndex == 0) {
if(refresh) this.calendars.splice(0, 1, DateTools.getCalendar(date, this.procCalendar));
this.calendars.splice(1, 1, DateTools.getCalendar(after, this.procCalendar));
this.calendars.splice(2, 1, DateTools.getCalendar(before, this.procCalendar));
} else if (this.calendarIndex == 1) {
this.calendars.splice(0, 1, DateTools.getCalendar(before, this.procCalendar));
if(refresh) this.calendars.splice(1, 1, DateTools.getCalendar(date, this.procCalendar));
this.calendars.splice(2, 1, DateTools.getCalendar(after, this.procCalendar));
} else if (this.calendarIndex == 2) {
this.calendars.splice(0, 1, DateTools.getCalendar(after, this.procCalendar));
this.calendars.splice(1, 1, DateTools.getCalendar(before, this.procCalendar));
if(refresh) this.calendars.splice(2, 1, DateTools.getCalendar(date, this.procCalendar));
}
this.title = DateTools.format(this.date, 'yyyy年mm月');
},
//
onSwiperChange(e) {
this.calendarIndex = e.detail.current;
let calendar = this.calendars[this.calendarIndex];
this.date = new Date(calendar[22].dateObj); //
this.refreshCalendars();
},
//
onSelectDate(date) {
if (~this.type.indexOf('range') && this.checkeds.length == 2) this.checkeds = [];
else if (!(~this.type.indexOf('range')) && this.checkeds.length) this.checkeds = [];
this.checkeds.push(new Date(date.dateObj));
this.checkeds.sort((a, b) => a - b); //
this.calendars.forEach(calendar => {
calendar.forEach(this.procCalendar); //
});
},
//
onCancelTime() {
this.showTimePicker = false;
this.type == 'time' && this.onCancel();
},
//
onConfirmTime() {
if (this.timeType == 'begin') this.beginTime = this.timeValue;
else this.endTime = this.timeValue;
this.showTimePicker = false;
this.type == 'time' && this.onConfirm();
},
//
onCancel() {
this.$emit('cancel', false);
},
//
onReset() {
this.$emit('reset', false);
},
//
onConfirm() {
let result = {
value: null,
date: null
};
//
let defaultFormat = {
'date': 'yyyy/mm/dd',
'time': 'hh:ii' + (this.showSeconds ? ':ss' : ''),
'datetime': ''
};
defaultFormat['datetime'] = defaultFormat.date + ' ' + defaultFormat.time;
let fillTime = (date, timeArr) => {
date.setHours(timeArr[0], timeArr[1]);
if (this.showSeconds) date.setSeconds(timeArr[2]);
};
if (this.type == 'time') {
let date = new Date();
fillTime(date, this.beginTime);
result.value = DateTools.format(date, this.format ? this.format : defaultFormat.time);
result.date = date;
} else {
if (this.isMultiSelect) {
let values = [],
dates = [];
if (this.checkeds.length < 2) return uni.showToast({
icon: 'none',
title: '请选择两个日期'
});
this.checkeds.forEach((date, index) => {
let newDate = new Date(date);
if (this.isContainTime) {
let time = [this.beginTime, this.endTime];
fillTime(newDate, time[index]);
}
values.push(DateTools.format(newDate, this.format ? this.format : defaultFormat[this.isContainTime ?
'datetime' : 'date']));
dates.push(newDate);
});
result.value = values;
result.date = dates;
} else {
let newDate = new Date(this.checkeds[0]);
if (this.isContainTime) {
newDate.setHours(this.beginTime[0], this.beginTime[1]);
if (this.showSeconds) newDate.setSeconds(this.beginTime[2]);
}
result.value = DateTools.format(newDate, this.format ? this.format : defaultFormat[this.isContainTime ?
'datetime' : 'date']);
result.date = newDate;
}
}
this.$emit('confirm', result);
}
},
computed: {
BeginTitle() {
let value = '未选择';
if (this.checkeds.length) value = DateTools.format(this.checkeds[0], 'yy/mm/dd');
return value;
},
EndTitle() {
let value = '未选择';
if (this.checkeds.length == 2) value = DateTools.format(this.checkeds[1], 'yy/mm/dd');
return value;
},
PickerTimeTitle() {
return DateTools.formatTimeArray(this.timeValue, this.showSeconds);
},
BeginTimeTitle() {
return this.BeginTitle != '未选择' ? DateTools.formatTimeArray(this.beginTime, this.showSeconds) : '';
},
EndTimeTitle() {
return this.EndTitle != '未选择' ? DateTools.formatTimeArray(this.endTime, this.showSeconds) : '';
}
},
watch: {
show(newValue, oldValue) {
newValue && this.setValue(this.value);
this.isShow = newValue;
},
value(newValue, oldValue) {
setTimeout(()=>{
this.setValue(newValue);
}, 0);
}
}
}
</script>
<style lang="scss" scoped>
$z-index: 100;
$cell-spacing: 20upx;
$calendar-size: 630upx;
$calendar-item-size: 90upx;
.picker {
position: fixed;
z-index: $z-index;
background: rgba(255, 255, 255, 0);
left: 0;
top: 0;
width: 100%;
height: 100%;
font-size: 28upx;
&-btn {
padding: $cell-spacing*0.5 $cell-spacing;
border-radius: 12upx;
color: #666;
&-active {
background: rgba(0, 0, 0, .1);
}
}
&-display {
color: #666;
&-text {
color: #000;
margin: 0 $cell-spacing*0.5;
}
&-link {
display: inline-block;
&-active {
background: rgba(0, 0, 0, .1);
}
}
}
&-time {
width: $calendar-size - 80upx !important;
left: ((750upx - $calendar-size) / 2 + 40upx) !important;
}
&-modal {
background: #fff;
position: absolute;
top: 50%;
left: (750upx - $calendar-size) / 2;
width: $calendar-size;
transform: translateY(-50%);
box-shadow: 0 0 20px 0 rgba(0, 0, 0, 0.1);
border-radius: 12upx;
&-header {
text-align: center;
line-height: 80upx;
font-size: 32upx;
&-title {
display: inline-block;
width: 40%;
}
.picker-icon {
display: inline-block;
line-height: 50upx;
width: 50upx;
height: 50upx;
border-radius: 50upx;
text-align: center;
margin: 10upx;
background: #fff;
font-size: 36upx;
&-active {
background: rgba(0, 0, 0, .1);
}
}
}
&-body {
width: $calendar-size !important;
height: $calendar-size !important;
position: relative;
}
&-time {
width: 100%;
height: 180upx;
text-align: center;
line-height: 60upx;
}
&-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: $cell-spacing;
&-info {
flex-grow: 1;
}
&-btn {
flex-shrink: 0;
display: flex;
}
}
}
&-calendar {
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
flex-wrap: wrap;
&-view {
position: relative;
width: $calendar-item-size;
height: $calendar-item-size;
text-align: center;
&-bgbegin,
&-bg,
&-bgend,
&-item,
&-dot,
&-tips {
position: absolute;
transition: .2s;
}
&-bgbegin,
&-bg,
&-bgend {
opacity: .15;
height: 80%;
}
&-bg {
left: 0;
top: 10%;
width: 100%;
}
&-bgbegin {
border-radius: $calendar-item-size 0 0 $calendar-item-size;
top: 10%;
left: 10%;
width: 90%;
}
&-bgend {
border-radius: 0 $calendar-item-size $calendar-item-size 0;
top: 10%;
left: 0%;
width: 90%;
}
&-item {
left: 5%;
top: 5%;
width: 90%;
height: 90%;
border-radius: $calendar-item-size;
display: flex;
align-items: center;
justify-content: center;
}
&-dot {
right: 10%;
top: 10%;
width: 12upx;
height: 12upx;
border-radius: 12upx;
}
&-tips {
bottom: 100%;
left: 50%;
transform: translateX(-50%);
background: #4E4B46;
color: #fff;
border-radius: 12upx;
padding: 10upx 20upx;
font-size: 24upx;
width: max-content;
margin-bottom: 5px;
pointer-events: none;
&:after {
content: "";
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-style: solid;
border-width: 5px 5px 0 5px;
border-color: #4E4B46 transparent transparent transparent;
}
}
}
}
}
@font-face {
font-family: "mxdatepickericon";
src: url('data:application/x-font-woff2;charset=utf-8;base64,d09GMgABAAAAAAMYAAsAAAAACBgAAALMAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHEIGVgCDIgqDRIJiATYCJAMUCwwABCAFhG0HSRvfBsg+QCa3noNAyAQ9w6GDvbwpNp2vloCyn8bD/x+y+/5qDhtj+T4eRVEcbsCoKMFASzCgLdDkmqYDwgxkWQ6YH5L/YnppOlLEjlnter43YRjU7M6vJ3iGADVAgJn5kqjv/wEii23T86UsAQT+04fV+o97VTMx4PPZt4DlorLXwIQiGMA5uhaVrBWqGHfQXcTEiE+PE+g2SUlxWlLVBHwUYFMgrgwSB3wstTKSGzqF1nOyiGeeOtNjV4An/vvxR58PSc3AzrMViyDvPo/7dVEUzn5GROfIWAcU4rLXfMFdhte56y4We9gGNEVIezkBOOaQXUrbTf/hJVkhGpDdCw7dSOEzByMEn3kIic98hMxnAfeFPKWCbjRcA148/HxhCEkaA94eGWFaGolsblpaWz8/Po2WVuNHh1fmBpZHIpqal9fOjizhTteY+RZ9rv02I/pq0W6QVH3pSncBz3m55r9ZIPycHfmenvxe4uyutIgfT5u4bgkDusl9gcF0rnfnz+b2NpSaQWBFeu8GIL1xQj5AH/6FAsEr/50F28e/gA9ny6KjLrxIp0TE+UucmQOl5AFNLXkzZufWamWHYEI39PEP2If97CMdm51N6DSmIekwAVmneXTBr0PVYx+aTgfQbU3p+R4jKHdRurBq0oEw6AKSfm+QDbpGF/w3VOP+oBnMHbqdx409FjP4RRHHkAj5IWgQiBUjHfMTuQ1Icpg5avI4sQVRu8EHdWptM1aKrIjuscfeL+kZwxBTYoElztOQ2UygjRIjEphaZsyWodHgvm9SC8QC/JygEA6DiCDeEMhAQFhhOpvxa/18A0TiYMahIy0L2hYIZWeYH9JR085Al4qts1re5St2/SR6DINBGEVYQCWOETHDMAHZ+pcZIQJGTV4RtMmg8UbhuWL1+VLLA2RFHYC71kiRo0SNpjwQh8pj2EFU3oTNmS1WqgIA') format('woff2');
}
.picker-icon {
font-family: "mxdatepickericon" !important;
}
.picker-icon-you:before {
content: "\e63e";
}
.picker-icon-zuo:before {
content: "\e640";
}
.picker-icon-zuozuo:before {
content: "\e641";
}
.picker-icon-youyou:before {
content: "\e642";
}
</style>

File diff suppressed because one or more lines are too long

@ -0,0 +1,237 @@
<template>
<view>
<!-- 上传文件 begin -->
<view class="display_flex">
<view class="img_list" v-for="(item,index) in fileList" :key="index">
<image v-show="item.ext!=='mp4'" class="img_item" :src="item.url" @click="preview(item.url)" mode="aspectFill" v-if="item.book == 'img'"></image>
<video object-fit="cover" @play="videoPlay" id="myVideo" @fullscreenchange="fullscreenchange" v-if="item.ext=='mp4'" class="img_item" :src="item.url" controls></video>
<view :initial-time="0" play-btn-position="center" class="upload-video " @click="prePdf(item.url,item.ext)" v-else>
<!-- <text class="iconfont iconexcel" style="color:#FF6969;font-size:88rpx"></text> -->
<image v-show="item.ext == 'pdf'" src="../../static/img/PDF.png" mode="aspectFill" style="width:88rpx;height: 88rpx;"></image>
<image v-show="item.ext == 'doc' || item.ext == 'docx'" src="../../static/img/word.png" mode="aspectFill" style="width:88rpx;height: 88rpx;"></image>
<image v-show="item.ext == 'xlsx' || item.ext == 'xls'" src="../../static/img/excel.png" mode="aspectFill" style="width:88rpx;height: 88rpx;"></image>
</view>
<view v-if="handle" class="iconfont deleteicon iconshanchu2" @click.stop="delteFile(index)"></view>
</view>
<view v-if="handle" class="upload img_list" @click="upload('img')">
<view v-if="handle" class="iconfont iconziyuan" style="font-size:40rpx;"></view>
上传照片
</view>
<view v-if="handle" class="upload img_list" @click="upload('file')">
<view v-if="handle" class="iconfont iconshangchuanwenjian fileicon"></view>
上传文件
</view>
</view>
<!-- 上传文件 end -->
</view>
</template>
<script>
import {
get,
post
} from '../utils/request.js'
import {
uploadImage,
uploadPdf
} from '../utils/uploadimage.js'
import {
previepdf
} from '../utils/openpdf.js'
export default {
name: 'upload',
model: {
prop: "showPop",
event: "change"
},
props: {
limitnumber: {
type: Number,
default: 100
},
//
files:{
type:Array,
default:[]
},
//
handle:{
type:Boolean,
default:true
}
},
data() {
return {
fileList: []
}
},
watch:{
files(val){
if(val&&val.length!==0){
this.fileList = val
// ul
if(val[0].url==''||val[0].url=='ul'){
this.fileList.splice(0,1)
}
}
}
},
methods: {
/**
* @description 上传文件
*/
upload(book) {
if (this.fileList.length == this.$props.limitnumber) {
uni.showToast({
title: '文件上传已上限',
icon: 'none'
})
return
}
if (book == 'img') {
uploadImage().then(res => {
this.fileList.push({
url: res.imageurl,
ext: res.ext,
book: 'img'
})
this.$emit('upload',this.fileList)
})
} else {
uploadPdf().then(res => {
this.fileList.push({
url: res.pdfurl,
ext: res.ext,
book: 'file'
})
this.$emit('upload',this.fileList)
})
}
},
//
test() {
var self = this;
uni.chooseMedia({
count: 9,
mediaType: ['image','video'],
sourceType: ['album', 'camera'],
maxDuration: 30,
camera: 'back',
success(res) {
console.log(res.tempFilest,res,'选择图片或者视频')
}
})
},
videoPlay(e){
console.log(e,'播放触发')
this.videoContext = uni.createVideoContext('myVideo',this)
this.videoContext.requestFullScreen();
},
// 退
fullscreenchange (e){
console.log(e,'看下退出')
if(!e.detail.fullScreen){
this.videoContext.stop()
e.timeStamp = 0 //
}
},
/**
* @description 预览图片
*/
preview(url) {
uni.previewImage({
urls: [url],
current: url
})
},
/**
* @description 预览pdf
*/
prePdf(e,ext) {
previepdf(e,ext)
},
/**
* @description 删除上传的文件
*/
delteFile(index) {
this.fileList.splice(index, 1)
this.$emit('upload',this.fileList)
},
}
}
</script>
<style>
@import "./pretty-uploadFile.css";
.display_flex {
display: flex;
flex-wrap: wrap;
width: 100%;
}
.fileicon {
font-size: 50rpx;
}
/* 上传图片 */
.img_list {
width: 140rpx;
height: 140rpx;
border-radius: 10rpx;
margin: 20rpx 10rpx;
position: relative;
}
.img>image {
width: 140rpx;
height: 140rpx;
border-radius: 10rpx;
}
.img_item {
width: 140rpx;
height: 140rpx;
border-radius: 10rpx;
}
.upload {
height: 140rpx;
width: 140rpx;
background-color: #f7f7f7;
border: 2rpx dashed rgb(224, 223, 223);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 28rpx;
color: #aaa;
word-break: break-all;
overflow: hidden;
}
.upload-video {
height: 140rpx;
width: 140rpx;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 28rpx;
color: #aaa;
word-break: break-all;
overflow: hidden;
}
.deleteicon {
position: absolute;
font-size: 40rpx;
color: #F93313;
top: -20rpx;
right: -12rpx;
background-color: #fff;
border-radius: 50%;
z-index: 999;
}
</style>

@ -0,0 +1,133 @@
<template>
<view class="upload">
<view class="list">
<view class="item interval" v-for="(item,index) in fileList" :key="index">
<image :src="item" v-if="item"></image>
<view class="icon-close" @click.stop="handleRemove(index)">
<uni-icon type="closeempty" size="20" color="#fff"></uni-icon>
</view>
</view>
<view class="item" @click="chooseImage" v-if="fileList.length<limit">
<uni-icon type="image" size="30" color="#cccccd"></uni-icon>
</view>
</view>
</view>
</template>
<script>
export default {
props: {
limit: {
type: Number,
default: 5
},
url: {
type: String,
required: true
},
name: {
type: String,
default: 'file'
},
formData: {
type: Object,
default () {
return {}
}
},
header: {
type: Object,
default () {
return {}
}
},
fileList: {
type: Array,
default () {
return []
}
}
},
data() {
return {}
},
methods: {
chooseImage() {
uni.chooseImage({
success: (chooseImageRes) => {
const uploadTask = uni.uploadFile({
url: 'https://www.huorantech.cn/api-guarantee/dg-apply-amount-info/uploadFile',
name: 'file',
filePath: chooseImageRes.tempFilePaths[0],
header: {
"Content-Type": "multipart/form-data",
"token":"eyJhbGciOiJIUzI1NiJ9.eyJqdGkiOiIxIiwic3ViIjoidG9rZW4iLCJpc3MiOiJBZG1pbiIsImlhdCI6MTYxMTcxMjUwNiwiZXhwIjoxNjExNzk4OTA2fQ.Hp6r0Abux__NoAp-8ssmcz4FPdiW_BRdNoXX5UDw_1w"
},
timeOut:30000,
success: (uploadFileRes) => {
console.log(uploadFileRes)
this.$emit('on-success', JSON.parse(uploadFileRes.data))
},
fail: (err) => {
this.$emit('on-error', err)
}
})
uploadTask.onProgressUpdate((res) => {
this.$emit('on-process', res)
})
}
})
},
handleRemove(index) {
this.$emit('on-remove', index)
}
}
}
</script>
<style lang="scss" scoped>
.upload {
.list {
display: flex;
flex-wrap: wrap;
.item {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 140rpx;
height: 140rpx;
border-radius: 8rpx;
border: 2rpx solid #D9D9D9;
background-color: #f3f3f3;
margin-bottom: 20rpx;
&.interval {
margin-right: 16rpx;
border: none;
}
image {
width: 100%;
height: 100%;
border-radius: 8rpx;
margin: 0;
}
.icon-close {
position: absolute;
top: 0;
right: 0;
width: 28rpx;
height: 28rpx;
background-color: red;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0 8rpx 0 0;
}
.icon {
width: 48rpx;
height: 48rpx;
}
}
}
}
</style>

@ -0,0 +1,134 @@
<template>
<!-- 这是底部流程图 -->
<view>
<!-- <view class="">
<text style="width:120rpx;display: block;" class="tx-center">抄送人</text>
<button class="margin-top cu-btn round lines-grey line-grey shadow" v-for="(item,index) in checkedData" :key="index">{{item.name}}</button>
</view> -->
<view class="cu-timeline">
<view class="cu-time" style="color: #0081ff;">审核流程进程</view>
<view class="cu-item text-blue" v-for="(item,index) in fromData" :key="index">
<view class="content">
<view class="content radius10 pdlr10">
<text class="mgr10">部门:</text>
<text>{{item.deptName||''}}</text>
</view>
<view class="content radius10 pdlr10">
<text class="mgr10">时间:</text>
<text>{{item.createTime||''}}</text>
</view>
<view class="content radius10 pdlr10">
<text class="mgr10">审批人:</text>
<text>{{item.approver||''}}</text>
</view>
<view class="content radius10 pdlr10">
<text class="mgr10">审批意见:</text>
<text>{{item.auditOpinion||''}}</text>
</view>
<view class="content radius10 pdlr10">
<text class="mgr10">原因:</text>
<text>{{item.reason||''}}</text>
</view>
<view class="content radius10 pdlr10">
<text class="mgr10">金额万元:</text>
<text>{{item.loanMoney||''}}</text>
</view>
<view class="content radius10 pdlr10">
<text class="mgr10">期限:</text>
<text>{{item.loanTern||''}}</text>
</view>
</view>
</view>
</view>
<view class="cu-timeline mat15 pdb10">
<view class="cu-time" style="color: #1cbbb4;">业务流程进程</view>
<view class="cu-item text-blue" v-for="item,index in fromData1" :key="index">
<view class="content">
<view class=" content radius10 pdlr10">
<text class="mgr10">部门:</text>
<text>{{item.deptName||''}}</text>
</view>
<view class=" content radius10 pdlr10">
<text class="mgr10">时间:</text>
<text>{{item.createTime||''}}</text>
</view>
<view class=" content radius10 pdlr10">
<text class="mgr10">审批人:</text>
<text>{{item.approver||''}}</text>
</view>
<view class=" content radius10 pdlr10">
<text class="mgr10">审批意见:</text>
<text>{{item.auditOpinion||''}}</text>
</view>
<view class=" content radius10 pdlr10">
<text class="mgr10">原因:</text>
<text>{{item.reason||''}}</text>
</view>
<view class=" content radius10 pdlr10">
<text class="mgr10">金额万元:</text>
<text>{{item.loanMoney||''}}</text>
</view>
<view class=" content radius10 pdlr10">
<text class="mgr10">期限:</text>
<text>{{item.loanTern||''}}</text>
</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default{
data(){
return{
fromData:[],//
fromData1:[],//
processId:'',//
busnissID:'',//id
checkedData:[],//
}
},
created() {
this.processId = JSON.parse(uni.getStorageSync('processId'))
this.busnissID = uni.getStorageSync('businessId')
this.listOne()//
this.listTwo()//
},
mounted() {
},
methods:{
listOne(){
let params = {
businessId: this.busnissID,
processId: this.processId
}
this.$http.get('/api-guarantee/dg-audit-process/auditProcessList',params).then(res=>{
console.log(res,'取得业务流程进程')
if(res.data) this.fromData = res.data
console.log(res,'当前的底部数据')
}).catch(err=>{console.log(err)})
},
listTwo(){
let params = {
businessId: this.busnissID,
processId: ''
}
this.$http.get('/api-guarantee/dg-audit-process/auditProcessList',params).then(res=>{
if(res.data) this.fromData1 = res.data
})
},
}
}
</script>
<style scoped lang="scss">
/deep/ .cu-timeline>.cu-item{
padding-top: 15rpx;
padding-bottom: 15rpx;
}
// /deep/
</style>

@ -0,0 +1,161 @@
<!-- 商品列表组件 <good-list :list="xx"></good-list> -->
<template>
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in list" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list">
<view class="charge" :id="'index-' + item.name" :data-index="item.name">
<view class="charge-title flex-between">
<text>{{item.name}}的贷款申请</text>
</view>
<view class="charge-text">
<view>
<text>业务编号</text>
<text class="mgl30">{{item.IDNumber}}</text>
</view>
<view>
<text>所在部门</text>
<text class="mgl30">{{item.department}}</text>
</view>
<view>
<text>客户名称</text>
<text class="mgl30">{{item.cusName}}</text>
</view>
<view class="flex-between">
<text class="time-text">{{item.time}}</text>
<text class="status-text" :style="'background-color:'+item.status.bgColor+';color:'+item.status.textColor">{{item.status.text}}</text>
</view>
</view>
</view>
</view>
<view class="move foot-btn">
<button class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="goto('/guaranteePages/pages/investigationSee/investigationSee')">查看</button>
<button class="mini-btn round suc-btn" type="primary" size="mini" @click="goto('/guaranteePages/pages/assignAB/assignAB')">指派</button>
</view>
</view>
</view>
</template>
<script>
export default {
props:{
list: {
type: Array,
default(){
return []
}
},
listTouchStart: {
type: Number,
default(){
return 0
}
},
listTouchDirection: {
type: String,
default(){
return ''
}
},
modalName: {
type: String,
default(){
return ''
}
}
},
methods: {
// ListTouch
ListTouchStart(e) {
// this.listTouchStart = e.touches[0].pageX
this.$emit('TouchStart', e);
},
// ListTouch
ListTouchMove(e) {
// this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
this.$emit('TouchMove', e);
},
// ListTouch
ListTouchEnd(e) {
this.$emit('TouchEnd', e);
// if (this.listTouchDirection == 'left') {
// this.modalName = e.currentTarget.dataset.target
// } else {
// this.modalName = null
// }
// this.listTouchDirection = null
}
}
}
</script>
<style lang="scss" scoped>
.good-list{
background-color: #fff;
.good-li{
display: flex;
align-items: center;
padding: 20upx;
border-bottom: 1upx solid #eee;
.good-img{
width: 160upx;
height: 160upx;
margin-right: 20rpx;
}
.flex-item{
flex: 1;
.good-name{
font-size: 26upx;
line-height: 40upx;
height: 80upx;
margin-bottom: 20upx;
overflow: hidden;
}
.good-price{
font-size: 26upx;
color: red;
}
.good-sold{
font-size: 24upx;
margin-left: 16upx;
color: gray;
}
}
}
}
.charge{
margin: 0 50rpx;
border-radius: 20rpx;
padding: 0 30rpx;
box-shadow: 0 0 16rpx #ccc;
margin-top: 40rpx;
.charge-title{
padding: 16rpx 0;
border-bottom: 4rpx solid #F2F2F2;
color: #000;
font-weight: bold;
.charge-status{
color: #ccc;
}
}
.charge-text{
padding: 16rpx 0;
color: #707070;
.time-text{
color: #ccc;
}
.status-text{
font-size: 28rpx;
padding: 8rpx 12rpx;
border-radius: 10rpx;
}
}
}
</style>

@ -0,0 +1,202 @@
<template>
<view class="uni-combox">
<view v-if="label" class="uni-combox__label" :style="labelStyle">
<text>{{label}}</text>
</view>
<view class="uni-combox__input-box">
<input class="uni-combox__input" type="text" :placeholder="placeholder" v-model="inputVal" @input="onInput"
@focus="onFocus" @blur="onBlur" />
<uni-icons class="uni-combox__input-arrow" type="arrowdown" size="14" @click="toggleSelector"></uni-icons>
<view class="uni-combox__selector" v-if="showSelector">
<scroll-view scroll-y="true" class="uni-combox__selector-scroll">
<view class="uni-combox__selector-empty" v-if="filterCandidatesLength === 0">
<text>{{emptyTips}}</text>
</view>
<view class="uni-combox__selector-item" v-for="(item,index) in filterCandidates" :key="index" @click="onSelectorClick(index)">
<text>{{item}}</text>
</view>
</scroll-view>
</view>
</view>
</view>
</template>
<script>
import uniIcons from '../uni-icons/uni-icons.vue'
export default {
name: 'uniCombox',
components: {
uniIcons
},
props: {
label: {
type: String,
default: ''
},
labelWidth: {
type: String,
default: 'auto'
},
placeholder: {
type: String,
default: ''
},
candidates: {
type: Array,
default () {
return []
}
},
emptyTips: {
type: String,
default: '无匹配项'
},
value: {
type: String,
default: ''
}
},
data() {
return {
showSelector: false,
inputVal: ''
}
},
computed: {
labelStyle() {
if (this.labelWidth === 'auto') {
return {}
}
return {
width: this.labelWidth
}
},
filterCandidates() {
return this.candidates.filter((item) => {
return item.indexOf(this.inputVal) > -1
})
},
filterCandidatesLength() {
return this.filterCandidates.length
}
},
watch: {
value: {
handler(newVal) {
this.inputVal = newVal
},
immediate: true
}
},
methods: {
toggleSelector() {
this.showSelector = !this.showSelector
},
onFocus() {
this.showSelector = true
},
onBlur() {
setTimeout(() => {
this.showSelector = false
},50)
},
onSelectorClick(index) {
this.inputVal = this.filterCandidates[index]
this.showSelector = false
this.$emit('input', this.inputVal)
},
onInput() {
setTimeout(() => {
this.$emit('input', this.inputVal)
})
}
}
}
</script>
<style lang="scss" scoped>
.uni-combox {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
height: 80rpx;
flex-direction: row;
align-items: center;
// border-bottom: solid 2rpx #DDDDDD;
}
.uni-combox__label {
font-size: 32rpx;
line-height: 44rpx;
padding-right: 20rpx;
color: #999999;
}
.uni-combox__input-box {
position: relative;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
}
.uni-combox__input {
flex: 1;
font-size: 32rpx;
height: 44rpx;
line-height: 44rpx;
}
.uni-combox__input-arrow {
padding: 20rpx;
}
.uni-combox__selector {
box-sizing: border-box;
position: absolute;
top: 84rpx;
left: 0;
width: 100%;
background-color: #FFFFFF;
border-radius: 12rpx;
box-shadow: #DDDDDD 8rpx 8rpx 16rpx, #DDDDDD -8rpx -8rpx 16rpx;
z-index: 2;
}
.uni-combox__selector-scroll {
max-height: 400rpx;
box-sizing: border-box;
}
.uni-combox__selector::before {
content: '';
position: absolute;
width: 0;
height: 0;
border-bottom: solid 12rpx #FFFFFF;
border-right: solid 12rpx transparent;
border-left: solid 12rpx transparent;
left: 50%;
top: -12rpx;
margin-left: -12rpx;
}
.uni-combox__selector-empty,
.uni-combox__selector-item {
/* #ifdef APP-NVUE */
display: flex;
/* #endif */
line-height: 72rpx;
font-size: 28rpx;
text-align: center;
border-bottom: solid 2rpx #DDDDDD;
margin: 0rpx 20rpx;
}
.uni-combox__selector-empty:last-child,
.uni-combox__selector-item:last-child {
border-bottom: none;
}
</style>

@ -0,0 +1,316 @@
const events = {
load: 'load',
error: 'error'
}
const pageMode = {
add: 'add',
replace: 'replace'
}
const attrs = [
'pageCurrent',
'pageSize',
'collection',
'action',
'field',
'getcount',
'orderby',
'where'
]
export default {
data() {
return {
loading: false,
listData: this.getone ? {} : [],
paginationInternal: {
current: this.pageCurrent,
size: this.pageSize,
count: 0
},
errorMessage: ''
}
},
created() {
let db = null;
let dbCmd = null;
if(this.collection){
this.db = uniCloud.database();
this.dbCmd = this.db.command;
}
this._isEnded = false
this.$watch(() => {
var al = []
attrs.forEach(key => {
al.push(this[key])
})
return al
}, (newValue, oldValue) => {
this.paginationInternal.pageSize = this.pageSize
let needReset = false
for (let i = 2; i < newValue.length; i++) {
if (newValue[i] != oldValue[i]) {
needReset = true
break
}
}
if (needReset) {
this.clear()
this.reset()
}
if (newValue[0] != oldValue[0]) {
this.paginationInternal.current = this.pageCurrent
}
this._execLoadData()
})
// #ifdef H5
if (process.env.NODE_ENV === 'development') {
this._debugDataList = []
if (!window.unidev) {
window.unidev = {
clientDB: {
data: []
}
}
}
unidev.clientDB.data.push(this._debugDataList)
}
// #endif
// #ifdef MP-TOUTIAO
let changeName
let events = this.$scope.dataset.eventOpts
for (var i = 0; i < events.length; i++) {
let event = events[i]
if (event[0].includes('^load')) {
changeName = event[1][0][0]
}
}
if (changeName) {
let parent = this.$parent
let maxDepth = 16
this._changeDataFunction = null
while (parent && maxDepth > 0) {
let fun = parent[changeName]
if (fun && typeof fun === 'function') {
this._changeDataFunction = fun
maxDepth = 0
break
}
parent = parent.$parent
maxDepth--;
}
}
// #endif
// if (!this.manual) {
// this.loadData()
// }
},
// #ifdef H5
beforeDestroy() {
if (process.env.NODE_ENV === 'development' && window.unidev) {
var cd = this._debugDataList
var dl = unidev.clientDB.data
for (var i = dl.length - 1; i >= 0; i--) {
if (dl[i] === cd) {
dl.splice(i, 1)
break
}
}
}
},
// #endif
methods: {
loadData(args1, args2) {
let callback = null
if (typeof args1 === 'object') {
if (args1.clear) {
this.clear()
this.reset()
}
if (args1.current !== undefined) {
this.paginationInternal.current = args1.current
}
if (typeof args2 === 'function') {
callback = args2
}
} else if (typeof args1 === 'function') {
callback = args1
}
this._execLoadData(callback)
},
loadMore() {
if (this._isEnded) {
return
}
this._execLoadData()
},
refresh() {
this.clear()
this._execLoadData()
},
clear() {
this._isEnded = false
this.listData = []
},
reset() {
this.paginationInternal.current = 1
},
remove(id, {
action,
callback,
confirmTitle,
confirmContent
} = {}) {
if (!id || !id.length) {
return
}
uni.showModal({
title: confirmTitle || '提示',
content: confirmContent || '是否删除该数据',
showCancel: true,
success: (res) => {
if (!res.confirm) {
return
}
this._execRemove(id, action, callback)
}
})
},
_execLoadData(callback) {
if (this.loading) {
return
}
this.loading = true
this.errorMessage = ''
this._getExec().then((res) => {
this.loading = false
const {
data,
count
} = res.result
this._isEnded = data.length < this.pageSize
callback && callback(data, this._isEnded)
this._dispatchEvent(events.load, data)
if (this.getone) {
this.listData = data.length ? data[0] : undefined
} else if (this.pageData === pageMode.add) {
this.listData.push(...data)
if (this.listData.length) {
this.paginationInternal.current++
}
} else if (this.pageData === pageMode.replace) {
this.listData = data
this.paginationInternal.count = count
}
// #ifdef H5
if (process.env.NODE_ENV === 'development') {
this._debugDataList.length = 0
this._debugDataList.push(...JSON.parse(JSON.stringify(this.listData)))
}
// #endif
}).catch((err) => {
this.loading = false
this.errorMessage = err
callback && callback()
this.$emit(events.error, err)
})
},
_getExec() {
let exec = this.db
if (this.action) {
exec = exec.action(this.action)
}
exec = exec.collection(this.collection)
if (!(!this.where || !Object.keys(this.where).length)) {
exec = exec.where(this.where)
}
if (this.field) {
exec = exec.field(this.field)
}
if (this.orderby) {
exec = exec.orderBy(this.orderby)
}
const {
current,
size
} = this.paginationInternal
exec = exec.skip(size * (current - 1)).limit(size).get({
getCount: this.getcount
})
return exec
},
_execRemove(id, action, callback) {
if (!this.collection || !id) {
return
}
const ids = Array.isArray(id) ? id : [id]
if (!ids.length) {
return
}
uni.showLoading({
mask: true
})
let exec = this.db
if (action) {
exec = exec.action(action)
}
exec.collection(this.collection).where({
_id: dbCmd.in(ids)
}).remove().then((res) => {
callback && callback(res.result)
if (this.pageData === pageMode.replace) {
this.refresh()
} else {
this.removeData(ids)
}
}).catch((err) => {
uni.showModal({
content: err.message,
showCancel: false
})
}).finally(() => {
uni.hideLoading()
})
},
removeData(ids) {
let il = ids.slice(0)
let dl = this.listData
for (let i = dl.length - 1; i >= 0; i--) {
let index = il.indexOf(dl[i]._id)
if (index >= 0) {
dl.splice(i, 1)
il.splice(index, 1)
}
}
},
_dispatchEvent(type, data) {
if (this._changeDataFunction) {
this._changeDataFunction(data, this._isEnded)
} else {
this.$emit(type, data, this._isEnded)
}
}
}
}

@ -0,0 +1,847 @@
<template>
<view class="uni-data-checklist">
<template v-if="loading">
<view class="uni-data-loading">
<uni-load-more status="loading" iconType="snow" :iconSize="18" :content-text="contentText"></uni-load-more>
</view>
</template>
<template v-else>
<checkbox-group v-if="multiple" class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne">
<label class="checklist-box" :class="item.labelClass" :style="[item.styleBackgroud]" v-for="(item,index) in dataList"
:key="index">
<checkbox class="hidden" hidden :disabled="!!item.disabled" :value="item.value+''" :checked="item.selected" />
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="checkbox__inner"
:class="item.checkboxBgClass" :style="[item.styleIcon]">
<view class="checkbox__inner-icon" :class="item.checkboxClass"></view>
</view>
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
<text class="checklist-text" :class="item.textClass" :style="[item.styleIconText]">{{item.text}}</text>
<!-- input的disable去掉,添加change事件发送值到父级 -->
<uni-easyinput @input="upToFa" v-if="inputVisble" type="text" :inputBorder="true" v-model="item.value" placeholder="请输入"></uni-easyinput>
<view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :class="item.listClass" :style="[item.styleBackgroud]"></view>
</view>
</label>
</checkbox-group>
<radio-group v-else class="checklist-group" :class="{'is-list':mode==='list','is-wrap':wrap}" @change="chagne">
<label class="checklist-box" :class="item.labelClass" :style="[item.styleBackgroud]" v-for="(item,index) in dataList" :key="index">
<radio hidden :disabled="item.disabled" :value="item.value+''" :checked="item.selected" />
<view v-if="(mode !=='tag' && mode !== 'list') || ( mode === 'list' && icon === 'left')" class="radio__inner"
:class="item.checkboxBgClass" :style="[item.styleBackgroud]">
<view class="radio__inner-icon" :class="item.checkboxClass" :style="[item.styleIcon]"></view>
</view>
<view class="checklist-content" :class="{'list-content':mode === 'list' && icon ==='left'}">
<text class="checklist-text" :class="item.textClass" :style="[item.styleIconText]">{{item.text}}</text>
<view v-if="mode === 'list' && icon === 'right'" class="checkobx__list" :class="item.listClass" :style="[item.styleRightIcon]"></view>
</view>
</label>
</radio-group>
</template>
</view>
</template>
<script>
/**
* DataChecklist 数据选择器
* @description 通过数据渲染 checkbox radio
* @tutorial https://ext.dcloud.net.cn/plugin?id=xxx
* @property {String} mode = [default| list | button | tag] 显示模式
* @value default 默认横排模式
* @value list 列表模式
* @value button 按钮模式
* @value tag 标签模式
* @property {Boolean} multiple = [true|false] 是否多选
* @property {Array|String|Number} value 默认值
* @property {Array} localdata 本地数据 格式 [{text:'',value:''}]
* @property {Number|String} min 最小选择个数 multiple为true时生效
* @property {Number|String} max 最大选择个数 multiple为true时生效
* @property {Boolean} wrap 是否换行显示
* @property {String} icon = [left|right] list 列表模式下icon显示位置
* @property {Boolean} selectedColor 选中颜色
* @property {Boolean} selectedTextColor 选中文本颜色如不填写则自动显示
* @value left 左侧显示
* @value right 右侧显示
* @event {Function} change 选中发生变化触发
*/
import clientdb from './clientdb.js'
export default {
name: 'uniDataChecklist',
mixins: [clientdb],
props: {
mode: {
type: String,
default: 'default'
},
multiple: {
type: Boolean,
default: false
},
value: {
type: [Array, String, Number],
default () {
return ''
}
},
localdata: {
type: Array,
default () {
return []
}
},
min: {
type: [Number, String],
default: ''
},
max: {
type: [Number, String],
default: ''
},
wrap: {
type: Boolean,
default: false
},
icon: {
type: String,
default: 'left'
},
selectedColor:{
type: String,
default: ''
},
selectedTextColor:{
type: String,
default: ''
},
// clientDB
options: {
type: [Object, Array],
default () {
return {}
}
},
collection: {
type: String,
default: ''
},
action: {
type: String,
default: ''
},
field: {
type: String,
default: ''
},
pageData: {
type: String,
default: 'add'
},
pageCurrent: {
type: Number,
default: 1
},
pageSize: {
type: Number,
default: 20
},
getcount: {
type: [Boolean, String],
default: false
},
orderby: {
type: String,
default: ''
},
where: {
type: [String, Object],
default: ''
},
getone: {
type: [Boolean, String],
default: false
},
manual: {
type: Boolean,
default: false
},
inputVisble: {
type: Boolean,
default: false
}
},
watch: {
localdata: {
handler(newVal) {
this.range = newVal
this.dataList = this.getDataList(this.getSelectedValue(newVal))
// console.log(this.dataList[0],'',newVal)
this.$forceUpdate()
},
deep: true
},
listData(newVal) {
this.range = newVal
this.dataList = this.getDataList(this.getSelectedValue(newVal))
},
value(newVal) {
this.dataList = this.getDataList(newVal)
this.formItem && this.formItem.setValue(newVal)
}
},
data() {
return {
dataList: [],
range: [],
contentText: {
contentdown: '查看更多',
contentrefresh: '加载中',
contentnomore: '没有更多'
},
styles: {
selectedColor: '#007aff',
selectedTextColor: '#333',
},
changeData:'888'
};
},
created() {
// console.log(this.localdata,'')
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
this.formItem && this.formItem.setValue(this.value)
this.styles = {
selectedColor: this.selectedColor,
selectedTextColor: this.selectedTextColor
}
if (this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
if (this.localdata && this.localdata.length !== 0) {
this.range = this.localdata
this.dataList = this.getDataList(this.getSelectedValue(this.range))
} else {
if (this.collection) {
this.loadData()
}
}
},
methods: {
init(range) {},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
chagne(e) {
const values = e.detail.value
let detail = {
value: [],
data: []
}
if (this.multiple) {
this.range.forEach(item => {
if (values.includes(item.value + '')) {
detail.value.push(item.value)
detail.data.push(item)
}
})
} else {
const range = this.range.find(item => (item.value + '') === values)
if (range) {
detail = {
value: range.value,
data: range
}
}
}
this.formItem && this.formItem.setValue(detail.value)
this.$emit('input', detail.value)
this.$emit('change', {
detail
})
if (this.multiple) {
// v-model
// if (this.value.length === 0) {
this.dataList = this.getDataList(detail.value, true)
// }
} else {
this.dataList = this.getDataList(detail.value)
}
},
getLabelClass(item, index) {
let classes = []
switch (this.mode) {
case 'default':
item.disabled && classes.push('disabled-cursor')
break
case 'button':
classes.push(...['is-button', ...this.getClasses(item, 'button')])
break
case 'list':
if (this.multiple) {
classes.push('is-list-multiple-box')
} else {
classes.push('is-list-box')
}
item.disabled && classes.push('is-list-disabled')
index !== 0 && classes.push('is-list-border')
break
case 'tag':
classes.push(...['is-tag', ...this.getClasses(item, 'tag')])
break
}
classes = classes.join(' ')
return classes
},
getCheckboxClass(item, type = '') {
let classes = []
if (this.multiple) {
classes.push(...this.getClasses(item, 'default-multiple', type))
} else {
classes.push(...this.getClasses(item, 'default', type))
}
classes = classes.join(' ')
return classes
},
getTextClass(item) {
let classes = []
switch (this.mode) {
case 'default':
classes.push(...this.getClasses(item, 'list'))
break
case 'button':
classes.push(...this.getClasses(item, 'list'))
break
case 'list':
classes.push(...this.getClasses(item, 'list'))
break
case 'tag':
classes.push(...['is-tag-text', ...this.getClasses(item, 'tag-text')])
break
}
classes = classes.join(' ')
return classes
},
/**
* 获取渲染的新数组
* @param {Object} value 选中内容
*/
getDataList(value) {
//
let dataList = JSON.parse(JSON.stringify(this.range))
let list = []
if (this.multiple) {
if (!Array.isArray(value)) {
value = []
// console.error('props ');
}
}
dataList.forEach((item, index) => {
item.disabled = item.disable || item.disabled || false
if (this.multiple) {
if (value.length > 0) {
let have = value.find(val => val === item.value)
item.selected = have !== undefined
} else {
item.selected = false
}
} else {
item.selected = value === item.value
}
list.push(item)
})
// console.log(dataList,'list')
return this.setRange(list)
},
/**
* 处理最大最小值
* @param {Object} list
*/
setRange(list) {
let selectList = list.filter(item => item.selected)
let min = Number(this.min) || 0
let max = Number(this.max) || ''
list.forEach((item, index) => {
if (this.multiple) {
if (selectList.length <= min) {
let have = selectList.find(val => val.value === item.value)
if (have !== undefined) {
item.disabled = true
}
}
if (selectList.length >= max && max !== '') {
let have = selectList.find(val => val.value === item.value)
if (have === undefined) {
item.disabled = true
}
}
}
this.setClass(item, index)
list[index] = item
})
// console.log(this.dataList,'')
return list
},
/**
* 设置 class
* @param {Object} item
* @param {Object} index
*/
setClass(item, index) {
// label class
item.labelClass = this.getLabelClass(item, index)
// checkbox
item.checkboxBgClass = this.getCheckboxClass(item, '-bg')
// checkbox
item.checkboxClass = this.getCheckboxClass(item)
//
item.textClass = this.getTextClass(item)
// list
item.listClass = this.getCheckboxClass(item, '-list')
//
item.styleBackgroud = this.setStyleBackgroud(item)
item.styleIcon = this.setStyleIcon(item)
item.styleIconText = this.setStyleIconText(item)
item.styleRightIcon = this.setStyleRightIcon(item)
},
/**
* 获取 class
*/
getClasses(item, name, type = '') {
let classes = []
item.disabled && classes.push('is-' + name + '-disabled' + type)
item.selected && classes.push('is-' + name + '-checked' + type)
if (this.mode !== 'button' || name === 'button') {
item.selected && item.disabled && classes.push('is-' + name + '-disabled-checked' + type)
}
return classes
},
/**
* 获取选中值
* @param {Object} range
*/
getSelectedValue(range) {
if (!this.multiple) return this.value
let selectedArr = []
range.forEach((item) => {
if (item.selected) {
selectedArr.push(item.value)
}
})
return this.value.length > 0 ? this.value : selectedArr
},
/**
* 设置背景样式
*/
setStyleBackgroud(item) {
let styles = {}
if (item.selected) {
if (this.mode !== 'list') {
styles.borderColor = this.styles.selectedColor
}
if (this.mode === 'tag') {
styles.backgroundColor = this.styles.selectedColor
}
}
return styles
},
setStyleIcon(item) {
let styles = {}
if (item.selected) {
styles.backgroundColor = this.styles.selectedColor
styles.borderColor = this.styles.selectedColor
}
return styles
},
setStyleIconText(item) {
let styles = {}
if (item.selected) {
if (this.styles.selectedTextColor) {
styles.color = this.styles.selectedTextColor
} else {
if(this.mode === 'tag'){
styles.color = '#fff'
}else{
styles.color = this.styles.selectedColor
}
}
}
return styles
},
setStyleRightIcon(item){
let styles = {}
if (item.selected) {
if(this.mode === 'list'){
styles.borderColor = this.styles.selectedColor
}
}
return styles
},
// --inpuitem
upToFa(val,item){
console.log(val,'输入的值','item',item)
this.$emit('getInput',val,item)
// this.$forceUpdate()
setTimeout(() => { console.log(val) }, 0)
}
}
}
</script>
<style>
.uni-data-checklist {
position: relative;
z-index: 0;
width: 80%;
margin-left: 20%;
/* min-height: 36px; */
}
.uni-data-loading {
display: flex;
align-items: center;
/* justify-content: center; */
height: 72rpx;
padding-left: 20rpx;
}
.checklist-group {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
flex-wrap: wrap;
}
.checklist-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
margin: 10rpx 0;
margin-right: 50rpx;
}
.checklist-text {
font-size: 28rpx;
color: #333;
margin-left: 10rpx;
transition: color 0.2s;
}
.is-button {
margin-right: 20rpx;
padding: 6rpx 30rpx;
border: 2rpx #DCDFE6 solid;
border-radius: 6rpx;
transition: border-color 0.2s;
}
.is-list {
flex-direction: column;
}
.is-list-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
padding: 20rpx 30rpx;
padding-left: 0;
margin: 0;
}
.checklist-content {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.list-content {
margin-left: 10rpx;
}
.is-list-multiple-box {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
padding: 20rpx 30rpx;
padding-left: 0;
margin: 0;
}
.is-list-border {
border-top: 2rpx #eee solid;
}
.is-tag {
margin-right: 20rpx;
padding: 6rpx 20rpx;
border: 2rpx #eee solid;
border-radius: 6rpx;
background-color: #f5f5f5;
/* transition: border-color 0.1s; */
}
.is-tag-text {
margin: 0;
color: #666;
}
.checkbox__inner {
flex-shrink: 0;
position: relative;
border: 2rpx solid #DCDFE6;
border-radius: 4rpx;
box-sizing: border-box;
width: 32rpx;
height: 32rpx;
background-color: #fff;
z-index: 1;
transition: border-color 0.1s;
}
.checkbox__inner-icon {
border: 2rpx solid #fff;
border-left: 0;
border-top: 0;
height: 16rpx;
left: 10rpx;
position: absolute;
top: 2rpx;
width: 6rpx;
opacity: 0;
transition: transform .2s;
transform-origin: center;
transform: rotate(40deg) scaleY(0.4);
}
.radio__inner {
flex-shrink: 0;
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
justify-content: center;
align-items: center;
position: relative;
border: 2rpx solid #DCDFE6;
border-radius: 4rpx;
box-sizing: border-box;
width: 32rpx;
height: 32rpx;
border-radius: 32rpx;
background-color: #fff;
z-index: 1;
transition: border-color .3s;
}
.radio__inner-icon {
width: 16rpx;
height: 16rpx;
border-radius: 20rpx;
opacity: 0;
transition: transform .3s;
}
.checkobx__list {
border: 2rpx solid #fff;
border-left: 0;
border-top: 0;
height: 24rpx;
width: 12rpx;
transform-origin: center;
opacity: 0;
transition: all 0.3s;
transform: rotate(45deg);
}
/* 禁用样式 */
.is-default-disabled-bg {
background-color: #F2F6FC;
border-color: #DCDFE6;
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
}
.is-default-multiple-disabled-bg {
background-color: #F2F6FC;
border-color: #DCDFE6;
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
}
.is-default-disabled {
border-color: #F2F6FC;
}
.is-default-multiple-disabled {
border-color: #F2F6FC;
}
.is-list-disabled {
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
color: #999;
}
.is-list-disabled-checked {
color: #a1dcc1;
}
.is-button-disabled {
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
border-color: #EBEEF5;
}
.is-button-text-disabled {
color: #C0C4CC;
}
.is-button-disabled-checked {
border-color: #a1dcc1;
}
.is-tag-disabled {
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
border-color: #e9e9eb;
background-color: #f4f4f5;
}
.is-tag-text-disabled {
color: #bcbec2;
}
/* 选中样式 */
.is-default-checked-bg {
border-color: #007aff;
}
.is-default-multiple-checked-bg {
border-color: #007aff;
background-color: #007aff;
}
.is-default-checked {
opacity: 1;
background-color: #007aff;
transform: rotate(45deg) scaleY(1);
}
.is-default-multiple-checked {
opacity: 1;
transform: rotate(45deg) scaleY(1);
}
.is-default-disabled-checked-bg {
opacity: 0.4;
}
.is-default-multiple-disabled-checked-bg {
opacity: 0.4;
}
.is-default-checked-list {
border-color: #007aff;
opacity: 1;
transform: rotate(45deg) scaleY(1);
}
.is-default-multiple-checked-list {
border-color: #007aff;
opacity: 1;
transform: rotate(45deg) scaleY(1);
}
.is-list-disabled-checked {
opacity: 0.4;
}
.is-default-disabled-checked-list {
opacity: 0.4;
}
.is-default-multiple-disabled-checked-list {
opacity: 0.4;
}
.is-button-checked {
border-color: #007aff;
}
.is-button-disabled-checked {
opacity: 0.4;
}
.is-list-checked {
color: #007aff;
}
.is-tag-checked {
border-color: #007aff;
background-color: #007aff;
}
.is-tag-text-checked {
color: #fff;
}
.is-tag-disabled-checked {
opacity: 0.4;
}
.disabled-cursor {
/* #ifdef H5 */
cursor: not-allowed;
/* #endif */
}
.is-wrap {
flex-direction: column;
}
.hidden {
/* #ifdef MP-ALIPAY */
display: none;
/* #endif */
}
</style>

@ -0,0 +1,364 @@
<template>
<view class="uni-datetime-picker">
<view @click="tiggerTimePicker">
<slot>
<view class="uni-datetime-picker-timebox uni-datetime-picker-flex">
{{time}}<view v-if="!time" class="uni-datetime-picker-time">选择日期<!-- 时间 --></view>
<view class="uni-datetime-picker-down-arrow"></view>
</view>
</slot>
</view>
<view v-if="visible" class="uni-datetime-picker-mask" @click="initTimePicker"></view>
<view v-if="visible" class="uni-datetime-picker-popup">
<view class="uni-title">
设置日期<!-- 和时间 -->
</view>
<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="ymd" @change="bindDateChange">
<picker-view-column class="uni-datetime-picker-hyphen">
<view class="uni-datetime-picker-item" v-for="(item,index) in years" :key="index">{{item}}</view>
</picker-view-column>
<picker-view-column class="uni-datetime-picker-hyphen">
<view class="uni-datetime-picker-item" v-for="(item,index) in months" :key="index">{{item < 10 ? '0' + item : item}}</view>
</picker-view-column>
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in days" :key="index">{{item < 10 ? '0' + item : item}}</view>
</picker-view-column>
</picker-view>
<!-- <picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="hms" @change="bindTimeChange">
<picker-view-column class="uni-datetime-picker-colon">
<view class="uni-datetime-picker-item" v-for="(item,index) in hours" :key="index">{{item < 10 ? '0' + item : item}}</view>
</picker-view-column>
<picker-view-column class="uni-datetime-picker-colon">
<view class="uni-datetime-picker-item" v-for="(item,index) in minutes" :key="index">{{item < 10 ? '0' + item : item}}</view>
</picker-view-column>
<picker-view-column>
<view class="uni-datetime-picker-item" v-for="(item,index) in seconds" :key="index">{{item < 10 ? '0' + item : item}}</view>
</picker-view-column>
</picker-view> -->
<view class="uni-datetime-picker-btn">
<!-- <view class="" @click="clearTime">重置</view> -->
<view class="uni-datetime-picker-btn-group">
<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">取消</view>
<view class="" @click="setTime">确定</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
visible: false,
time: '',
years: [],
months: [],
days: [],
hours: [],
minutes: [],
seconds: [],
year: 1900,
month: 0,
day: 0,
hour: 0,
minute: 0,
second: 0,
indicatorStyle: `height: 50px;`,
}
},
props: {
type: {
type: String,
default: 'datetime-local'
},
timestamp: {
type: Boolean,
default: false
},
value: {
type: [String, Number],
default: ''
},
maxYear: {
type: [Number, String],
default: 2100
},
minYear: {
type: [Number, String],
default: 1900
}
},
computed: {
ymd() {
return [this.year - this.minYear, this.month - 1, this.day - 1]
},
hms() {
return [this.hour, this.minute, this.second]
}
},
watch: {
value(newValue) {
this.parseValue(this.value)
this.initTime()
}
},
created() {
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
},
mounted() {
const date = new Date()
for (let i = this.minYear; i <= this.maxYear; i++) {
this.years.push(i)
}
for (let i = 1; i <= 12; i++) {
this.months.push(i)
}
for (let i = 1; i <= 31; i++) {
this.days.push(i)
}
for (let i = 0; i <= 23; i++) {
this.hours.push(i)
}
for (let i = 0; i <= 59; i++) {
this.minutes.push(i)
}
for (let i = 0; i <= 59; i++) {
this.seconds.push(i)
}
this.parseValue(this.value)
if (this.value) {
this.initTime()
}
},
methods: {
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
parseDateTime(datetime) {
let defaultDate = null
if (!datetime) {
defaultDate = new Date()
} else {
defaultDate = new Date(datetime)
}
this.year = defaultDate.getFullYear()
if (this.year < this.minYear || this.year > this.maxYear) {
const now = Date.now()
this.parseDateTime(now)
return
}
this.month = defaultDate.getMonth() + 1
this.day = defaultDate.getDate()
this.hour = defaultDate.getHours()
this.minute = defaultDate.getMinutes()
this.second = defaultDate.getSeconds()
},
parseValue(defaultTime) {
if (Number(defaultTime)) {
defaultTime = parseInt(defaultTime)
}
this.parseDateTime(defaultTime)
},
bindDateChange(e) {
const val = e.detail.value
this.year = this.years[val[0]]
this.month = this.months[val[1]]
this.day = this.days[val[2]]
console.log(this.year+'-'+this.month+'-'+this.day)
},
bindTimeChange(e) {
const val = e.detail.value
this.hour = this.hours[val[0]]
this.minute = this.minutes[val[1]]
this.second = this.seconds[val[2]]
},
initTimePicker() {
// if (!this.time) {
// this.parseValue()
// }
this.parseValue(this.value)
this.visible = !this.visible
},
tiggerTimePicker() {
this.visible = !this.visible
},
clearTime() {
this.time = ''
this.tiggerTimePicker()
},
initTime() {
this.time = this.createDomSting()
if (!this.timestamp) {
this.formItem && this.formItem.setValue(this.time)
this.$emit('change', this.time)
} else {
this.formItem && this.formItem.setValue(this.createTimeStamp(this.time))
this.$emit('change', this.createTimeStamp(this.time))
}
},
setTime() {
this.initTime()
this.tiggerTimePicker()
},
createTimeStamp(time) {
return Date.parse(new Date(time))
},
createDomSting() {
const yymmdd = this.year +
'-' +
(this.month < 10 ? '0' + this.month : this.month) +
'-' +
(this.day < 10 ? '0' + this.day : this.day)
// +
// ' ' +
// (this.hour < 10 ? '0' + this.hour : this.hour) +
// ':' +
// (this.minute < 10 ? '0' + this.minute : this.minute) +
// ':' +
// (this.second < 10 ? '0' + this.second : this.second)
return yymmdd
}
}
}
</script>
<style>
.uni-datetime-picker-view {
width: 100%;
height: 130px;
margin-top: 60rpx;
}
.uni-datetime-picker-item {
line-height: 100rpx;
text-align: center;
}
.uni-title{
text-align: center;
}
.uni-datetime-picker-btn {
margin-top: 120rpx;
display: flex;
justify-content: center;
align-items: center;
color: #00B9FF;
cursor: pointer;
}
.uni-datetime-picker-btn-group {
display: flex;
}
.uni-datetime-picker-cancel {
margin-right: 60rpx;
}
.uni-datetime-picker-mask {
position: fixed;
bottom: 0px;
top: 0px;
left: 0px;
right: 0px;
background-color: rgba(0, 0, 0, 0.4);
transition-duration: 0.3s;
z-index: 998;
}
.uni-datetime-picker-popup {
border-radius: 16rpx;
padding: 60rpx;
width: 540rpx;
background-color: #fff;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition-duration: 0.3s;
z-index: 999;
}
.uni-datetime-picker-time {
color: grey;
}
.uni-datetime-picker-colon::after {
content: ':';
position: absolute;
top: 106rpx;
right: 0;
}
.uni-datetime-picker-hyphen::after {
content: '-';
position: absolute;
top: 106rpx;
right: -4rpx;
}
.uni-datetime-picker-timebox {
border: 2rpx solid #E5E5E5;
border-radius: 40rpx;
padding: 14rpx 40rpx;
box-sizing: border-box;
cursor: pointer;
width: 80%;
margin-left: 20%;
}
//
.uni-datetime-picker-down-arrow {
display :inline-block;
position: relative;
width: 40rpx;
height: 30rpx;
}
.uni-datetime-picker-down-arrow::after {
display: inline-block;
content: " ";
height: 18rpx;
width: 18rpx;
border-width: 0 2rpx 2rpx 0;
border-color: #E5E5E5;
border-style: solid;
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
transform-origin: center;
transition: transform .3s;
position: absolute;
top: 50%;
right: 10rpx;
margin-top: -10rpx;
}
.uni-datetime-picker-flex {
display: flex;
justify-content: space-between;
}
</style>

@ -0,0 +1,56 @@
/**
* @desc 函数防抖
* @param func 目标函数
* @param wait 延迟执行毫秒数
* @param immediate true - 立即执行 false - 延迟执行
*/
export const debounce = function(func, wait = 1000, immediate = true) {
let timer;
console.log(1);
return function() {
console.log(123);
let context = this,
args = arguments;
if (timer) clearTimeout(timer);
if (immediate) {
let callNow = !timer;
timer = setTimeout(() => {
timer = null;
}, wait);
if (callNow) func.apply(context, args);
} else {
timer = setTimeout(() => {
func.apply(context, args);
}, wait)
}
}
}
/**
* @desc 函数节流
* @param func 函数
* @param wait 延迟执行毫秒数
* @param type 1 使用表时间戳在时间段开始的时候触发 2 使用表定时器在时间段结束的时候触发
*/
export const throttle = (func, wait = 1000, type = 1) => {
let previous = 0;
let timeout;
return function() {
let context = this;
let args = arguments;
if (type === 1) {
let now = Date.now();
if (now - previous > wait) {
func.apply(context, args);
previous = now;
}
} else if (type === 2) {
if (!timeout) {
timeout = setTimeout(() => {
timeout = null;
func.apply(context, args)
}, wait)
}
}
}
}

@ -0,0 +1,402 @@
<template>
<view class="uni-easyinput" :class="{'uni-easyinput-error':msg}" :style="{color:inputBorder && msg?'#dd524d':styles.color}">
<view class="uni-easyinput__content" :class="{'is-input-border':inputBorder ,'is-input-error-border':inputBorder && msg,'is-textarea':type==='textarea','is-disabled':disabled}"
:style="{'border-color':inputBorder && msg?'#dd524d':styles.borderColor,'background-color':disabled?styles.disableColor:'#fff'}">
<uni-icons v-if="prefixIcon" class="content-clear-icon" :type="prefixIcon" color="#c0c4cc" @click="onClickIcon('prefix')"></uni-icons>
<textarea v-if="type === 'textarea'" class="uni-easyinput__content-textarea" :class="{'input-padding':inputBorder}"
:name="name" :value="val" :placeholder="placeholder" :placeholderStyle="placeholderStyle" :disabled="disabled"
:maxlength="inputMaxlength" :focus="focused" :autoHeight="autoHeight" @input="onInput" @blur="onBlur" @focus="onFocus"
@confirm="onConfirm"></textarea>
<input v-else :type="type === 'password'?'text':type" class="uni-easyinput__content-input" :style="{
'padding-right':type === 'password' ||clearable || prefixIcon?'':'40rpx',
'padding-left':prefixIcon?'':'40rpx'
}"
:name="name" :value="val" :password="!showPassword && type === 'password'" :placeholder="placeholder"
:placeholderStyle="placeholderStyle" :disabled="disabled" :maxlength="inputMaxlength" :focus="focused" @focus="onFocus"
@blur="onBlur" @input="onInput" @confirm="onConfirm" />
<template v-if="type === 'password'">
<uni-icons v-if="val != '' " class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}" :type="showPassword?'eye-slash-filled':'eye-filled'"
:size="18" color="#c0c4cc" @click="onEyes"></uni-icons>
</template>
<template v-else-if="suffixIcon">
<uni-icons v-if="suffixIcon" class="content-clear-icon" :type="suffixIcon" color="#c0c4cc" @click="onClickIcon('suffix')"></uni-icons>
</template>
<template v-else>
<uni-icons class="content-clear-icon" :class="{'is-textarea-icon':type==='textarea'}" type="clear" :size="clearSize"
v-if="clearable && focused && val " color="#c0c4cc" @click="onClear"></uni-icons>
</template>
</view>
</view>
</template>
<script>
/**
* Field 输入框
* @description 此组件可以实现表单的输入与校验包括 "text" "textarea" 类型
* @tutorial https://ext.dcloud.net.cn/plugin?id=21001
* @property {String| Number} value 输入内容
* @property {String } type 输入框的类型默认text password/text/textarea/..
* @value text 文本输入键盘
* @value textarea 多行文本输入键盘
* @value password 密码输入键盘
* @value number 数字输入键盘注意iOS上app-vue弹出的数字键盘并非9宫格方式
* @value idcard 身份证输入键盘支付宝百度QQ小程序
* @value digit 带小数点的数字键盘 App的nvue页面微信支付宝百度头条QQ小程序支持
* @property {Boolean} clearable 是否显示右侧清空内容的图标控件(输入框有内容且获得焦点时才显示)点击可清空输入框内容默认true
* @property {Boolean} autoHeight 是否自动增高输入区域type为textarea时有效默认true
* @property {String } placeholder 输入框的提示文字
* @property {String } placeholderStyle placeholder的样式(内联样式字符串)"color: #ddd"
* @property {Boolean} focus 是否自动获得焦点默认false
* @property {Boolean} disabled 是否不可输入默认false
* @property {Number } maxlength 最大输入长度设置为 -1 的时候不限制最大长度默认140
* @property {String } confirmType 设置键盘右下角按钮的文字仅在type="text"时生效默认done
* @property {Number } clearSize 清除图标的大小单位px默认15
* @property {String} prefixIcon 输入框头部图标
* @property {String} suffixIcon 输入框尾部图标
* @property {Boolean} trim 是否自动去除两端的空格
* @property {Boolean} inputBorder 是否显示input输入框的边框默认false
* @property {Object} styles 自定义颜色
* @event {Function} input 输入框内容发生变化时触发
* @event {Function} focus 输入框获得焦点时触发
* @event {Function} blur 输入框失去焦点时触发
* @event {Function} confirm 点击完成按钮时触发
* @event {Function} iconClick 点击图标时触发
* @example <uni-easyinput v-model="mobile"></uni-easyinput>
*/
import {
debounce,
throttle
} from './common.js'
export default {
name: 'uni-easyinput',
props: {
name: String,
value: [Number, String],
type: {
type: String,
default: 'text'
},
clearable: {
type: Boolean,
default: true
},
autoHeight: {
type: Boolean,
default: false
},
placeholder: String,
placeholderStyle: String,
focus: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
maxlength: {
type: [Number, String],
default: 140
},
confirmType: {
type: String,
default: 'done'
},
//
clearSize: {
type: [Number, String],
default: 15
},
// input
inputBorder: {
type: Boolean,
default: true
},
prefixIcon: {
type: String,
default: ''
},
suffixIcon: {
type: String,
default: ''
},
//
trim: {
type: Boolean,
default: true
},
//
styles: {
type: Object,
default () {
return {
color: '#333',
disableColor: '#eee',
borderColor: '#e5e5e5'
}
}
}
},
data() {
return {
focused: false,
errMsg: '',
val: '',
showMsg: '',
border: false,
isFirstBorder: false,
showClearIcon: false,
showPassword: false
};
},
computed: {
msg() {
return this.errorMessage || this.errMsg;
},
// uniappinputmaxlength
inputMaxlength() {
return Number(this.maxlength);
},
},
watch: {
value(newVal) {
if (this.errMsg) this.errMsg = ''
this.val = newVal
if (this.formItem) {
this.formItem.setValue(newVal)
}
},
focus(newVal) {
this.$nextTick(() => {
this.focused = this.focus
})
}
},
created() {
this.val = this.value
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
},
mounted() {
// this.onInput = throttle(this.input, 500)
this.$nextTick(() => {
this.focused = this.focus
})
},
methods: {
/**
* 初始化变量值
*/
init() {
},
onClickIcon(type) {
this.$emit('iconClick', type)
},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false;
parentName = parent.$options.name;
}
return parent;
},
onEyes() {
this.showPassword = !this.showPassword
},
onInput(event) {
let value = event.detail.value;
//
if (this.trim) value = this.trimStr(value);
if (this.errMsg) this.errMsg = ''
this.val = value
this.$emit('input', value);
},
onFocus(event) {
this.focused = true;
this.$emit('focus', event);
},
onBlur(event) {
let value = event.detail.value;
// 使@touchstarthx2.8.4
// @blur
setTimeout(() => {
this.focused = false;
}, 100);
this.$emit('blur', event);
},
onConfirm(e) {
this.$emit('confirm', e.detail.value);
},
onClear(event) {
this.val = '';
this.$emit('input', '');
},
fieldClick() {
this.$emit('click');
},
trimStr(str, pos = 'both') {
if (pos == 'both') {
return str.replace(/^\s+|\s+$/g, '');
} else if (pos == 'left') {
return str.replace(/^\s*/, '');
} else if (pos == 'right') {
return str.replace(/(\s*$)/g, '');
} else if (pos == 'all') {
return str.replace(/\s+/g, '');
} else {
return str;
}
}
}
};
</script>
<style lang="scss" scoped>
.uni-easyinput {
/* #ifndef APP-NVUE */
width: 80%;
margin-left: 20%;
/* #endif */
flex: 1;
position: relative;
// padding: 16px 14px;
text-align: left;
color: #333;
font-size: 28rpx;
}
.uni-easyinput__content {
flex: 1;
/* #ifndef APP-NVUE */
width: 100%;
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
box-sizing: border-box;
min-height: 72rpx;
}
.uni-easyinput__content-input {
position: relative;
overflow: hidden;
flex: 1;
width: auto;
line-height: 2;
font-size: 28rpx;
// padding-right: 10px;
}
.is-textarea {
align-items: flex-start;
}
.is-textarea-icon {
margin-top: 10rpx;
}
.uni-easyinput__content-textarea {
position: relative;
overflow: hidden;
flex: 1;
width: auto;
line-height: 1.5;
font-size: 28rpx;
// padding-right: 10px;
padding-top: 20rpx;
padding-bottom: 20rpx;
// box-sizing: border-box;
min-height: 160rpx;
height: 160rpx;
}
.input-padding {
padding-left: 20rpx;
}
.content-clear-icon {
padding: 0 10rpx;
}
.label-icon {
margin-right: 10rpx;
margin-top: -2rpx;
}
//
.is-input-border {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
align-items: center;
border: 2rpx solid $uni-border-color;
border-radius: 40rpx;
box-sizing: border-box;
}
.uni-easyinput__right {
// margin-left: 5px;
}
//
.is-required {
color: $uni-color-error;
}
.uni-error-message {
position: absolute;
bottom: -34rpx;
left: 0;
line-height: 24rpx;
color: $uni-color-error;
font-size: 24rpx;
text-align: left;
}
.uni-error-msg--boeder {
position: relative;
bottom: 0;
line-height: 44rpx;
}
.is-input-error-border {
border-color: $uni-color-error;
}
.uni-easyinput--border {
margin-bottom: 0;
padding: 20rpx 30rpx;
// padding-bottom: 0;
border-top: 2rpx #eee solid;
}
.uni-easyinput-error {
padding-bottom: 0;
}
.is-first-border {
border: none;
}
.is-disabled {
background-color: #eee;
}
</style>

@ -0,0 +1,440 @@
<template>
<view class="uni-forms-item" :class="{'uni-forms-item--border':border,'is-first-border':border&&isFirstBorder,'uni-forms-item-error':msg}">
<view class="uni-forms-item__inner" :class="['is-direction-'+labelPos,]">
<view v-if="label" class="uni-forms-item__label" :style="{width:labelWid+'px',justifyContent: justifyContent}">
<slot name="left">
<uni-icons v-if="leftIcon" class="label-icon" size="16" :type="leftIcon" :color="iconColor" />
<text v-if="required" class="is-required">*</text>
<text class="label-color">{{label}}</text>
</slot>
</view>
<view class="uni-forms-item__content" :class="{'is-input-error-border': msg}">
<slot></slot>
</view>
</view>
<view class="uni-error-message" :class="{'uni-error-msg--boeder':border}" :style="{
paddingLeft: (labelPos === 'left'? Number(labelWid)+5:5) + 'px'
}">{{ showMsg === 'undertext' ? msg:'' }}</view>
</view>
</template>
<script>
/**
* Field 输入框
* @description 此组件可以实现表单的输入与校验包括 "text" "textarea" 类型
* @tutorial https://ext.dcloud.net.cn/plugin?id=21001
* @property {Boolean} required 是否必填左边显示红色"*"默认false
* @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit 可选
* @value bind 发生变化时触发
* @value submit 提交时触发
* @property {String } leftIcon label左边的图标 uni-ui 的图标名称
* @property {String } iconColor 左边通过icon配置的图标的颜色默认#606266
* @property {String } label 输入框左边的文字提示
* @property {Number } labelWidth label的宽度单位px默认65
* @property {String } labelAlign = [left|center|right] label的文字对齐方式默认left
* @value left label 左侧显示
* @value center label 居中
* @value right label 右侧对齐
* @property {String } labelPosition = [top|left] label的文字的位置默认left
* @value top 顶部显示 label
* @value left 左侧显示 label
* @property {String } errorMessage 显示的错误提示内容如果为空字符串或者false则不显示错误信息
* @property {String } name 表单域的属性名在使用校验规则时必填
*/
export default {
name: "uniFormsItem",
props: {
//
custom: {
type: Boolean,
default: false
},
//
showMessage: {
type: Boolean,
default: true
},
name: String,
required: Boolean,
validateTrigger: {
type: String,
default: ''
},
leftIcon: String,
iconColor: {
type: String,
default: '#606266'
},
label: String,
// px
labelWidth: {
type: [Number, String],
default: ''
},
// left|center|right
labelAlign: {
type: String,
default: ''
},
// lable left-top-
labelPosition: {
type: String,
default: ''
},
errorMessage: {
type: [String, Boolean],
default: ''
}
},
data() {
return {
errorTop: false,
errorBottom: false,
labelMarginBottom: '',
errorWidth: '',
errMsg: '',
val: '',
labelPos: '',
labelWid: '',
labelAli: '',
showMsg: 'undertext',
border: false,
isFirstBorder: false
};
},
computed: {
msg() {
return this.errorMessage || this.errMsg;
},
fieldStyle() {
let style = {}
if (this.labelPos == 'top') {
style.padding = '0 0'
this.labelMarginBottom = '6px'
}
if (this.labelPos == 'left' && this.msg !== false && this.msg != '') {
style.paddingBottom = '0px'
this.errorBottom = true
this.errorTop = false
} else if (this.labelPos == 'top' && this.msg !== false && this.msg != '') {
this.errorBottom = false
this.errorTop = true
} else {
// style.paddingBottom = ''
this.errorTop = false
this.errorBottom = false
}
return style
},
// unicomputedstyle.justifyContent = 'center'
justifyContent() {
if (this.labelAli === 'left') return 'flex-start';
if (this.labelAli === 'center') return 'center';
if (this.labelAli === 'right') return 'flex-end';
}
},
watch: {
validateTrigger(trigger) {
this.formTrigger = trigger
}
},
created() {
this.form = this.getForm()
this.group = this.getForm('uniGroup')
this.formRules = []
this.formTrigger = this.validateTrigger
// if (this.form) {
this.form.childrens.push(this)
// }
this.init()
},
destroyed() {
if (this.form) {
this.form.childrens.forEach((item, index) => {
if (item === this) {
this.form.childrens.splice(index, 1)
}
})
}
},
methods: {
init() {
if (this.form) {
let {
formRules,
validator,
formData,
value,
labelPosition,
labelWidth,
labelAlign,
errShowType
} = this.form
this.labelPos = this.labelPosition ? this.labelPosition : labelPosition
this.labelWid = this.label ? (this.labelWidth ? this.labelWidth : labelWidth):0
this.labelAli = this.labelAlign ? this.labelAlign : labelAlign
// item
if (!this.form.isFirstBorder) {
this.form.isFirstBorder = true
this.isFirstBorder = true
}
// group item
if (this.group) {
if (!this.group.isFirstBorder) {
this.group.isFirstBorder = true
this.isFirstBorder = true
}
}
this.border = this.form.border
this.showMsg = errShowType
if (formRules) {
this.formRules = formRules[this.name] || {}
}
this.validator = validator
// if (this.name) {
// // formData[this.name] = value.hasOwnProperty(this.name) ? value[this.name] : this.form._getValue(this, '')
// }
} else {
this.labelPos = this.labelPosition || 'left'
this.labelWid = this.labelWidth || 65
this.labelAli = this.labelAlign || 'left'
}
},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
/**
* 移除该表单项的校验结果
*/
clearValidate() {
this.errMsg = ''
},
setValue(value){
if (this.name) {
if(this.errMsg) this.errMsg = ''
this.form.formData[this.name] = this.form._getValue(this, value)
}
},
/**
* 校验规则
* @param {Object} value
*/
async triggerCheck(value, callback) {
let promise = null;
this.errMsg = ''
// if no callback, return promise
if (callback && typeof callback !== 'function' && Promise) {
promise = new Promise((resolve, reject) => {
callback = function(valid) {
!valid ? resolve(valid) : reject(valid)
};
});
}
if (!this.validator) {
typeof callback === 'function' && callback(null);
if (promise) return promise
}
const isNoField = this.isRequired(this.formRules.rules || [])
let isTrigger = this.isTrigger(this.formRules.validateTrigger, this.validateTrigger, this.form.validateTrigger)
let result = null
if (!(!isTrigger)) {
result = this.validator && (await this.validator.validateUpdate({
[this.name]: value
}, this.form.formData))
}
//
if (!isNoField && !value) {
result = null
}
if (isTrigger && result && result.errorMessage) {
if (this.form.errShowType === 'toast') {
uni.showToast({
title: result.errorMessage || '校验错误',
icon: 'none'
})
}
if (this.form.errShowType === 'modal') {
uni.showModal({
title: '提示',
content: result.errorMessage || '校验错误'
})
}
}
this.errMsg = !result ? '' : result.errorMessage
this.form.validateCheck(result ? result : null)
typeof callback === 'function' && callback(result ? result : null);
if (promise) return promise
},
/**
* 触发时机
* @param {Object} event
*/
isTrigger(rule, itemRlue, parentRule) {
let rl = true;
// bind submit
if (rule === 'submit' || !rule) {
if (rule === undefined) {
if (itemRlue !== 'bind') {
if (!itemRlue) {
return parentRule === 'bind' ? true : false
}
return false
}
return true
}
return false
}
return true;
},
//
isRequired(rules) {
let isNoField = false
for (let i = 0; i < rules.length; i++) {
const ruleData = rules[i]
if (ruleData.required) {
isNoField = true
break
}
}
return isNoField
}
}
};
</script>
<style lang="scss" scoped>
.uni-forms-item {
position: relative;
// padding: 16px 14px;
text-align: left;
color: #333;
font-size: 28rpx;
margin-bottom: 44rpx;
background-color: #fff;
}
.uni-forms-item__inner {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
// flex-direction: row;
// align-items: center;
}
.is-direction-left {
flex-direction: row;
}
.is-direction-top {
flex-direction: column;
}
.uni-forms-item__label {
/* #ifndef APP-NVUE */
display: flex;
flex-shrink: 0;
/* #endif */
flex-direction: row;
align-items: center;
font-size: 28rpx;
color: #333;
width: 130rpx;
// line-height: 2;
// margin-top: 3px;
padding: 10rpx 0;
box-sizing: border-box;
height: 72rpx;
margin-right: 10rpx;
}
.uni-forms-item__content {
/* #ifndef APP-NVUE */
width: 100%;
// display: flex;
/* #endif */
// flex: 1;
// flex-direction: row;
// align-items: center;
box-sizing: border-box;
min-height: 72rpx;
}
.label-icon {
margin-right: 10rpx;
margin-top: -2px;
}
//
.is-required {
color: $uni-color-error;
}
.label-color{
color: #00B9FF;
}
.uni-error-message {
position: absolute;
bottom: -34rpx;
left: 0;
line-height: 24rpx;
color: $uni-color-error;
font-size: 24rpx;
text-align: left;
}
.uni-error-msg--boeder {
position: relative;
bottom: 0;
line-height: 44rpx;
}
.is-input-error-border {
border-color: $uni-color-error;
}
.uni-forms-item--border {
margin-bottom: 0;
padding: 20rpx 30rpx;
// padding-bottom: 0;
border-top: 2rpx #eee solid;
}
.uni-forms-item-error {
padding-bottom: 0;
}
.is-first-border {
border: none;
}
</style>

@ -0,0 +1,420 @@
<template>
<!-- -->
<view class="uni-forms" :class="{'uni-forms--top':!border}">
<form @submit.stop="submitForm" @reset="resetForm">
<slot></slot>
</form>
</view>
</template>
<script>
/**
* Forms 表单
* @description 由输入框选择器单选框多选框等控件组成用以收集校验提交数据
* @tutorial https://ext.dcloud.net.cn/plugin?id=2773
* @property {Object} rules 表单校验规则
* @property {String} validateTrigger = [bind|submit] 校验触发器方式 默认 submit 可选
* @value bind 发生变化时触发
* @value submit 提交时触发
* @property {String} labelPosition = [top|left] label 位置 默认 left 可选
* @value top 顶部显示 label
* @value left 左侧显示 label
* @property {String} labelWidth label 宽度默认 65px
* @property {String} labelAlign = [left|center|right] label 居中方式 默认 left 可选
* @value left label 左侧显示
* @value center label 居中
* @value right label 右侧对齐
* @property {String} errShowType = [undertext|toast|modal] 校验错误信息提示方式
* @value undertext 错误信息在底部显示
* @value toast 错误信息toast显示
* @value modal 错误信息modal显示
* @event {Function} submit 提交时触发
*/
import Vue from 'vue'
Vue.prototype.binddata = function(name, value, formName) {
if (formName) {
this.$refs[formName].setValue(name, value)
} else {
let formVm
for (let i in this.$refs) {
const vm = this.$refs[i]
if (vm && vm.$options && vm.$options.name === 'uniForms') {
formVm = vm
break
}
}
if (!formVm) return console.error('当前 uni-froms 组件缺少 ref 属性')
formVm.setValue(name, value)
}
}
import Validator from './validate.js'
export default {
name: 'uniForms',
props: {
value: {
type: Object,
default () {
return {}
}
},
//
rules: {
type: Object,
default () {
return {}
}
},
//
validateTrigger: {
type: String,
default: ''
},
// label top/left
labelPosition: {
type: String,
default: 'left'
},
// label px
labelWidth: {
type: [String, Number],
default: 65
},
// label left/center/right
labelAlign: {
type: String,
default: 'left'
},
errShowType: {
type: String,
default: 'undertext'
},
border: {
type: Boolean,
default: false
}
},
data() {
return {
formData: {}
};
},
watch: {
rules(newVal) {
this.init(newVal)
},
trigger(trigger) {
this.formTrigger = trigger
},
value: {
handler(newVal) {
if (this.isChildEdit) {
this.isChildEdit = false
return
}
this.childrens.forEach((item) => {
if (item.name) {
const formDataValue = newVal.hasOwnProperty(item.name) ? newVal[item.name] : null
this.formData[item.name] = this._getValue(item, formDataValue)
}
})
},
deep: true
}
},
created() {
let _this = this
this.childrens = []
this.inputChildrens = []
this.checkboxChildrens = []
this.formRules = []
this.init(this.rules)
},
methods: {
init(formRules) {
if (Object.keys(formRules).length > 0) {
this.formTrigger = this.trigger
this.formRules = formRules
if (!this.validator) {
this.validator = new Validator(formRules)
}
}
this.childrens.forEach((item) => {
item.init()
})
},
/**
* 设置校验规则
* @param {Object} formRules
*/
setRules(formRules) {
this.init(formRules)
},
/**
* 公开给用户使用
* 设置自定义表单组件 value
* @param {String} name 字段名称
* @param {String} value 字段值
*/
setValue(name, value, callback) {
let example = this.childrens.find(child => child.name === name)
if (!example) return null
this.isChildEdit = true
value = this._getValue(example, value)
this.formData[name] = value
example.val = value
this.$emit('input', Object.assign({}, this.value, this.formData))
return example.triggerCheck(value, callback)
},
/**
* TODO 表单提交 小程序暂不支持这种用法
* @param {Object} event
*/
submitForm(event) {
const value = event.detail.value
return this.validateAll(value || this.formData, 'submit')
},
/**
* 表单重置
* @param {Object} event
*/
resetForm(event) {
this.childrens.forEach(item => {
item.errMsg = ''
const inputComp = this.inputChildrens.find(child => child.rename === item.name)
if (inputComp) {
inputComp.errMsg = ''
inputComp.$emit('input', inputComp.multiple?[]:'')
}
})
this.isChildEdit = true
this.childrens.forEach((item) => {
if (item.name) {
this.formData[item.name] = this._getValue(item, '')
}
})
this.$emit('input', this.formData)
this.$emit('reset', event)
},
/**
* 触发表单校验通过 @validate 获取
* @param {Object} validate
*/
validateCheck(validate) {
if (validate === null) validate = null
this.$emit('validate', validate)
},
/**
* 校验所有或者部分表单
*/
async validateAll(invalidFields, type, callback) {
this.childrens.forEach(item => {
item.errMsg = ''
})
let promise;
if (!callback && typeof callback !== 'function' && Promise) {
promise = new Promise((resolve, reject) => {
callback = function(valid, invalidFields) {
!valid ? resolve(invalidFields) : reject(valid);
};
});
}
let fieldsValue = {}
let tempInvalidFields = Object.assign({}, invalidFields)
Object.keys(this.formRules).forEach(item => {
const values = this.formRules[item]
const rules = (values && values.rules) || []
let isNoField = false
for (let i = 0; i < rules.length; i++) {
const rule = rules[i]
if (rule.required) {
isNoField = true
break
}
}
// required
if (!isNoField && (!tempInvalidFields[item] && tempInvalidFields[item] !== false)) {
delete tempInvalidFields[item]
}
})
//
for (let i in this.formRules) {
for (let j in tempInvalidFields) {
if (i === j) {
fieldsValue[i] = tempInvalidFields[i]
}
}
}
let result = []
let example = null
if (this.validator) {
for (let i in fieldsValue) {
const resultData = await this.validator.validateUpdate({
[i]: fieldsValue[i]
}, this.formData)
if (resultData) {
example = this.childrens.find(child => child.name === resultData.key)
const inputComp = this.inputChildrens.find(child => child.rename === example.name)
if (inputComp) {
inputComp.errMsg = resultData.errorMessage
}
result.push(resultData)
if (this.errShowType === 'undertext') {
if (example) example.errMsg = resultData.errorMessage
} else {
if (this.errShowType === 'toast') {
uni.showToast({
title: resultData.errorMessage || '校验错误',
icon: 'none'
})
break
} else if (this.errShowType === 'modal') {
uni.showModal({
title: '提示',
content: resultData.errorMessage || '校验错误'
})
break
} else {
if (example) example.errMsg = resultData.errorMessage
}
}
}
}
}
if (Array.isArray(result)) {
if (result.length === 0) result = null
}
if (type === 'submit') {
this.$emit('submit', {
detail: {
value: invalidFields,
errors: result
}
})
} else {
this.$emit('validate', result)
}
callback && typeof callback === 'function' && callback(result, invalidFields)
if (promise && callback) {
return promise
} else {
return null
}
},
/**
* 外部调用方法
* 手动提交校验表单
* 对整个表单进行校验的方法参数为一个回调函数
*/
submit(callback) {
// Object.assign(this.formData,formData)
return this.validateAll(this.formData, 'submit', callback)
},
/**
* 外部调用方法
* 校验表单
* 对整个表单进行校验的方法参数为一个回调函数
*/
validate(callback) {
return this.validateAll(this.formData, '', callback)
},
/**
* 部分表单校验
* @param {Object} props
* @param {Object} cb
*/
validateField(props, callback) {
props = [].concat(props);
let invalidFields = {}
this.childrens.forEach(item => {
// item.parentVal((val, name) => {
if (props.indexOf(item.name) !== -1) {
invalidFields = Object.assign({}, invalidFields, {
[item.name]: this.formData[item.name]
})
}
// })
})
return this.validateAll(invalidFields, '', callback)
},
/**
* 对整个表单进行重置将所有字段值重置为初始值并移除校验结果
*/
resetFields() {
this.resetForm()
},
/**
* 移除表单项的校验结果传入待移除的表单项的 prop 属性或者 prop 组成的数组如不传则移除整个表单的校验结果
*/
clearValidate(props) {
props = [].concat(props);
this.childrens.forEach(item => {
const inputComp = this.inputChildrens.find(child => child.rename === item.name)
if (props.length === 0) {
item.errMsg = ''
if (inputComp) {
inputComp.errMsg = ''
}
} else {
if (props.indexOf(item.name) !== -1) {
item.errMsg = ''
if (inputComp) {
inputComp.errMsg = ''
}
}
}
})
},
// value
_getValue(item, value) {
const rules = item.formRules.rules || []
const isRuleNum = rules.find(val => val.format && this.type_filter(val.format))
const isRuleBool = rules.find(val => val.format && val.format === 'boolean' || val.format === 'bool')
// number
if (isRuleNum) {
value = value === '' || value === null ? null : Number(value)
}
//
if (isRuleBool) {
value = !value ? false : true
}
return value
},
//
type_filter(format) {
return format === 'int' || format === 'double' || format === 'number'
}
}
}
</script>
<style lang="scss" scoped>
.uni-forms {
overflow: hidden;
// padding: 10px 15px;
// background-color: #fff;
}
.uni-forms--top {
padding: 20rpx 30rpx;
// padding-top: 22px;
}
</style>

@ -0,0 +1,442 @@
var pattern = {
email: /^\S+?@\S+?\.\S+?$/,
url: new RegExp("^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", 'i')
};
const FORMAT_MAPPING = {
"int": 'number',
"bool": 'boolean',
"double": 'number',
"long": 'number',
"password": 'string'
}
function formatMessage(args, resources) {
var defaultMessage = ['label']
defaultMessage.forEach((item) => {
if (args[item] === undefined) {
args[item] = ''
}
})
let str = resources
for (let key in args) {
let reg = new RegExp('{' + key + '}')
str = str.replace(reg, args[key])
}
return str
}
function isEmptyValue(value, type) {
if (value === undefined || value === null) {
return true;
}
if (typeof value === 'string' && !value) {
return true;
}
if (Array.isArray(value) && !value.length) {
return true;
}
if (type === 'object' && !Object.keys(value).length) {
return true;
}
return false;
}
const types = {
integer(value) {
return types.number(value) && parseInt(value, 10) === value;
},
string(value) {
return typeof value === 'string';
},
number(value) {
if (isNaN(value)) {
return false;
}
return typeof value === 'number';
},
"boolean": function (value) {
return typeof value === 'boolean';
},
"float": function (value) {
return types.number(value) && !types.integer(value);
},
array(value) {
return Array.isArray(value);
},
object(value) {
return typeof value === 'object' && !types.array(value);
},
date(value) {
var v
if (value instanceof Date) {
v = value;
} else {
v = new Date(value);
}
return typeof v.getTime === 'function' && typeof v.getMonth === 'function' && typeof v.getYear === 'function' && !isNaN(v.getTime());
},
timestamp(value) {
if (!this.integer(value) || Math.abs(value).toString().length > 16) {
return false
}
return this.date(value);
},
email(value) {
return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255;
},
url(value) {
return typeof value === 'string' && !!value.match(pattern.url);
},
pattern(reg, value) {
try {
return new RegExp(reg).test(value);
} catch (e) {
return false;
}
},
method(value) {
return typeof value === 'function';
}
}
class RuleValidator {
constructor(message) {
this._message = message
}
async validateRule(key, value, data, allData) {
var result = null
let rules = key.rules
let hasRequired = rules.findIndex((item) => {
return item.required
})
if (hasRequired < 0) {
if (value === null || value === undefined) {
return result
}
if (typeof value === 'string' && !value.length) {
return result
}
}
var message = this._message
if (rules === undefined) {
return message['default']
}
for (var i = 0; i < rules.length; i++) {
let rule = rules[i]
let vt = this._getValidateType(rule)
if (key.label !== undefined) {
Object.assign(rule, {
label: key.label
})
}
if (RuleValidatorHelper[vt]) {
result = RuleValidatorHelper[vt](rule, value, message)
if (result != null) {
break
}
}
if (rule.validateExpr) {
let now = Date.now()
let resultExpr = rule.validateExpr(value, allData, now)
if (resultExpr === false) {
result = this._getMessage(rule, rule.errorMessage || this._message['default'])
break
}
}
if (rule.validateFunction) {
result = await this.validateFunction(rule, value, data, allData, vt)
if (result !== null) {
break
}
}
}
return result
}
async validateFunction(rule, value, data, allData, vt) {
let result = null
try {
let callbackMessage = null
const res = await rule.validateFunction(rule, value, allData || data, (message) => {
callbackMessage = message
})
if (callbackMessage || (typeof res === 'string' && res) || res === false) {
result = this._getMessage(rule, callbackMessage || res, vt)
}
} catch (e) {
result = this._getMessage(rule, e.message, vt)
}
return result
}
_getMessage(rule, message, vt) {
return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default'])
}
_getValidateType(rule) {
// TODO
var result = ''
if (rule.required) {
result = 'required'
} else if (rule.format) {
result = 'format'
} else if (rule.range) {
result = 'range'
} else if (rule.maximum || rule.minimum) {
result = 'rangeNumber'
} else if (rule.maxLength || rule.minLength) {
result = 'rangeLength'
} else if (rule.pattern) {
result = 'pattern'
}
return result
}
}
const RuleValidatorHelper = {
required(rule, value, message) {
if (rule.required && isEmptyValue(value, rule.format || typeof value)) {
return formatMessage(rule, rule.errorMessage || message.required);
}
return null
},
range(rule, value, message) {
const { range, errorMessage } = rule;
let list = new Array(range.length);
for (let i = 0; i < range.length; i++) {
const item = range[i];
if (types.object(item) && item.value !== undefined) {
list[i] = item.value;
} else {
list[i] = item;
}
}
let result = false
if (Array.isArray(value)) {
result = (new Set(value.concat(list)).size === list.length);
} else {
if (list.indexOf(value) > -1) {
result = true;
}
}
if (!result) {
return formatMessage(rule, errorMessage || message['enum']);
}
return null
},
rangeNumber(rule, value, message) {
if (!types.number(value)) {
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
}
let { minimum, maximum, exclusiveMinimum, exclusiveMaximum } = rule;
let min = exclusiveMinimum ? value <= minimum : value < minimum;
let max = exclusiveMaximum ? value >= maximum : value > maximum;
if (minimum !== undefined && min) {
return formatMessage(rule, rule.errorMessage || message['number'].min)
} else if (maximum !== undefined && max) {
return formatMessage(rule, rule.errorMessage || message['number'].max)
} else if (minimum !== undefined && maximum !== undefined && (min || max)) {
return formatMessage(rule, rule.errorMessage || message['number'].range)
}
return null
},
rangeLength(rule, value, message) {
if (!types.string(value) && !types.array(value)) {
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
}
let min = rule.minLength;
let max = rule.maxLength;
let val = value.length;
if (min !== undefined && val < min) {
return formatMessage(rule, rule.errorMessage || message['length'].min)
} else if (max !== undefined && val > max) {
return formatMessage(rule, rule.errorMessage || message['length'].max)
} else if (min !== undefined && max !== undefined && (val < min || val > max)) {
return formatMessage(rule, rule.errorMessage || message['length'].range)
}
return null
},
pattern(rule, value, message) {
if (!types['pattern'](rule.pattern, value)) {
return formatMessage(rule, rule.errorMessage || message.pattern.mismatch);
}
return null
},
format(rule, value, message) {
var customTypes = Object.keys(types);
var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : rule.format;
if (customTypes.indexOf(format) > -1) {
if (!types[format](value)) {
return formatMessage(rule, rule.errorMessage || message.types[format]);
}
}
return null
}
}
class SchemaValidator extends RuleValidator {
constructor(schema, options) {
super(SchemaValidator.message);
this._schema = schema
this._options = options || null
}
updateSchema(schema) {
this._schema = schema
}
async validate(data, allData) {
let result = this._checkFieldInSchema(data)
if (!result) {
result = await this.invokeValidate(data, false, allData)
}
return result.length ? result[0] : null
}
async validateAll(data, allData) {
let result = this._checkFieldInSchema(data)
if (!result) {
result = await this.invokeValidate(data, true, allData)
}
return result
}
async validateUpdate(data, allData) {
let result = this._checkFieldInSchema(data)
if (!result) {
result = await this.invokeValidateUpdate(data, false, allData)
}
return result.length ? result[0] : null
}
async invokeValidate(data, all, allData) {
let result = []
let schema = this._schema
for (let key in schema) {
let value = schema[key]
let errorMessage = await this.validateRule(value, data[key], data, allData)
if (errorMessage != null) {
result.push({
key,
errorMessage
})
if (!all) break
}
}
return result
}
async invokeValidateUpdate(data, all, allData) {
let result = []
for (let key in data) {
let errorMessage = await this.validateRule(this._schema[key], data[key], data, allData)
if (errorMessage != null) {
result.push({
key,
errorMessage
})
if (!all) break
}
}
return result
}
_checkFieldInSchema(data) {
var keys = Object.keys(data)
var keys2 = Object.keys(this._schema)
if (new Set(keys.concat(keys2)).size === keys2.length) {
return ''
}
return [{
key: 'invalid',
errorMessage: SchemaValidator.message['defaultInvalid']
}]
}
}
function Message() {
return {
default: '验证错误',
defaultInvalid: '字段超出范围',
required: '{label}必填',
'enum': '{label}超出范围',
whitespace: '{label}不能为空',
date: {
format: '{label}日期{value}格式无效',
parse: '{label}日期无法解析,{value}无效',
invalid: '{label}日期{value}无效'
},
types: {
string: '{label}类型无效',
array: '{label}类型无效',
object: '{label}类型无效',
number: '{label}类型无效',
date: '{label}类型无效',
boolean: '{label}类型无效',
integer: '{label}类型无效',
float: '{label}类型无效',
regexp: '{label}无效',
email: '{label}类型无效',
url: '{label}类型无效'
},
length: {
min: '{label}长度不能少于{minLength}',
max: '{label}长度不能超过{maxLength}',
range: '{label}必须介于{minLength}和{maxLength}之间'
},
number: {
min: '{label}不能小于{minimum}',
max: '{label}不能大于{maximum}',
range: '{label}必须介于{minimum}and{maximum}之间'
},
pattern: {
mismatch: '{label}格式不匹配'
}
};
}
SchemaValidator.message = new Message();
export default SchemaValidator

@ -0,0 +1,127 @@
<template>
<view class="uni-group" :class="['uni-group--'+mode ,margin?'group-margin':'']" :style="{marginTop: `${top}px` }">
<slot name="title">
<view v-if="title" class="uni-group__title" :style="{'padding-left':border?'30px':'15px'}">
<text class="uni-group__title-text">{{ title }}</text>
</view>
</slot>
<view class="uni-group__content" :class="{'group-conent-padding':border}">
<slot />
</view>
</view>
</template>
<script>
/**
* Group 分组
* @description 表单字段分组
* @tutorial https://ext.dcloud.net.cn/plugin?id=21002
* @property {String} title 主标题
* @property {Number} top 分组间隔
*/
export default {
name: 'uniGroup',
props: {
title: {
type: String,
default: ''
},
top: {
type: [Number, String],
default: 10
},
mode: {
type: String,
default: 'default'
}
},
data() {
return {
margin: false,
border: false
}
},
watch: {
title(newVal) {
if (uni.report && newVal !== '') {
uni.report('title', newVal)
}
}
},
created() {
this.form = this.getForm()
if (this.form) {
this.margin = true
this.border = this.form.border
}
},
methods: {
/**
* 获取父元素实例
*/
getForm() {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== 'uniForms') {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
onClick() {
this.$emit('click')
}
}
}
</script>
<style lang="scss" scoped>
.uni-group {
background: #fff;
margin-top: 20rpx;
// border: 1px red solid;
}
.group-margin {
margin: 0 -30rpx;
}
.uni-group__title {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
align-items: center;
padding-left: 30rpx;
height: 80rpx;
background-color: $uni-bg-color-grey;
font-weight: normal;
color: $uni-text-color;
}
.uni-group__content {
padding: 30rpx 0;
// padding-bottom: 5px;
background-color: #FFF;
}
.group-conent-padding {
padding: 0 30rpx;
}
.uni-group__title-text {
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.distraction {
flex-direction: row;
align-items: center;
}
.uni-group--card {
margin: 20rpx;
border-radius: 10rpx;
overflow: hidden;
box-shadow: 0 0 10rpx 2rpx rgba($color: #000000, $alpha: 0.08);
}
</style>

File diff suppressed because one or more lines are too long

@ -0,0 +1,132 @@
export default {
"pulldown": "\ue588",
"refreshempty": "\ue461",
"back": "\ue471",
"forward": "\ue470",
"more": "\ue507",
"more-filled": "\ue537",
"scan": "\ue612",
"qq": "\ue264",
"weibo": "\ue260",
"weixin": "\ue261",
"pengyouquan": "\ue262",
"loop": "\ue565",
"refresh": "\ue407",
"refresh-filled": "\ue437",
"arrowthindown": "\ue585",
"arrowthinleft": "\ue586",
"arrowthinright": "\ue587",
"arrowthinup": "\ue584",
"undo-filled": "\ue7d6",
"undo": "\ue406",
"redo": "\ue405",
"redo-filled": "\ue7d9",
"bars": "\ue563",
"chatboxes": "\ue203",
"camera": "\ue301",
"chatboxes-filled": "\ue233",
"camera-filled": "\ue7ef",
"cart-filled": "\ue7f4",
"cart": "\ue7f5",
"checkbox-filled": "\ue442",
"checkbox": "\ue7fa",
"arrowleft": "\ue582",
"arrowdown": "\ue581",
"arrowright": "\ue583",
"smallcircle-filled": "\ue801",
"arrowup": "\ue580",
"circle": "\ue411",
"eye-filled": "\ue568",
"eye-slash-filled": "\ue822",
"eye-slash": "\ue823",
"eye": "\ue824",
"flag-filled": "\ue825",
"flag": "\ue508",
"gear-filled": "\ue532",
"reload": "\ue462",
"gear": "\ue502",
"hand-thumbsdown-filled": "\ue83b",
"hand-thumbsdown": "\ue83c",
"hand-thumbsup-filled": "\ue83d",
"heart-filled": "\ue83e",
"hand-thumbsup": "\ue83f",
"heart": "\ue840",
"home": "\ue500",
"info": "\ue504",
"home-filled": "\ue530",
"info-filled": "\ue534",
"circle-filled": "\ue441",
"chat-filled": "\ue847",
"chat": "\ue263",
"mail-open-filled": "\ue84d",
"email-filled": "\ue231",
"mail-open": "\ue84e",
"email": "\ue201",
"checkmarkempty": "\ue472",
"list": "\ue562",
"locked-filled": "\ue856",
"locked": "\ue506",
"map-filled": "\ue85c",
"map-pin": "\ue85e",
"map-pin-ellipse": "\ue864",
"map": "\ue364",
"minus-filled": "\ue440",
"mic-filled": "\ue332",
"minus": "\ue410",
"micoff": "\ue360",
"mic": "\ue302",
"clear": "\ue434",
"smallcircle": "\ue868",
"close": "\ue404",
"closeempty": "\ue460",
"paperclip": "\ue567",
"paperplane": "\ue503",
"paperplane-filled": "\ue86e",
"person-filled": "\ue131",
"contact-filled": "\ue130",
"person": "\ue101",
"contact": "\ue100",
"images-filled": "\ue87a",
"phone": "\ue200",
"images": "\ue87b",
"image": "\ue363",
"image-filled": "\ue877",
"location-filled": "\ue333",
"location": "\ue303",
"plus-filled": "\ue439",
"plus": "\ue409",
"plusempty": "\ue468",
"help-filled": "\ue535",
"help": "\ue505",
"navigate-filled": "\ue884",
"navigate": "\ue501",
"mic-slash-filled": "\ue892",
"search": "\ue466",
"settings": "\ue560",
"sound": "\ue590",
"sound-filled": "\ue8a1",
"spinner-cycle": "\ue465",
"download-filled": "\ue8a4",
"personadd-filled": "\ue132",
"videocam-filled": "\ue8af",
"personadd": "\ue102",
"upload": "\ue402",
"upload-filled": "\ue8b1",
"starhalf": "\ue463",
"star-filled": "\ue438",
"star": "\ue408",
"trash": "\ue401",
"phone-filled": "\ue230",
"compose": "\ue400",
"videocam": "\ue300",
"trash-filled": "\ue8dc",
"download": "\ue403",
"chatbubble-filled": "\ue232",
"chatbubble": "\ue202",
"cloud-download": "\ue8e4",
"cloud-upload-filled": "\ue8e5",
"cloud-upload": "\ue8e6",
"cloud-download-filled": "\ue8e9",
"headphones":"\ue8bf",
"shop":"\ue609"
}

File diff suppressed because one or more lines are too long

Binary file not shown.

@ -0,0 +1,210 @@
<template>
<view class="uni-pagination">
<view class="uni-pagination__btns">
<view class="uni-pagination__btn" :class="currentIndex === 1 ? 'uni-pagination--disabled' : 'uni-pagination--enabled'"
:hover-class="currentIndex === 1 ? '' : 'uni-pagination--hover'" :hover-start-time="20" :hover-stay-time="70"
@click="clickLeft">
<template v-if="showIcon===true || showIcon === 'true'">
<uni-icons color="#000" size="20" type="arrowleft" />
</template>
<template v-else><text class="uni-pagination__child-btn">{{ prevText }}</text></template>
</view>
<view class="uni-pagination__btn" :class="currentIndex === maxPage ? 'uni-pagination--disabled' : 'uni-pagination--enabled'"
:hover-class="currentIndex === maxPage ? '' : 'uni-pagination--hover'" :hover-start-time="20" :hover-stay-time="70"
@click="clickRight">
<template v-if="showIcon===true || showIcon === 'true'">
<uni-icons color="#000" size="20" type="arrowright" />
</template>
<template v-else><text class="uni-pagination__child-btn">{{ nextText }}</text></template>
</view>
</view>
<view class="uni-pagination__num">
<view class="uni-pagination__num-current">
<text class="uni-pagination__num-current-text" style="color:#007aff">{{ currentIndex }}</text><text class="uni-pagination__num-current-text">/{{ maxPage || 0 }}</text>
</view>
</view>
</view>
</template>
<script>
import uniIcons from '../uni-icons/uni-icons.vue'
export default {
name: 'UniPagination',
components: {
uniIcons
},
props: {
prevText: {
type: String,
default: '上一页'
},
nextText: {
type: String,
default: '下一页'
},
current: {
type: [Number, String],
default: 1
},
total: { //
type: [Number, String],
default: 0
},
pageSize: { //
type: [Number, String],
default: 10
},
showIcon: { // icon
type: [Boolean, String],
default: false
}
},
data() {
return {
currentIndex: 1
}
},
computed: {
maxPage() {
let maxPage = 1
let total = Number(this.total)
let pageSize = Number(this.pageSize)
if (total && pageSize) {
maxPage = Math.ceil(total / pageSize)
}
return maxPage
}
},
watch: {
current(val) {
this.currentIndex = +val
}
},
created() {
this.currentIndex = +this.current
},
methods: {
clickLeft() {
if (Number(this.currentIndex) === 1) {
return
}
this.currentIndex -= 1
this.change('prev')
},
clickRight() {
if (Number(this.currentIndex) === this.maxPage) {
return
}
this.currentIndex += 1
this.change('next')
},
change(e) {
this.$emit('change', {
type: e,
current: this.currentIndex
})
}
}
}
</script>
<style lang="scss" scoped>
.uni-pagination {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
/* #ifdef APP-NVUE */
padding: 0 40rpx;
/* #endif */
width: 700rpx;
position: relative;
overflow: hidden;
flex-direction: row;
justify-content: center;
align-items: center;
}
.uni-pagination__btns {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex: 1;
justify-content: space-between;
align-items: center;
flex-direction: row;
}
.uni-pagination__btn {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
width: 60rpx;
height: 60rpx;
line-height: 60rpx;
border-radius: 50%;
font-size: $uni-font-size-base;
position: relative;
background-color: $uni-bg-color-grey;
flex-direction: row;
justify-content: center;
align-items: center;
text-align: center;
border-width: 2rpx;
border-style: solid;
border-color: $uni-border-color;
}
.uni-pagination__child-btn {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
font-size: $uni-font-size-base;
position: relative;
flex-direction: row;
justify-content: center;
align-items: center;
text-align: center;
}
.uni-pagination__num {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
position: absolute;
left: 300rpx;
top: 0;
flex-direction: row;
justify-content: center;
align-items: center;
width: 100rpx;
height: 60rpx;
line-height: 60rpx;
font-size: $uni-font-size-base;
color: $uni-text-color;
}
.uni-pagination__num-current {
/* #ifndef APP-NVUE */
display: flex;
/* #endif */
flex-direction: row;
}
.uni-pagination__num-current-text {
font-size: 30rpx;
}
.uni-pagination--enabled {
color: #333333;
opacity: 1;
}
.uni-pagination--disabled {
opacity: 0.3;
}
.uni-pagination--hover {
color: rgba(0, 0, 0, .6);
background-color: $uni-bg-color-hover;
}
</style>

@ -0,0 +1,324 @@
<template>
<view class="uni-datetime-picker">
<view @click="tiggerTimePicker">
<slot>
<view class="uni-datetime-picker-timebox uni-datetime-picker-flex">
{{pickValue}}<view v-if="!pickValue" class="uni-datetime-picker-time">请选择<!-- 时间 --></view>
<template>
<uni-icons class="content-clear-icon is-textarea-icon" type="clear" :size="clearSize"
color="#c0c4cc" @click="onClear"></uni-icons>
</template>
<view class="uni-datetime-picker-down-arrow"></view>
</view>
</slot>
</view>
<view v-if="visible" class="uni-datetime-picker-mask" @click="initTimePicker"></view>
<view v-if="visible" class="uni-datetime-picker-popup">
<view class="uni-title">
请选择<!-- 和时间 -->
</view>
<!-- <picker @change="bindPickerChange" :value="index" :range="array" :range-key="'name'" class="uni-datetime-picker-hyphen">
</picker> -->
<picker-view class="uni-datetime-picker-view" :indicator-style="indicatorStyle" :value="pickValue" @change="bindPickerChange">
<picker-view-column class="uni-datetime-picker-colon">
<view class="uni-datetime-picker-item" v-for="(item,index) in pickData" :key="index">{{item.name}}</view>
</picker-view-column>
</picker-view>
<view class="uni-datetime-picker-btn">
<!-- <view class="" @click="clearTime">重置</view> -->
<view class="uni-datetime-picker-btn-group">
<view class="uni-datetime-picker-cancel" @click="tiggerTimePicker">取消</view>
<view class="" @click="setTime">确定</view>
</view>
</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
visible: false,
pickName: '',
years: [],
year: 1900,
indicatorStyle: `height: 50px;`,
}
},
props: {
pickData: {
type: Object,
default: []
},
pickValue: {
type: [String, Number],
default: ''
},
type: {
type: String,
default: 'datetime-local'
},
timestamp: {
type: Boolean,
default: false
},
value: {
type: [String, Number],
default: ''
}
},
watch: {
value(newValue) {
this.parseValue(this.value)
this.initTime()
}
},
created() {
this.form = this.getForm('uniForms')
this.formItem = this.getForm('uniFormsItem')
if (this.formItem) {
if (this.formItem.name) {
this.rename = this.formItem.name
this.form.inputChildrens.push(this)
}
}
},
mounted() {
this.parseValue(this.value)
if (this.value) {
this.initTime()
}
},
methods: {
//
tiggerTimePicker() {
this.visible = !this.visible
},
bindPickerChange(e){
const val = e.detail.value
this.pickValue = this.pickData[val[0]].value
this.pickName = this.pickData[val[0]].name
console.log(this.pickValue)
},
onClear(){
this.pickValue = '',
this.pickName = ''
},
setTime() {
this.$emit('change', this.pickName)
this.tiggerTimePicker()
},
/**
* 获取父元素实例
*/
getForm(name = 'uniForms') {
let parent = this.$parent;
let parentName = parent.$options.name;
while (parentName !== name) {
parent = parent.$parent;
if (!parent) return false
parentName = parent.$options.name;
}
return parent;
},
parseDateTime(datetime) {
let defaultDate = null
if (!datetime) {
defaultDate = new Date()
} else {
defaultDate = new Date(datetime)
}
this.year = defaultDate.getFullYear()
if (this.year < this.minYear || this.year > this.maxYear) {
const now = Date.now()
this.parseDateTime(now)
return
}
this.month = defaultDate.getMonth() + 1
this.day = defaultDate.getDate()
this.hour = defaultDate.getHours()
this.minute = defaultDate.getMinutes()
this.second = defaultDate.getSeconds()
},
parseValue(defaultTime) {
if (Number(defaultTime)) {
defaultTime = parseInt(defaultTime)
}
this.parseDateTime(defaultTime)
},
bindDateChange(e) {
const val = e.detail.value
this.year = this.years[val[0]]
this.month = this.months[val[1]]
this.day = this.days[val[2]]
},
bindTimeChange(e) {
const val = e.detail.value
this.hour = this.hours[val[0]]
this.minute = this.minutes[val[1]]
this.second = this.seconds[val[2]]
},
initTimePicker() {
// if (!this.time) {
// this.parseValue()
// }
this.parseValue(this.value)
this.visible = !this.visible
},
clearTime() {
this.time = ''
this.tiggerTimePicker()
},
initTime() {
this.time = this.createDomSting()
if (!this.timestamp) {
this.formItem && this.formItem.setValue(this.time)
this.$emit('change', this.time)
} else {
this.formItem && this.formItem.setValue(this.createTimeStamp(this.time))
this.$emit('change', this.createTimeStamp(this.time))
}
},
createTimeStamp(time) {
return Date.parse(new Date(time))
},
createDomSting() {
const yymmdd = this.year +
'-' +
(this.month < 10 ? '0' + this.month : this.month) +
'-' +
(this.day < 10 ? '0' + this.day : this.day)
// +
// ' ' +
// (this.hour < 10 ? '0' + this.hour : this.hour) +
// ':' +
// (this.minute < 10 ? '0' + this.minute : this.minute) +
// ':' +
// (this.second < 10 ? '0' + this.second : this.second)
return yymmdd
}
}
}
</script>
<style>
.uni-datetime-picker-view {
width: 100%;
height: 130px;
margin-top: 60rpx;
}
.uni-datetime-picker-item {
line-height: 100rpx;
text-align: center;
}
.uni-title{
text-align: center;
}
.uni-datetime-picker-btn {
margin-top: 120rpx;
display: flex;
justify-content: center;
align-items: center;
color: #00B9FF;
cursor: pointer;
}
.uni-datetime-picker-btn-group {
display: flex;
}
.uni-datetime-picker-cancel {
margin-right: 60rpx;
}
.uni-datetime-picker-mask {
position: fixed;
bottom: 0px;
top: 0px;
left: 0px;
right: 0px;
background-color: rgba(0, 0, 0, 0.4);
transition-duration: 0.3s;
z-index: 998;
}
.uni-datetime-picker-popup {
border-radius: 16rpx;
padding: 60rpx;
width: 540rpx;
background-color: #fff;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
transition-duration: 0.3s;
z-index: 999;
}
.uni-datetime-picker-time {
color: grey;
}
.uni-datetime-picker-colon::after {
content: ':';
position: absolute;
top: 106rpx;
right: 0;
}
.uni-datetime-picker-hyphen::after {
content: '-';
position: absolute;
top: 106rpx;
right: -4rpx;
}
.uni-datetime-picker-timebox {
border: 2rpx solid #E5E5E5;
border-radius: 40rpx;
padding: 14rpx 40rpx;
box-sizing: border-box;
cursor: pointer;
width: 80%;
margin-left: 20%;
}
//
.uni-datetime-picker-down-arrow {
display :inline-block;
position: relative;
width: 40rpx;
height: 30rpx;
}
.uni-datetime-picker-down-arrow::after {
display: inline-block;
content: " ";
height: 18rpx;
width: 18rpx;
border-width: 0 2rpx 2rpx 0;
border-color: #E5E5E5;
border-style: solid;
transform: matrix(0.71, 0.71, -0.71, 0.71, 0, 0);
transform-origin: center;
transition: transform .3s;
position: absolute;
top: 50%;
right: 10rpx;
margin-top: -10rpx;
}
.uni-datetime-picker-flex {
display: flex;
justify-content: space-between;
}
</style>

@ -0,0 +1,38 @@
//#ifdef H5
const BASEURL = ''
//#endif
//#ifndef H5
// const BASEURL = getApp().globalData.url
//#endif
function previepdf(url,ext) {
uni.showLoading({
title: '加载中'
});
uni.downloadFile({
url: url,
success(res) {
let path = res.tempFilePath;
uni.openDocument({
filePath: path,
fileType: ext,
success() {
uni.hideLoading();
},
fail() {
uni.hideLoading();
}
});
}
});
}
module.exports = {
previepdf
};

@ -0,0 +1,101 @@
/**
* GET请求封装
*/
function get(url, data = {}) {
return request(url, data, 'GET');
}
function put(url, data = {}) {
return request(url, data, 'put');
}
function deletes(url, data = {}) {
return request(url, data, 'delete');
}
/**
* POST请求封装
*/
function post(url, data = {}) {
return request(url, data, 'POST');
}
/**
* 微信的request
*/
//#ifdef H5
const BASEURL = ''
//#endif
//#ifndef H5
const BASEURL = getApp().globalData.url
//#endif
function request(url, data = {}, method = "GET") {
return new Promise(function(resolve, reject) {
uni.showLoading({
title: "加载中"
});
uni.request({
url: BASEURL + url,
method: method,
data: data,
header: {
'mini-session': uni.getStorageSync('session') || '',
'Accept': 'application/json',
'X-Requested-With': 'XMLHttpRequest',
'Content-Type': 'application/json'
},
success(res) {
if (res.data) {
if (res.data.error) {
if (res.data.error.code != 404) {
uni.showToast({
title: res.data.error.message,
icon: 'none'
});
}
if (res.data.error.code == 9999) {
uni.clearStorageSync('session');
uni.showToast({
title: '登录失效,请重新登录',
icon: 'none'
});
setTimeout(() => {
uni.reLaunch({
url: '/pages/login/login'
});
}, 1000);
}
reject(res.data.error.message);
} else {
resolve(res.data);
}
} else {
resolve(null);
uni.hideLoading();
}
},
fail(res) {
console.log(res);
uni.showToast({
title: '请求超时,请重试',
icon: 'none'
}); // wx.hideLoading()
}
});
});
}
module.exports = {
get,
post,
put,
deletes,
};

@ -0,0 +1,167 @@
//#ifdef H5
const BASEURL = ''
//#endif
//#ifndef H5
//#endif
import Vue from 'vue'
const BASEURL = Vue.prototype.baseUrl // 取得全局变量
function uploadImage() {
return new Promise((resolve, reject) => {
// 原本的上传图片
// uni.chooseImage({
// count: 1,
// sizeType: ['original', 'compressed'],
// sourceType: ['album', 'camera'],
// 修改为上传图片或者视频
uni.chooseMedia({
count: 1,// 待处理,多个上传需要封装循环上传
mediaType: ['image','video'],
sourceType: ['album', 'camera'],
maxDuration: 30,
camera: 'back',
success(ress) {
if (ress.tempFiles.length > 0) {
console.log(ress,'看下是啥格式')
// 修改--改为选择图片或者视频的接口
// const tempFilePaths = ress.tempFiles[0].path
const tempFilePaths = ress.tempFiles[0].tempFilePath
const size = ress.tempFiles[0].size
if (size < 8388608) {
uni.showLoading({
title:'上传中'
})
// let imageurl = tempFilePaths
let pdfurl = ""
uni.uploadFile({
url: BASEURL+'/api-guarantee/dg-apply-amount-info/uploadFile',
filePath: tempFilePaths,
header: {
// "mini-session": uni.getStorageSync('session'),
"token": uni.getStorageSync('token'),
"Content-Type": "multipart/form-data;boundary=----WebKitFormBoundaryi8lPVoSysovJLNqi",
"Accept": "application/json"
},
name: 'file',
// formData: {
// 'user': 'test'
// },
success(res) {
if (res.statusCode == 500) {
// 判断图片或者视频未做
uni.showToast({
title: '上传失败',
icon: 'none'
})
reject('失败')
} else {
uni.showToast({
title: '上传成功',
icon: 'success'
})
if (res.data) {
let imageurl = JSON.parse(res.data).data[0]
let index= imageurl.lastIndexOf(".");
let ext = imageurl.substr(index+1);
resolve({
imageurl, // 图片地址
ext: ext
})
}
}
}
})
} else {
uni.showToast({
title: '超出限制大小',
icon: "none"
})
}
} else {
uni.showToast({
title: '文件不存在',
icon: "none"
})
}
}
})
})
}
function uploadPdf() {
return new Promise((resolve, reject) => {
uni.chooseMessageFile({
count: 1,
type: 'file',
success(ress) {
if (ress.tempFiles.length > 0) {
if (ress.tempFiles[0].size < 8388608) {
uni.showLoading({
title:'上传中'
})
// let pdfurl = ress.tempFiles[0].name
uni.uploadFile({
url:BASEURL+'/api-guarantee/dg-apply-amount-info/uploadFile',
filePath: ress.tempFiles[0].path,
name: 'file',
header: {
// "mini-session": uni.getStorageSync('session'),
"token": uni.getStorageSync('token'),
"Content-Type": "multipart/form-data; boundary=----WebKitFormBoundaryi8lPVoSysovJLNqi",
"Accept": "application/json"
},
// formData: {
// 'user': 'test'
// },
success(res) {
if (res.statusCode != 500) {
uni.showToast({
title: '文件上传成功',
icon: 'success'
})
if (res.data) {
let pdfurl = JSON.parse(res.data).data[0]
let index= pdfurl.lastIndexOf(".");
let ext = pdfurl.substr(index+1);
resolve({
pdfurl, // 文件地址
ext: ext
})
} else {
reject('失败')
}
} else {
uni.showToast({
title: '服务器错误',
icon: 'none'
})
}
}
})
} else {
uni.showToast({
title: '超出限制大小',
icon: "none"
})
return
}
} else {
uni.showToast({
title: '文件不存在',
icon: "none"
})
}
}
})
})
}
module.exports = {
uploadImage,
uploadPdf
}

@ -0,0 +1,347 @@
<template>
<view>
<!-- 菜单 (悬浮,预先隐藏)-->
<me-tabs v-if="isShowSticky" v-model="tabIndex" :fixed="true" :tabs="tabs" @change="tabChange"></me-tabs>
<mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption" @scroll="scroll" @topclick="topClick">
<!-- 菜单 (在mescroll-uni中不能使用fixed,否则iOS滚动时会抖动, 所以需在mescroll-uni之外存在一个一样的菜单) -->
<view id="tabInList">
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- 搜索 -->
<view class="cu-bar bg-white search" :class="scrolltop ? 'fixed' : ''" :style="[{top:CustomBar + 'px'}]">
<view class="search-form round">
<input @input="searchList" v-model="listParams.customerNumberOrName" type="text" placeholder="输入搜索的关键词" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<!-- 数据列表 -->
<!-- <index-list :list="chargeList"></index-list> -->
<!-- <touch-list :list="chargeList" :listTouchDirection="listTouchDirection" :listTouchStart="listTouchStart" :modalName="modalName"
@TouchStart="ListTouchStart" @TouchMove="ListTouchMove" @TouchEnd="ListTouchEnd"
></touch-list> -->
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in listData" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list">
<view class="charge" :id="'index-' + item.name" :data-index="item.name">
<navigator v-if="[65,66,67].includes(item.roleId)" @click="handleGe(item,'watch')" hover-class='none' :url="'/guaranteePages/pages/InformationSee/InformationSee'"
class="nav-li" navigateTo :style="[{animation: 'show ' + ((index+1)*0.2+1) + 's 1'}]">
<view class="charge-title flex-between">
<text>{{item.name}}的贷款申请</text>
</view>
<view class="charge-text">
<view>
<text>业务编号</text>
<text class="mgl30">{{item.businessCode}}</text>
</view>
<view>
<text>客户名称</text>
<text class="mgl30">{{item.name}}</text>
</view>
<view>
<text>联系电话</text>
<text class="mgl30">{{item.phone}}</text>
</view>
<view>
<text>业务类别</text>
<text class="mgl30">{{item.businessType}}</text>
</view>
<view>
<text>申请额度万元</text>
<text class="mgl30">{{item.applyAmount}}</text>
</view>
<view>
<text>申请期限</text>
<text class="mgl30">{{item.applyTime}}</text>
</view>
<view>
<text>申请日期</text>
<text class="mgl30">{{item.createTime}}</text>
</view>
<view>
<text>审批状态</text>
<text class="mgl30">{{item.statusVal}}</text>
</view>
<view>
<text>业务状态</text>
<text class="mgl30">{{item.businessStatusVal}}</text>
</view>
<view>
<text>操作状态</text>
<text class="mgl30">{{item.operationType}}</text>
</view>
<!-- <view class="flex-between">
<text class="time-text">{{item.time}}</text>
<text class="status-text" :style="'background-color:'+item.status.bgColor+';color:'+item.status.textColor">{{item.status.text}}</text>
</view> -->
</view>
</navigator>
</view>
</view>
<view class="move foot-btn sideBtn">
<button v-show="item.status===1&&item.operatingStatus == 1||item.status===4&&item.operatingStatus == 1" v-if="[66].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleGe(item,'survey')">调查</button>
<button v-show="item.status===0&&item.operatingStatus == 1" v-if="[65].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round suc-btn" type="primary" size="mini" @click="handleGe(item,'none',true)">指派</button>
<view class="margin-top">
<button v-show="item.status===1&&item.operatingStatus == 1||item.status===4&&item.operatingStatus == 1" v-if="[65,67].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleGe(item,'audit')">审核</button>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
export default {
mixins: [MescrollMixin], // 使mixin
data() {
return {
upOption: {
// mescroll-uni onScroll: true, @scroll="scroll"; mescroll-bodyonPageScroll
// onScroll: true // , false (true,@scroll="scroll")
},
tabs:[
{name:'全部', num:1, y:0, curPageLen:0, hasNext:true},
{name:'待指派', num:1, y:0, curPageLen:0, hasNext:true},
{name:'审核中', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已审核', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已拒绝', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已驳回', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已撤销', num:1, y:0, curPageLen:0, hasNext:true},
],
tabIndex: 0, //
preIndex: 0, //
navTop: null, // nav (,)
isShowSticky: false, //
TabCur: 0,
scrollLeft: 0,
CustomBar: this.CustomBar,
listCurID: '',
listData: [],
total: 1,
scrolltop: false,
listTouchStart: 0,
listTouchDirection: null,
modalName: null,
listParams:{
customerNumberOrName:"",//
page:1,
size:10
},
status:'',//
};
},
onShow() {
// this.getList()//
this.upCallback({num:1,size:10,search:''})
},
mounted() {
// this.getList()//
// this.listData.map(e =>{
// e.status = this.core.auditStatus(e.status)
// })
},
destroyed() {
},
methods: {
changeTab(index){
this.TabCur = index;
},
/*下拉刷新的回调 */
downCallback() {
// ,
// loadSwiper();
// , ( page.num=1, upCallback )
this.mescroll.resetUpScroll()
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
this.$http.get('/api-guarantee/dg-message-investigation/messageList',{
page: page.num?page.num:1,
size: page.size?page.size:10,
status:this.status?this.status:'',
customerNumberOrName:page.search?page.search:''
}).then(res => {
if(page.num == 1) this.listData = []; //
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.status)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operatingStatus)
})
this.listData = this.listData.concat(res.data.list); //
this.mescroll.endSuccess(res.data.list); //
}).catch(()=>{
//,
this.mescroll.endErr();
})
},
// --
searchList(e){
let that = this
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.timer = setTimeout(function () {
that.upCallback({num:1,size:10,search:e.detail.value})
},800)
},
// nav (0, navTop)
setNavTop(){
let view = uni.createSelectorQuery().select('#tabInList');
view.boundingClientRect(data => {
this.navTop = data.top //
}).exec();
},
// mescroll-uni (uponScroll:true)
// mescroll-bodyonPageScroll
scroll(){
// : ,
if (this.mescroll.getScrollTop() >= this.navTop) {
this.isShowSticky = true //
} else {
this.isShowSticky = false //
}
},
// ,,
topClick(){
this.isShowSticky = false
},
//
tabChange (index) {
if(index==0){
this.status = ''
}else{
this.status = `${index-1}`
}
this.upCallback({
num:1,size:10
})
//
let preTab = this.tabs[this.preIndex]
preTab.y = this.mescroll.getScrollTop(); //
this.preIndex = index;
//
let curTab = this.tabs[index]
if (!curTab.goods) {
// ,
this.isChangeTab = true;
this.mescroll.resetUpScroll()
} else{
// ,
this.mescroll.setPageNum(curTab.num + 1); //
this.mescroll.endSuccess(curTab.curPageLen, curTab.hasNext); //
this.$nextTick(()=>{
this.mescroll.scrollTo(curTab.y, 0) //
})
}
},
// ListTouch
ListTouchStart(e) {
this.listTouchStart = e.touches[0].pageX
},
// ListTouch
ListTouchMove(e) {
this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
},
// ListTouch
ListTouchEnd(e) {
if (this.listTouchDirection == 'left') {
this.modalName = e.currentTarget.dataset.target
} else {
this.modalName = null
}
this.listTouchDirection = null
},
//
//
getList(val){
if(val==='search'){
this.listParams.page = 1
this.listParams.size = 10
}
this.$http.get('/api-guarantee/dg-message-investigation/messageList',this.listParams).then(res=>{
if(res.data&&res.data.list&&res.data.list.length!==0){
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.status)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operatingStatus)
})
this.listData = res.data.list
}else{
this.listData = []
}
})
},
//
handleGe(item,str,assign){
if(assign){// -
this.goto('/guaranteePages/pages/assignInformation/assignInformation')//
uni.removeStorageSync('handleGe')
}else{
uni.setStorageSync('handleGe',str)//
}
if(!assign&&str!=='watch'){//
this.goto('/guaranteePages/pages/InformationSee/InformationSee')
}
uni.setStorageSync('applyMsg',JSON.stringify(item))// --
uni.setStorageSync('businessId',item.businessId)// id
},
},
// // 使mescroll-bodyonPageScroll
// onPageScroll(e){
// if (e.scrollTop >= this.navTop) {
// this.isShowSticky = true //
// } else {
// this.isShowSticky = false //
// }
// },
}
</script>
<style lang="scss" scoped>
.cu-bar .search-form{
border: 2rpx solid #00B9FF;
border-radius: 20rpx;
background-color: #fff;
margin: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
margin-top: 20rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.add-customer{
padding: 0 50rpx;
button{
background-color: #00B9FF;
border-radius: 30rpx;
.mar-icon{
margin-right: 10rpx;
}
}
}
.sideBtn{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
</style>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

@ -0,0 +1,361 @@
<template>
<view>
<!-- 菜单 (悬浮,预先隐藏)-->
<!-- <me-tabs v-if="isShowSticky" v-model="tabIndex" :fixed="true" :tabs="tabs" @change="tabChange"></me-tabs> -->
<mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption" @scroll="scroll" @topclick="topClick">
<!-- 菜单 (在mescroll-uni中不能使用fixed,否则iOS滚动时会抖动, 所以需在mescroll-uni之外存在一个一样的菜单) -->
<view id="tabInList">
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- 搜索 -->
<view class="cu-bar bg-white search" :class="scrolltop ? 'fixed' : ''" :style="[{top:CustomBar + 'px'}]">
<view class="search-form round">
<input @input="searchList" v-model="serach" type="text" placeholder="输入搜索的关键词" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<view class="add-customer mat15 flex-between">
<text>{{total}}</text>
<view v-if="roleArr.includes('57')">
<button class="mini-btn" type="primary" size="mini" @click="handleApply(item,'new')"><text class="cuIcon-add mar-icon"></text>添加</button>
</view>
</view>
<!-- 数据列表 -->
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in businessApplyListData" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list">
<view class="charge" :id="'index-' + item.name" :data-index="index">
<navigator v-if="[57,58].includes(item.roleId)" @click="handleApply(item,'watch')" hover-class='none' :url="'/guaranteePages/pages/addApplication/addApplication'"
class="nav-li" navigateTo :style="[{animation: 'show ' + ((index+1)*0.2+1) + 's 1'}]">
<view class="charge-title flex-between">
<text>{{item.name}}</text>
</view>
<view class="charge-text">
<view>
<text>业务编号</text>
<text class="mgl30">{{item.businessCode}}</text>
</view>
<view>
<text>客户名称</text>
<text class="mgl30">{{item.name}}</text>
</view>
<view>
<text>联系电话</text>
<text class="mgl30">{{item.phone}}</text>
</view>
<view>
<text>业务类别</text>
<text class="mgl30">{{item.businessType}}</text>
</view>
<view>
<text>申请额度(万元)</text>
<text class="mgl30">{{item.applyAmount||''}}</text>
</view>
<view>
<text>申请期限</text>
<text class="mgl30">{{item.applyTime}}</text>
</view>
<view>
<text>申请日期</text>
<text class="mgl30">{{item.createTime}}</text>
</view>
<view>
<text>审批状态</text>
<text class="mgl30">{{item.statusVal}}</text>
</view>
<view>
<text>业务状态</text>
<text class="mgl30">{{item.businessStatusVal}}</text>
</view>
<view>
<text>操作状态</text>
<text class="mgl30">{{item.operationType}}</text>
</view>
<view>
<text>提单人</text>
<text class="mgl30">{{item.account}}</text>
</view>
<view>
<text>所属部门</text>
<text class="mgl30">{{item.deptName}}</text>
</view>
</view>
</navigator>
</view>
</view>
<view class="move foot-btn sideBtn">
<view class="">
<button v-show="item.status===1" v-if="[57].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr mgr6" type="primary" size="mini" @click="repaelPop(item)">撤销</button>
<button v-show="item.status===1" v-if="[58].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleApply(item,'audit')">审核</button>
</view>
<view>
<button v-show="item.status===4||item.status===5||item.status == 6&&item.operatingStatus == 3&&item.businessStatus !== 2" v-if="[57].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleApply(item,'change')">修改</button>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
// import core from "@/util/core.js"
export default {
mixins: [MescrollMixin], // 使mixin
data() {
return {
upOption: {
// mescroll-uni onScroll: true, @scroll="scroll"; mescroll-bodyonPageScroll
// onScroll: true // , false (true,@scroll="scroll")
},
tabs:[
{name:'待指派', num:1, y:0, curPageLen:0, hasNext:true},
{name:'审核中', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已审核', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已拒绝', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已驳回', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已撤销', num:1, y:0, curPageLen:0, hasNext:true},
{name:'草稿', num:1, y:0, curPageLen:0, hasNext:true},
],
tabIndex: 0, //
preIndex: 0, //
navTop: null, // nav (,)
isShowSticky: false, //
TabCur: 0,
scrollLeft: 0,
CustomBar: this.CustomBar,
listCurID: '',
businessApplyListData: [],
page: 1,
pageSize: 10,
total: 0,
CustomerNumberOrName: '',
scrolltop: false,
listTouchStart: 0,
listTouchDirection: null,
modalName: null,
roleArr:'',//
serach:'',//
status:'',//
};
},
onShow() {
//
this.upCallback({num:1,size:10,search:''})//
},
created() {
this.roleArr = uni.getStorageSync('roleArr')
},
destroyed() {},//
methods: {
// -
getBusinessApply(){
let params = {
page: this.page?this.page:1,
size: this.pageSize?this.pageSize:10,
CustomerNumberOrName: this.CustomerNumberOrName
}
this.$http.get('/api-guarantee/dg-apply-amount-info/businessApplicationList',params).then(res => {
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.status)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operatingStatus)
})
this.businessApplyListData = res.data.list
this.total = res.data.totalCount
}).catch(function (error) {});
},
//
repaelPop(item){
let _this = this
uni.showModal({
title: '提示',
content: '是否撤销该申请',
success: function (res) {
if (res.confirm) {
_this.$http.get('/api-guarantee/dg-apply-amount-info/revokeBusinessApplication',{id: item.id}).then(res => {
uni.showToast({title: '撤销成功'})
_this.upCallback({num:1,size:10,search:''})
}).catch(function (error) {});
} else if (res.cancel) {}
}
});
},
changeTab(index){
this.TabCur = index;
},
/*下拉刷新的回调 */
downCallback() {
// ,
// loadSwiper();
// , ( page.num=1, upCallback )
this.mescroll.resetUpScroll()
// this.getBusinessApply()
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
this.$http.get('/api-guarantee/dg-apply-amount-info/businessApplicationList',{
page: page.num,
size: page.size,
status:this.status,
CustomerNumberOrName:page.search?page.search:''
}).then(res => {
if(page.num == 1) this.businessApplyListData = []; //
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.status)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operatingStatus)
})
this.businessApplyListData = this.businessApplyListData.concat(res.data.list); //
this.total = res.data.totalCount
this.mescroll.endSuccess(res.data.list); //
}).catch(()=>{
//,
this.mescroll.endErr();
})
},
// --
searchList(e){
let that = this
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.timer = setTimeout(function () {
that.upCallback({num:1,size:10,search:e.detail.value})
},800)
},
// nav (0, navTop)
setNavTop(){
let view = uni.createSelectorQuery().select('#tabInList');
view.boundingClientRect(data => {
if(data) this.navTop = data.top //
}).exec();
},
// mescroll-uni (uponScroll:true)
// mescroll-bodyonPageScroll
scroll(){
// : ,
if (this.mescroll.getScrollTop() >= this.navTop) {
this.isShowSticky = true //
} else {
this.isShowSticky = false //
}
},
// ,,
topClick(){
this.isShowSticky = false
},
//
tabChange (index) {
this.status = index+"";
this.upCallback({num:1,size:10})
//
// let preTab = this.tabs[this.preIndex]
// preTab.y = this.mescroll.getScrollTop(); //
// //
// let curTab = this.tabs[index]
// if (!curTab.goods) {
// // ,
// this.isChangeTab = true;
// this.mescroll.resetUpScroll()
// } else{
// // ,
// this.mescroll.setPageNum(curTab.num + 1); //
// this.mescroll.endSuccess(curTab.curPageLen, curTab.hasNext); //
// this.$nextTick(()=>{
// this.mescroll.scrollTo(curTab.y, 0) //
// })
// this.getBusinessApply()
// }
},
// ListTouch
ListTouchStart(e) {
this.listTouchStart = e.touches[0].pageX
},
// ListTouch
ListTouchMove(e) {
this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
},
// ListTouch
ListTouchEnd(e) {
if (this.listTouchDirection == 'left') {
this.modalName = e.currentTarget.dataset.target
} else {
this.modalName = null
}
this.listTouchDirection = null
},
//
handleApply(item,str){
uni.setStorageSync('applyHandle',str)//
if(str==='new'){//
this.goto('/guaranteePages/pages/addApplication/addApplication')
uni.removeStorageSync('applyMsg')
uni.removeStorageSync('businessId')
}else{
uni.setStorageSync('applyMsg',JSON.stringify(item))//
uni.setStorageSync('businessId',item.businessId)// id
}
if(str!='new'&&str!='watch'){//
this.goto('/guaranteePages/pages/addApplication/addApplication')
}
},
},
// 使mescroll-bodyonPageScroll
onPageScroll(e){
if (e.scrollTop >= this.navTop) {
this.isShowSticky = true //
} else {
this.isShowSticky = false //
}
},
}
</script>
<style lang="scss" scoped>
.cu-bar .search-form{
border: 2rpx solid #00B9FF;
border-radius: 20rpx;
background-color: #fff;
margin: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
margin-top: 20rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.add-customer{
padding: 0 50rpx;
button{
background-color: #00B9FF;
border-radius: 30rpx;
.mar-icon{
margin-right: 10rpx;
}
}
}
.sideBtn{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
/deep/ .cu-list>.cu-item .move view{
flex: none;
margin-top: 10rpx;
}
</style>

@ -0,0 +1,341 @@
<template>
<view>
<!-- 菜单 (悬浮,预先隐藏)-->
<me-tabs v-if="isShowSticky" v-model="tabIndex" :fixed="true" :tabs="tabs" @change="tabChange"></me-tabs>
<mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption" @scroll="scroll" @topclick="topClick">
<!-- 菜单 (在mescroll-uni中不能使用fixed,否则iOS滚动时会抖动, 所以需在mescroll-uni之外存在一个一样的菜单) -->
<view id="tabInList">
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- 搜索 -->
<view class="cu-bar bg-white search" :class="scrolltop ? 'fixed' : ''" :style="[{top:CustomBar + 'px'}]">
<view class="search-form round">
<input @input="searchList" v-model="listParams.customerNumberOrName" type="text" placeholder="输入搜索的关键词" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in listData" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list">
<view class="charge" :id="'index-' + item.name" :data-index="item.name">
<navigator @click="handleGe(item,'watch')" hover-class='none' :url="'/guaranteePages/pages/assetSee/assetSee'"
class="nav-li" navigateTo :style="[{animation: 'show ' + ((index+1)*0.2+1) + 's 1'}]">
<view class="charge-title flex-between">
<text>{{item.name}}</text>
</view>
<view class="charge-text">
<view>
<text>业务编号</text>
<text class="mgl30">{{item.businessCode}}</text>
</view>
<view>
<text>客户名称</text>
<text class="mgl30">{{item.name}}</text>
</view>
<view>
<text>联系电话</text>
<text class="mgl30">{{item.phone}}</text>
</view>
<view>
<text>业务类别</text>
<text class="mgl30">{{item.businessType}}</text>
</view>
<view>
<text>申请额度万元</text>
<text class="mgl30">{{item.applyAmount}}</text>
</view>
<view>
<text>申请期限</text>
<text class="mgl30">{{item.applyTime}}</text>
</view>
<view>
<text>申请日期</text>
<text class="mgl30">{{item.createTime}}</text>
</view>
<view>
<text>审批状态</text>
<text class="mgl30">{{item.statusVal}}</text>
</view>
<view>
<text>业务状态</text>
<text class="mgl30">{{item.businessStatusVal}}</text>
</view>
<view>
<text>操作状态</text>
<text class="mgl30">{{item.operationType}}</text>
</view>
<!-- <view class="flex-between">
<text class="time-text">{{item.time}}</text>
<text class="status-text" :style="'background-color:'+item.status.bgColor+';color:'+item.status.textColor">{{item.status.text}}</text>
</view> -->
</view>
</navigator>
</view>
</view>
<view class="move foot-btn sideBtn">
<view class="">
<button v-show="item.status===1&&item.operatingStatus == 1||item.status===4&&item.operatingStatus == 1" v-if="[63].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleGe(item,'survey')">调查</button>
<button v-show="item.status===0&&item.operatingStatus == 1" v-if="[62].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round suc-btn" type="primary" size="mini" @click="handleGe(item,'none',true)">指派</button>
</view>
<view class="">
<button v-show="item.status===1&&item.operatingStatus == 1||item.status===4&&item.operatingStatus == 1" v-if="[62,64].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleGe(item,'audit')">审核</button>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
export default {
mixins: [MescrollMixin], // 使mixin
data() {
return {
upOption: {
// mescroll-uni onScroll: true, @scroll="scroll"; mescroll-bodyonPageScroll
// onScroll: true // , false (true,@scroll="scroll")
},
tabs:[
{name:'全部', num:1, y:0, curPageLen:0, hasNext:true},
{name:'待指派', num:1, y:0, curPageLen:0, hasNext:true},
{name:'审核中', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已审核', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已拒绝', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已驳回', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已撤销', num:1, y:0, curPageLen:0, hasNext:true},
],
tabIndex: 0, //
preIndex: 0, //
navTop: null, // nav (,)
isShowSticky: false, //
TabCur: 0,
scrollLeft: 0,
CustomBar: this.CustomBar,
listCurID: '',
listData: [],
status:'',//
total: 1,
scrolltop: false,
listTouchStart: 0,
listTouchDirection: null,
modalName: null,
listParams:{
customerNumberOrName:"",//
page:1,
size:10
}
};
},
onShow() {
this.upCallback({
num:1,size:10,serach:''
})
},
mounted() {
},
methods: {
changeTab(index){
this.TabCur = index;
},
/*下拉刷新的回调 */
downCallback() {
// ,
// loadSwiper();
// , ( page.num=1, upCallback )
this.mescroll.resetUpScroll()
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
this.$http.post('/api-guarantee/dg-assets-investigation/assetsList',{
page: page.num?page.num:1,
size: page.size?page.size:10,
status:this.status?this.status:'',
customerNumberOrName:page.search?page.search:''
}).then(res => {
if(page.num == 1) this.listData = []; //
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.status)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operatingStatus)
})
this.listData = this.listData.concat(res.data.list); //
this.mescroll.endSuccess(res.data.list); //
}).catch(()=>{
//,
this.mescroll.endErr();
})
},
// --
searchList(e){
let that = this
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.timer = setTimeout(function () {
that.upCallback({num:1,size:10,search:e.detail.value})
},800)
},
// nav (0, navTop)
setNavTop(){
let view = uni.createSelectorQuery().select('#tabInList');
view.boundingClientRect(data => {
this.navTop = data.top //
}).exec();
},
// mescroll-uni (uponScroll:true)
// mescroll-bodyonPageScroll
scroll(){
// : ,
if (this.mescroll.getScrollTop() >= this.navTop) {
this.isShowSticky = true //
} else {
this.isShowSticky = false //
}
},
// ,,
topClick(){
this.isShowSticky = false
},
//
tabChange (index) {
// this.listParams.status = index
if(index==0){
this.status = ''
}else{
this.status = `${index-1}`
}
this.upCallback({
num:1,size:10,serach:''
})
//
let preTab = this.tabs[this.preIndex]
preTab.y = this.mescroll.getScrollTop(); //
this.preIndex = index;
//
let curTab = this.tabs[index]
if (!curTab.goods) {
// ,
this.isChangeTab = true;
this.mescroll.resetUpScroll()
} else{
// ,
this.mescroll.setPageNum(curTab.num + 1); //
this.mescroll.endSuccess(curTab.curPageLen, curTab.hasNext); //
this.$nextTick(()=>{
this.mescroll.scrollTo(curTab.y, 0) //
})
}
},
// ListTouch
ListTouchStart(e) {
this.listTouchStart = e.touches[0].pageX
},
// ListTouch
ListTouchMove(e) {
this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
},
// ListTouch
ListTouchEnd(e) {
if (this.listTouchDirection == 'left') {
this.modalName = e.currentTarget.dataset.target
} else {
this.modalName = null
}
this.listTouchDirection = null
},
//
getList(val){
if(val==='search'){
this.listParams.page = 1
this.listParams.size = 10
}
this.$http.get('/api-guarantee/dg-assets-investigation/assetsList',this.listParams).then(res=>{
if(res.data&&res.data.list&&res.data.list.length!==0){
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.status)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operatingStatus)
})
this.listData = res.data.list
}else{
this.listData = []
}
})
},
//
handleGe(item,str,assign){
if(assign){// -
this.goto('/guaranteePages/pages/assignCommissioner/assignCommissioner')//
uni.removeStorageSync('handleGe')
}else{
uni.setStorageSync('handleGe',str)//
}
if(!assign&&str!=='watch'){//
this.goto('/guaranteePages/pages/assetSee/assetSee')
}
uni.setStorageSync('applyMsg',JSON.stringify(item))//
uni.setStorageSync('businessId',item.businessId)// id
},
},
// 使mescroll-bodyonPageScroll
onPageScroll(e){
if (e.scrollTop >= this.navTop) {
this.isShowSticky = true //
} else {
this.isShowSticky = false //
}
},
}
</script>
<style lang="scss" scoped>
.cu-bar .search-form{
border: 2rpx solid #00B9FF;
border-radius: 20rpx;
background-color: #fff;
margin: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
margin-top: 20rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.add-customer{
padding: 0 50rpx;
button{
background-color: #00B9FF;
border-radius: 30rpx;
.mar-icon{
margin-right: 10rpx;
}
}
}
.sideBtn{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,289 @@
<template>
<view class="ass-content">
<view class="flex-align-start">
<view class="ass-label">
<text>A角选择</text>
</view>
<view class="assign-view">
<view class="cu-bar search">
<view class="search-form round">
<input type="text" v-model="aSearch" placeholder="请输入" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<mix-tree @checkedRund = "getCheckedA" :fliter="aSearch" :list="data" @treeItemClick="handleCheck"></mix-tree>
</view>
</view>
<view class="flex-align-start mat15">
<view class="ass-label">
<text>B角选择</text>
</view>
<view class="assign-view">
<view class="cu-bar search">
<view class="search-form round">
<input type="text" v-model="bSearch" placeholder="请输入" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<mix-tree @checkedRund = "getCheckedB" :fliter="bSearch" :list="data" @treeItemClick="handleCheck"></mix-tree>
</view>
</view>
<view class="flex-align-start mat15">
<view class="ass-label">
<text>已选员工</text>
</view>
<view class="AB-input">
<view class="flex">
<input disabled type="text" :value="'A角: '+A" placeholder="请选择A角"/>
<!-- <text class="cuIcon-roundclosefill" @click="A = ''"></text> -->
</view>
<view class="flex mat15">
<input disabled type="text" :value="'B角: '+B" placeholder="请选择B角"/>
<!-- <text class="cuIcon-roundclosefill" @click="B = ''"></text> -->
</view>
</view>
</view>
<view class="mat40 flex-justify-center ass-foot-btn">
<button class="cu-btn block def-btn margin-tb-sm lg round mar15" @click="geDesignateAB">确定</button>
<button class="cu-btn block cancel-btn margin-tb-sm lg round mal15" @click="back()">取消</button>
</view>
</view>
</template>
<script>
import LyTree from '@/components/ly-tree/ly-tree.vue'
// import mix-tree from '@/components/mix-tree/mix-tree.vue'
export default {
components: {
LyTree
},
data() {
return {
filterText: '',
A: '',
B: '',
data: [],
defaultProps: {
children: 'children',
label: 'name'
},
bSearch:'',
aSearch:'',
getItem:{},// item
arrDatas:[],//
aID:'',
bID:'',
};
},
created() {
if(uni.getStorageSync('decideIndex')){
let data = JSON.parse(uni.getStorageSync('decideIndex'))
this.getItem.businessId = data.businessId
this.getItem.companyId = data.companyId
this.getItem.id = data.detailId
uni.removeStorageSync('decideIndex')
}else{
this.getItem = JSON.parse(uni.getStorageSync('applyMsg'))
}
},
onHide() {
uni.removeStorageSync('applyMsg')
},
onShow() {
this.trees()//
},
onLoad() {
//
// this.$nextTick(() => {
// this.$refs.tree.setCheckedKeys([6])
// })
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
},
bSearch(val){
}
},
//
methods: {
// ""()obj
handleCheck(obj) {
},
//
trees(){
this.$http.get('/api-hrms//hrms/dept/empTrees').then(res=>{
this.data = res.data
this.disposeData(this.data)//
})
},
//
disposeData(data){// childrenchildren
let len = data.length
for(let i = 0;i<len;i++){
if(data[i].children){ // children---children
if(data[i].userDept){//
if(data[i].userDept.length!==0){//
let len = data[i].userDept.length
for(let a=0;a<len;a++){
data[i].userDept[a].name=data[i].userDept[a].account
}
}
data[i].children = [...data[i].children,...data[i].userDept]
data[i].children = [...new Set(data[i].children)]
}
if(data[i].children.length!==0){//
this.disposeData(data[i].children)
}
}else if(!data[i].children&&data[i].userDept&&data[i].userDept.length!==0){//children
data[i].children = []
let len = data[i].userDept.length
for(let a=0;a<len;a++){
data[i].userDept[a].name=data[i].userDept[a].account
}
data[i].children = [...data[i].children,...data[i].userDept]
data[i].children =[...new Set(data[i].children)]
}
}
},
//
getCheckedA(nameList,idsList){
if(idsList.length!==0){
this.aID = idsList[0]
}else{
this.aID = ''
}
if(nameList&&nameList.length!==0){
this.A = nameList[0]
}else{
this.A = ''
}
if(this.aID&&this.aID===this.bID){
this.aID = ''
return uni.showToast({title: '请勿选择相同的A/B角!',icon:'none'})
}
},
// b
getCheckedB(nameList,idsList){
this.B = nameList[0]
this.bID = idsList[0]
if(this.aID===this.bID){
this.bID = ''
return uni.showToast({title: '请勿选择相同的A/B角!',icon:'none'})
}
},
//
renderTreeList(list=[], rank=0, parentId=[]){
list.forEach(item=>{
this.arrDatas.push({
id: item.id,
name: item.name,
parentId, // id
rank, //
showChild: false, //
show: rank === 0 ,//
//
account: item.account?item.account:false,//
})
if(Array.isArray(item.children) && item.children.length > 0){
let parents = [...parentId];
parents.push(item.id);
this.renderTreeList(item.children, rank+1, parents);
}else{
this.arrDatas[this.arrDatas.length-1].lastRank = true;
}
})
},
// // --
// async filterNode(value,data,str) {
// if(!value){
// this.trees()
// return
// }
// let arr = JSON.parse(JSON.stringify(data))
// this.renderTreeList(data)//
// this.data = []
// this.arrDatas.forEach(e=>{//
// if (e.name.includes(value)){
// this.data.push(e)
// }
// })
// this.data = [...new Set(this.data)]
// },
// ab
geDesignateAB(){
let obj = {
businessId:this.getItem.businessId,
companyId:this.getItem.companyId,
id:this.getItem.id,
}
obj.empAId = this.aID
obj.empBId = this.bID
if(!obj.empAId||!obj.empBId){
return uni.showToast({title: '请选择角色后再确定!',icon:'none'})
}
this.$http.post('/api-guarantee/dg-guarantee-assign-user/assignCorners',obj).then(res=>{
uni.showToast({
title: "操作成功!"
})
this.back()
})
}
}
};
</script>
<style lang="scss" scoped>
.ass-content{
padding: 40rpx 40rpx;
.ass-foot-btn .cu-btn.lg{
padding: 0 100rpx;
}
}
.ass-label{
color: #00B9FF;
font-size: 28rpx;
margin-right: 40rpx;
}
.assign-view{
border-radius: 20rpx;
background-color: #fff;
box-shadow: 0 0 16rpx #ccc;
height: 600rpx;
overflow-x: hidden;
overflow-y: scroll;
}
.cu-bar .search-form{
border-radius: 20rpx;
background-color: #f5f5f5;
height: 72rpx;
line-height: 72rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.AB-input{
view{
background-color: #f5f5f5;
color: #666;
padding: 20rpx 40rpx;
border-radius: 20rpx;
text{
color: #B2B2B2;
}
}
}
</style>

@ -0,0 +1,251 @@
<template>
<view class="ass-content">
<view class="flex-align-start">
<view class="ass-label">
<text>资产部专员</text>
</view>
<view class="assign-view">
<view class="cu-bar search">
<view class="search-form round">
<input type="text" v-model="search" placeholder="请输入" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<mix-tree class="wd60" @checkedRund = "getChecked" :fliter="search" :list="data" @treeItemClick="handleCheck"></mix-tree>
</view>
</view>
<view class="flex-align-start mat15">
<view class="ass-label">
<text>已选调查专员</text>
</view>
<view class="AB-input">
<view class="flex">
<input disabled type="text" :value="name" placeholder="请选择调查专员"/>
<!-- <text class="cuIcon-roundclosefill" @click="A = ''"></text> -->
</view>
</view>
</view>
<view class="mat40 flex-justify-center ass-foot-btn">
<button class="cu-btn block def-btn margin-tb-sm lg round mar15" @click="assetAssign()">确定</button>
<button class="cu-btn block cancel-btn margin-tb-sm lg round mal15" @click="back()">取消</button>
</view>
</view>
</template>
<script>
import LyTree from '@/components/ly-tree/ly-tree.vue'
export default {
components: {
LyTree
},
data() {
return {
filterNodeData: null,
filterText: '',
name:'',//
ID:"",// id
data: [],//
defaultProps: {
children: 'children',
label: 'label'
},
search:'',//
getItem:{},//
};
},
props:{},
created() {
if(uni.getStorageSync('decideIndex')){
let data = JSON.parse(uni.getStorageSync('decideIndex'))
this.getItem.businessId = data.businessId
this.getItem.companyId = data.companyId
this.getItem.id = data.detailId
uni.removeStorageSync('decideIndex')
}else{
this.getItem = JSON.parse(uni.getStorageSync('applyMsg'))
}
},
onShow() {
this.trees()//
},
beforeDestroy() {
uni.removeStorageSync('applyMsg')
},
onLoad() {
//
// this.$nextTick(() => {
// this.$refs.tree.setCheckedKeys([6])
// })
},
watch: {
// filterText(val) {
// this.$refs.tree.filter(val);
// }
},
//
methods: {
// ""()obj
handleCheck(obj) {
// obj: {
// checkedKeys: [9, 5], // id
// checkedNodes: [{...}, {...}], //
// data: {...}, //
// halfCheckedKeys: [1, 4, 2], // id
// halfCheckedNodes: [{...}, {...}, {...}], //
// node: Node {...} //
// }
},
// // /
// handleRadioChange(obj) {
// },
// handleNodeClick(obj) {
// },
// //
// filterNode(value, data) {
// if (!value) return true;
// return data.label.indexOf(value) !== -1;
// },
//
trees(){
this.$http.get('/api-hrms//hrms/dept/empTrees').then(res=>{
this.data = res.data
this.disposeData(this.data)//
})
},
//
disposeData(data){// childrenchildren
let len = data.length
for(let i = 0;i<len;i++){
if(data[i].children){ // children---children
if(data[i].userDept){//
if(data[i].userDept.length!==0){//
let len = data[i].userDept.length
for(let a=0;a<len;a++){
data[i].userDept[a].name=data[i].userDept[a].account
}
}
data[i].children = [...data[i].children,...data[i].userDept]
data[i].children = [...new Set(data[i].children)]
}
if(data[i].children.length!==0){//
this.disposeData(data[i].children)
}
}else if(!data[i].children&&data[i].userDept&&data[i].userDept.length!==0){//children
data[i].children = []
let len = data[i].userDept.length
for(let a=0;a<len;a++){
data[i].userDept[a].name=data[i].userDept[a].account
}
data[i].children = [...data[i].children,...data[i].userDept]
data[i].children =[...new Set(data[i].children)]
}
}
},
// handleCheck(obj) {
// if (obj.node.checked) {
// this.placeholder = `"${obj.data.label}"`;
// this.filterNodeData = obj.data;
// } else {
// this.placeholder = ``;
// this.filterNodeData = null;
// }
// // filter(val, nodeData),AnodeDatatreeDataA
// this.$refs.tree.filter(this.filterText, this.filterNodeData);
// }
// id
getChecked(nameList,idsList){
if(idsList.length!==0){
this.ID = idsList[0]
}else{
this.ID = ''
}
if(nameList&&nameList.length!==0){
this.name = nameList[0]
}else{
this.name = ''
}
},
//
assetAssign(){
let obj = {
businessId:this.getItem.businessId,
companyId:this.getItem.companyId,
id:this.getItem.id,
}
if(!this.ID){
return uni.showToast({title: '请选择后再确定!',icon:'none'})
}
//
obj.empId = this.ID
this.$http.post('/api-guarantee/dg-assets-investigation/assignCorners',obj).then(res=>{
this.back()
uni.showToast({
title:'操作成功!'
})
}).catch(err=>{
uni.showToast({
title:err.data.message,icon:'none'
})
})
},
}
};
</script>
<style lang="scss" scoped>
.ass-content{
padding: 40rpx 40rpx;
.ass-foot-btn .cu-btn.lg{
padding: 0 100rpx;
}
}
.ass-label{
color: #00B9FF;
font-size: 28rpx;
margin-right: 40rpx;
}
.assign-view{
border-radius: 20rpx;
background-color: #fff;
box-shadow: 0 0 16rpx #ccc;
height: 800rpx;
overflow-x: hidden;
overflow-y: scroll;
}
.cu-bar .search-form{
border-radius: 20rpx;
background-color: #f5f5f5;
height: 72rpx;
line-height: 72rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.AB-input{
view{
background-color: #f5f5f5;
color: #666;
padding: 20rpx 40rpx;
border-radius: 20rpx;
text{
color: #B2B2B2;
}
}
}
</style>

@ -0,0 +1,237 @@
<template>
<view class="ass-content">
<view class="flex-align-start">
<view class="ass-label">
<text>信息部专员</text>
</view>
<view class="assign-view">
<view class="cu-bar search">
<view class="search-form round">
<input type="text" v-model="search" placeholder="请输入" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<mix-tree class="wd60" @checkedRund = "getChecked" :fliter="search" :list="data" @treeItemClick="handleCheck"></mix-tree>
</view>
</view>
<view class="flex-align-start mat15">
<view class="ass-label">
<text>已选调查专员</text>
</view>
<view class="AB-input">
<view class="flex">
<input disabled type="text" :value="name" placeholder="请选择调查专员"/>
<!-- <text class="cuIcon-roundclosefill" @click="A = ''"></text> -->
</view>
</view>
</view>
<view class="mat40 flex-justify-center ass-foot-btn">
<button class="cu-btn block def-btn margin-tb-sm lg round mar15" @click="assetAssign">确定</button>
<button class="cu-btn block cancel-btn margin-tb-sm lg round mal15" @click="back()">取消</button>
</view>
</view>
</template>
<script>
import LyTree from '@/components/ly-tree/ly-tree.vue'
export default {
components: {
LyTree
},
data() {
return {
filterNodeData: null,
search: '',//
name:'',//
data: [],
defaultProps: {
children: 'children',
label: 'label'
}
};
},
onShow() {
this.trees()//
},
onLoad() {
//
// this.$nextTick(() => {
// // this.$refs.tree.setCheckedKeys([6])
// })
},
created() {
if(uni.getStorageSync('decideIndex')){
let data = JSON.parse(uni.getStorageSync('decideIndex'))
this.getItem.businessId = data.businessId
this.getItem.companyId = data.companyId
this.getItem.id = data.detailId
uni.removeStorageSync('decideIndex')
}else{
this.getItem = JSON.parse(uni.getStorageSync('applyMsg'))
}
},
watch: {
search(val){
}
// filterText(val) {
// this.$refs.tree.filter(val);
// }
},
//
methods: {
// ""()obj
handleCheck(obj) {
},
// /
handleRadioChange(obj) {
},
handleNodeClick(obj) {
},
//
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
//
trees(){
this.$http.get('/api-hrms//hrms/dept/empTrees').then(res=>{
this.data = res.data
this.disposeData(this.data)//
})
},
//
disposeData(data){// childrenchildren
let len = data.length
for(let i = 0;i<len;i++){
if(data[i].children){ // children---children
if(data[i].userDept){//
if(data[i].userDept.length!==0){//
let len = data[i].userDept.length
for(let a=0;a<len;a++){
data[i].userDept[a].name=data[i].userDept[a].account
}
}
data[i].children = [...data[i].children,...data[i].userDept]
data[i].children = [...new Set(data[i].children)]
}
if(data[i].children.length!==0){//
this.disposeData(data[i].children)
}
}else if(!data[i].children&&data[i].userDept&&data[i].userDept.length!==0){//children
data[i].children = []
let len = data[i].userDept.length
for(let a=0;a<len;a++){
data[i].userDept[a].name=data[i].userDept[a].account
}
data[i].children = [...data[i].children,...data[i].userDept]
data[i].children =[...new Set(data[i].children)]
}
}
},
// id
getChecked(nameList,idsList){
if(idsList.length!==0){
this.ID = idsList[0]
}else{
this.ID = ''
}
if(nameList&&nameList.length!==0){
this.name = nameList[0]
}else{
this.name = ''
}
},
//
assetAssign(){
let obj = {
businessId:this.getItem.businessId,
companyId:this.getItem.companyId,
id:this.getItem.id,
}
if(!this.ID){
return uni.showToast({title: '请选择后再确定!',icon:'none'})
}
//
obj.empId = this.ID
uni.showLoading({
title:'提交中'
})
this.$http.post('/api-guarantee/dg-message-investigation/assignCorners',obj).then(res=>{
setTimeout(()=>{
uni.hideLoading()
this.back()
},500)
}).catch(err=>{
uni.showToast({
title:err.data.message,icon:'none'
})
})
},
// handleCheck(obj) {
// if (obj.node.checked) {
// this.placeholder = `"${obj.data.label}"`;
// this.filterNodeData = obj.data;
// } else {
// this.placeholder = ``;
// this.filterNodeData = null;
// }
// // filter(val, nodeData),AnodeDatatreeDataA
// this.$refs.tree.filter(this.filterText, this.filterNodeData);
// }
}
};
</script>
<style lang="scss" scoped>
.ass-content{
padding: 40rpx 40rpx;
.ass-foot-btn .cu-btn.lg{
padding: 0 100rpx;
}
}
.ass-label{
color: #00B9FF;
font-size: 28rpx;
margin-right: 40rpx;
}
.assign-view{
border-radius: 20rpx;
background-color: #fff;
box-shadow: 0 0 16rpx #ccc;
height: 800rpx;
overflow-x: hidden;
overflow-y: scroll;
}
.cu-bar .search-form{
border-radius: 20rpx;
background-color: #f5f5f5;
height: 72rpx;
line-height: 72rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.AB-input{
view{
background-color: #f5f5f5;
color: #666;
padding: 20rpx 40rpx;
border-radius: 20rpx;
text{
color: #B2B2B2;
}
}
}
</style>

@ -0,0 +1,236 @@
<template>
<view class="ass-content">
<view class="flex-align-start">
<view class="ass-label">
<text>合规调查专员</text>
</view>
<view class="assign-view">
<view class="cu-bar search">
<view class="search-form round">
<input type="text" v-model="search" placeholder="请输入" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<mix-tree class="wd60" @checkedRund = "getChecked" :fliter="search" :list="data" @treeItemClick="handleCheck"></mix-tree>
<!-- <ly-tree :tree-data="data"
:props="defaultProps"
ref="tree"
node-key="id"
show-radio
checkOnlyLeaf
@check="handleCheck"
@radio-change="handleRadioChange"
@node-click="handleNodeClick"
:filter-node-method="filterNode"
child-visible-for-filter-node
/> -->
</view>
</view>
<view class="flex-align-start mat15">
<view class="ass-label">
<text>已选调查专员</text>
</view>
<view class="AB-input">
<view class="flex">
<input disabled type="text" :value="name" placeholder="请选择调查专员"/>
<!-- <text class="cuIcon-roundclosefill" @click="A = ''"></text> -->
</view>
</view>
</view>
<view class="mat40 flex-justify-center ass-foot-btn">
<button class="cu-btn block def-btn margin-tb-sm lg round mar15" @click="assetAssign">确定</button>
<button class="cu-btn block cancel-btn margin-tb-sm lg round mal15" @click="back()">取消</button>
</view>
</view>
</template>
<script>
import LyTree from '@/components/ly-tree/ly-tree.vue'
export default {
components: {
LyTree
},
data() {
return {
filterNodeData: null,
search: '',//
name:'',//
data: [],
defaultProps: {
children: 'children',
label: 'label'
}
};
},
onShow() {
this.trees()//
},
onLoad() {
//
// this.$nextTick(() => {
// // this.$refs.tree.setCheckedKeys([6])
// })
},
created() {
if(uni.getStorageSync('decideIndex')){
let data = JSON.parse(uni.getStorageSync('decideIndex'))
this.getItem.businessId = data.businessId
this.getItem.companyId = data.companyId
this.getItem.iid = data.detailId
uni.removeStorageSync('decideIndex')
}else{
this.getItem = JSON.parse(uni.getStorageSync('applyMsg'))
}
},
watch: {
filterText(val) {
this.$refs.tree.filter(val);
}
},
//
methods: {
// ""()obj
handleCheck(obj) {
},
// /
handleRadioChange(obj) {
},
handleNodeClick(obj) {
},
//
filterNode(value, data) {
if (!value) return true;
return data.label.indexOf(value) !== -1;
},
//
trees(){
this.$http.get('/api-hrms//hrms/dept/empTrees').then(res=>{
this.data = res.data
this.disposeData(this.data)//
})
},
//
disposeData(data){// childrenchildren
let len = data.length
for(let i = 0;i<len;i++){
if(data[i].children){ // children---children
if(data[i].userDept){//
if(data[i].userDept.length!==0){//
let len = data[i].userDept.length
for(let a=0;a<len;a++){
data[i].userDept[a].name=data[i].userDept[a].account
}
}
data[i].children = [...data[i].children,...data[i].userDept]
data[i].children = [...new Set(data[i].children)]
}
if(data[i].children.length!==0){//
this.disposeData(data[i].children)
}
}else if(!data[i].children&&data[i].userDept&&data[i].userDept.length!==0){//children
data[i].children = []
let len = data[i].userDept.length
for(let a=0;a<len;a++){
data[i].userDept[a].name=data[i].userDept[a].account
}
data[i].children = [...data[i].children,...data[i].userDept]
data[i].children =[...new Set(data[i].children)]
}
}
},
// id
getChecked(nameList,idsList){
if(idsList.length!==0){
this.ID = idsList[0]
}else{
this.ID = ''
}
if(nameList&&nameList.length!==0){
this.name = nameList[0]
}else{
this.name = ''
}
},
//
assetAssign(){
let obj = {
iid:this.getItem.iid,
}
if(!this.ID){
return uni.showToast({title: '请选择后再确定!',icon:'none'})
}
//
obj.userId = this.ID
this.$http.get('/api-guarantee/compliance/investigation/assign',obj).then(res=>{
uni.showToast({
title:'操作成功!'
})
this.back()
})
},
// handleCheck(obj) {
// if (obj.node.checked) {
// this.placeholder = `"${obj.data.label}"`;
// this.filterNodeData = obj.data;
// } else {
// this.placeholder = ``;
// this.filterNodeData = null;
// }
// // filter(val, nodeData),AnodeDatatreeDataA
// this.$refs.tree.filter(this.filterText, this.filterNodeData);
// }
}
};
</script>
<style lang="scss" scoped>
.ass-content{
padding: 40rpx 40rpx;
.ass-foot-btn .cu-btn.lg{
padding: 0 100rpx;
}
}
.ass-label{
color: #00B9FF;
font-size: 28rpx;
margin-right: 40rpx;
}
.assign-view{
border-radius: 20rpx;
background-color: #fff;
box-shadow: 0 0 16rpx #ccc;
height: 800rpx;
overflow-x: hidden;
overflow-y: scroll;
}
.cu-bar .search-form{
border-radius: 20rpx;
background-color: #f5f5f5;
height: 72rpx;
line-height: 72rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.AB-input{
view{
background-color: #f5f5f5;
color: #666;
padding: 20rpx 40rpx;
border-radius: 20rpx;
text{
color: #B2B2B2;
}
}
}
</style>

@ -0,0 +1,342 @@
<template>
<view>
<!-- 菜单 (悬浮,预先隐藏)-->
<me-tabs v-if="isShowSticky" v-model="tabIndex" :fixed="true" :tabs="tabs" @change="tabChange"></me-tabs>
<mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption" @scroll="scroll" @topclick="topClick">
<!-- 菜单 (在mescroll-uni中不能使用fixed,否则iOS滚动时会抖动, 所以需在mescroll-uni之外存在一个一样的菜单) -->
<view id="tabInList">
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- 搜索 -->
<view class="cu-bar bg-white search" :class="scrolltop ? 'fixed' : ''" :style="[{top:CustomBar + 'px'}]">
<view class="search-form round">
<input @input="searchList" type="text" v-model="listParams.CustomerNumberOrName" placeholder="输入搜索的关键词" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in listData" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list">
<view class="charge" :id="'index-' + item.name" :data-index="index">
<navigator @click="handleGe(item,'watch')" hover-class='none' :url="'/guaranteePages/pages/investigationSee/investigationSee'"
class="nav-li" navigateTo :style="[{animation: 'show ' + ((index+1)*0.2+1) + 's 1'}]">
<view class="charge-title flex-between">
<text>{{item.name}}</text>
</view>
<view class="charge-text">
<view>
<text>业务编号</text>
<text class="mgl30">{{item.businessCode}}</text>
</view>
<view>
<text>客户名称</text>
<text class="mgl30">{{item.name}}</text>
</view>
<view>
<text>联系电话</text>
<text class="mgl30">{{item.phone}}</text>
</view>
<view>
<text>业务类别</text>
<text class="mgl30">{{item.businessType}}</text>
</view>
<view>
<text>申请额度(万元)</text>
<text class="mgl30">{{item.applyAmount}}</text>
</view>
<view>
<text>申请期限</text>
<text class="mgl30">{{item.applyTime}}</text>
</view>
<view>
<text>申请日期</text>
<text class="mgl30">{{item.createTime}}</text>
</view>
<view>
<text>审批状态</text>
<text class="mgl30">{{item.statusVal}}</text>
</view>
<view>
<text>业务状态</text>
<text class="mgl30">{{item.businessStatusVal}}</text>
</view>
<view>
<text>操作状态</text>
<text class="mgl30">{{item.operationType}}</text>
</view>
</view>
</navigator>
</view>
</view>
<view class="move foot-btn sideBtn">
<view class="">
<button v-show="item.status===0&&item.operatingStatus == 1" v-if="[58].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr mgr6" type="primary" size="mini" @click="handleGe(item,'none',true)">指派</button>
<button v-show="item.status===1&&item.operatingStatus == 1||item.status===4&&item.operatingStatus == 1" v-if="[58,61].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleGe(item,'audit')">审核</button>
</view>
<view class="margin-top">
<button v-show="item.status===1&&item.operatingStatus == 1||item.status===4&&item.operatingStatus == 1" v-if="[59,60].includes(item.roleId)&&item.businessStatus !== 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleGe(item,'servey')">调查</button>
</view>
</view>
</view>
</view>
<!-- 数据列表 -->
<!-- <index-list :list="chargeList"></index-list> -->
<!-- <touch-list :list="chargeList" :listTouchDirection="listTouchDirection" :listTouchStart="listTouchStart" :modalName="modalName"
@TouchStart="ListTouchStart" @TouchMove="ListTouchMove" @TouchEnd="ListTouchEnd"
></touch-list> -->
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
export default {
mixins: [MescrollMixin], // 使mixin
data() {
return {
upOption: {
// mescroll-uni onScroll: true, @scroll="scroll"; mescroll-bodyonPageScroll
// onScroll: true // , false (true,@scroll="scroll")
},
tabs:[
{name:'全部', num:1, y:0, curPageLen:0, hasNext:true},
{name:'待指派', num:1, y:0, curPageLen:0, hasNext:true},
{name:'审核中', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已审核', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已拒绝', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已驳回', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已撤销', num:1, y:0, curPageLen:0, hasNext:true},
],
tabIndex: 0, //
preIndex: 0, //
navTop: null, // nav (,)
isShowSticky: false, //
TabCur: 0,
scrollLeft: 0,
CustomBar: this.CustomBar,
listCurID: '',
chargeList: [],
total: 1,
scrolltop: false,
listTouchStart: 0,
listTouchDirection: null,
modalName: null,
listData:{},//
listParams:{
CustomerNumberOrName:'',//
status:'',// 0-6
size:10,
page:1,
},
status:''
};
},
created() {
},
onShow() {
this.upCallback({
num:1,size:10,search:''
})//
},
mounted() {
// this.chargeList.map(e =>{
// e.status = this.core.auditStatus(e.status)
// })
},
methods: {
changeTab(index){
this.TabCur = index;
},
/*下拉刷新的回调 */
downCallback() {
// ,
// loadSwiper();
// , ( page.num=1, upCallback )
this.mescroll.resetUpScroll()
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
this.$http.get('/api-guarantee/dg-guarantee-assign-user/guaranteeList',{
page: page.num?page.num:1,
size: page.size?page.size:10,
status:this.status?this.status:'',
CustomerNumberOrName:page.search?page.search:''
}).then(res => {
if(page.num == 1) this.listData = []; //
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.status)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operatingStatus)
})
this.listData = this.listData.concat(res.data.list); //
this.mescroll.endSuccess(res.data.list); //
}).catch(()=>{
//,
this.mescroll.endErr();
})
},
// --
searchList(e){
let that = this
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.timer = setTimeout(function () {
that.upCallback({num:1,size:10,search:e.detail.value})
},800)
},
// nav (0, navTop)
setNavTop(){
let view = uni.createSelectorQuery().select('#tabInList');
view.boundingClientRect(data => {
if(data) this.navTop = data.top //
}).exec();
},
// mescroll-uni (uponScroll:true)
// mescroll-bodyonPageScroll
scroll(){
// : ,
if (this.mescroll.getScrollTop() >= this.navTop) {
this.isShowSticky = true //
} else {
this.isShowSticky = false //
}
}, // "path": "pages/index/index",//
// ,,
topClick(){
this.isShowSticky = false
},
//
tabChange (index) {
if(index==0){
this.status = ''
}else{
this.status = `${index-1}`
}
this.upCallback({
num:1,size:10,serch:''
})
//
let preTab = this.tabs[this.preIndex]
preTab.y = this.mescroll.getScrollTop(); //
this.preIndex = index;
//
let curTab = this.tabs[index]
if (!curTab.goods) {
// ,
this.isChangeTab = true;
this.mescroll.resetUpScroll()
} else{
// ,
this.mescroll.setPageNum(curTab.num + 1); //
this.mescroll.endSuccess(curTab.curPageLen, curTab.hasNext); //
this.$nextTick(()=>{
this.mescroll.scrollTo(curTab.y, 0) //
})
}
},
// ListTouch
ListTouchStart(e) {
this.listTouchStart = e.touches[0].pageX
},
// ListTouch
ListTouchMove(e) {
this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
},
// ListTouch
ListTouchEnd(e) {
if (this.listTouchDirection == 'left') {
this.modalName = e.currentTarget.dataset.target
} else {
this.modalName = null
}
this.listTouchDirection = null
},
//
getList(val){
if(val==='search'){
this.listParams.page = 1
this.listParams.size = 10
}
this.$http.get('/api-guarantee/dg-guarantee-assign-user/guaranteeList',this.listParams).then(res=>{
if(res.data&&res.data.list&&res.data.list.length!==0){
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.status)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operatingStatus)
})
this.listData = res.data.list
}else{
this.listData = []
}
})
},
//
handleGe(item,str,assign){
if(assign){// -
this.goto('/guaranteePages/pages/assignAB/assignAB')//
uni.removeStorageSync('handleGe')
}else{
uni.setStorageSync('handleGe',str)//
}
if(!assign&&str!=='watch'){
this.goto('/guaranteePages/pages/investigationSee/investigationSee')
}
uni.setStorageSync('applyMsg',JSON.stringify(item))//
uni.setStorageSync('businessId',item.businessId)// id
},
},
// 使mescroll-bodyonPageScroll
// onPageScroll(e){
// if (e.scrollTop >= this.navTop) {
// this.isShowSticky = true //
// } else {
// this.isShowSticky = false //
// }
// }
}
</script>
<style lang="scss" scoped>
.cu-bar .search-form{
border: 2rpx solid #00B9FF;
border-radius: 20rpx;
background-color: #fff;
margin: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
margin-top: 20rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.add-customer{
padding: 0 50rpx;
button{
background-color: #00B9FF;
border-radius: 30rpx;
.mar-icon{
margin-right: 10rpx;
}
}
}
.sideBtn{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
/deep/ .cu-list>.cu-item .move view{
flex: none;
margin-top: 10rpx;
}
</style>

File diff suppressed because it is too large Load Diff

@ -0,0 +1,228 @@
<template>
<view>
<mescroll-body :sticky="true" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback">
<view class="sticky-tabs">
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- 搜索 -->
<view class="cu-bar bg-white search">
<view class="search-form round">
<input v-model="searchVal" @confirm="keywordSearch" type="text" placeholder="输入搜索的关键词" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<view class="add-customer mat15">
<text>{{total}}</text>
</view>
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in processData" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list" @tap="letterData(item,2)">
<view class="charge" :id="'index-' + item.name" :data-index="item.name">
<view class="charge-title flex-between">
<text>{{item.name}}的贷款申请</text>
</view>
<view class="charge-text">
<view class="flex-between">
<text>业务编号</text>
<text class="mgl30">{{item.businessCode}}</text>
</view>
<view class="flex-between">
<text>联系电话</text>
<text class="mgl30">{{item.phone}}</text>
</view>
<view class="flex-between">
<text>业务类别</text>
<text class="mgl30">{{item.businessType}}</text>
</view>
<view class="flex-between">
<text>担保额度(万元)</text>
<text class="mgl30">{{item.loanMoney}}</text>
</view>
<view class="flex-between">
<text>担保期限</text>
<text class="mgl30">{{item.loanTern}}</text>
</view>
<view class="flex-between">
<text>贷审会日期</text>
<text class="mgl30">{{item.passingTime}}</text>
</view>
<view class="flex-between">
<text>审批状态</text>
<text class="mgl30">{{item.statusText}}</text>
</view>
<view class="flex-between">
<text>业务状态</text>
<text class="mgl30">{{item.businessText}}</text>
</view>
<view class="flex-between">
<text>操作状态</text>
<text class="mgl30">{{item.operatingText}}</text>
</view>
</view>
</view>
</view>
<view class="move foot-btn">
<button v-if="item.isFgJl == 1 && item.operatingText !== '已处理'&&item.businessStatus !== 3" @tap="letterData(item,1)"
class="mini-btn round def-btn mar-lr" type="primary" size="mini">担保函</button>
<button v-if="item.isFgJl != 1 && item.operatingText !== '已处理'&&item.businessStatus !== 3" @tap="letterData(item,3)"
class="mini-btn round suc-btn" type="primary" size="mini">确认</button>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
export default {
mixins: [MescrollMixin], // 使mixin
data() {
return {
tabs:[
{name:'全部', value: ''},
{name:'审核中', value: 1},
{name:'已审核', value: 2},
{name:'已驳回', value: 4}
],
tabIndex: 0, //
processData: [],
total: 0,
page: {
num: 1,
size: 10,
},
status: '',//
searchVal: '', //
listTouchStart: 0,
listTouchDirection: null,
modalName: null
};
},
onShow() {
this.upCallback({
num:1,size:10
})
},
methods: {
//
keywordSearch(e){
this.searchVal = e.detail.value
this.upCallback(this.page)
},
/*下拉刷新的回调 */
downCallback() {
// , ( page.num=1, upCallback )
this.mescroll.resetUpScroll()
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
this.$http.post('/api-guarantee/dg-guarantee-letter-assign-user/guaranteeLetterList',{
page: page.num,
size: page.size,
customerNumberOrName: this.searchVal,
status: this.status
}).then(res => {
if(page.num == 1) this.processData = []; //
res.data.list.map(e =>{
e.statusText = this.core.auditStatus(e.status).text
e.businessText = this.core.businessType(e.businessStatus)
e.operatingText = this.core.operationType(e.operatingStatus)
})
this.total = res.data.totalCount
this.processData = this.processData.concat(res.data.list); //
this.mescroll.endSuccess(res.data.list); //
}).catch(()=>{
//,
this.mescroll.endErr();
})
},
//
tabChange (index) {
this.status = this.tabs[index].value;//
this.searchVal = '';
this.processData = []; // ,
this.mescroll.resetUpScroll()
},
letterData(item,Type){
let operationType = Type //
//
let letterValue = {
businessId: item.businessId,//id
bank: item.bank,//
name: item.name,//
passingTime: item.passingTime,//
loanMoney: this.core.fMoney(item.loanMoney),//
guaranteeMoney : this.core.fMoney3(item.loanMoney),//
file: item.file,//
loanTern: item.loanTern,//
auditOpinion: item.auditOpinion,//
roleId: item.roleId //id
}
let fileList = item.file.split(",")
letterValue = JSON.stringify(letterValue)
uni.navigateTo({
url: '../letterSee/letterSee?fileList='+fileList+'&operationType='+operationType+'&letterValue='+letterValue
});
uni.setStorageSync('businessId',item.businessId)// id--
},
// ListTouch
ListTouchStart(e) {
this.listTouchStart = e.touches[0].pageX
},
// ListTouch
ListTouchMove(e) {
this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
},
// ListTouch
ListTouchEnd(e) {
if (this.listTouchDirection == 'left') {
this.modalName = e.currentTarget.dataset.target
} else {
this.modalName = null
}
this.listTouchDirection = null
}
}
}
</script>
<style lang="scss" scoped>
.cu-bar .search-form{
border: 2rpx solid #00B9FF;
border-radius: 20rpx;
background-color: #fff;
margin: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
margin-top: 20rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.add-customer{
padding: 0 50rpx;
button{
background-color: #00B9FF;
border-radius: 30rpx;
.mar-icon{
margin-right: 10rpx;
}
}
}
</style>

@ -0,0 +1,199 @@
<template>
<view class="letter-content">
<view class="letter-header">
<text>担保承诺函</text>
</view>
<view class="mat15 bank">
<text>{{letterValue.bank}}</text>
</view>
<view class="letter-main mat15">
<text v-show="operationType !== 2">
根据<text class="letter-key">{{letterValue.name}}</text>的贷款申请经我公司项目贷审会研究同意为其在贵行申请的贷款提供担保特向贵行提供承诺本承诺系我公司同意与贵行签订保证合同的意向一切权利义务以保证合同为准
</text>
<!-- 查看担保函的文字 -->
<text v-show="operationType === 2">
根据<text class="letter-key">{{letterValue.name}}</text>的贷款申请经我公司<text class="letter-key">{{letterValue.passingTime}}</text>项目贷审会研究同意为其在贵行申请的贷款提供<text class="letter-key">{{letterValue.loanMoney}}</text>万元担保期限<text class="letter-key">{{letterValue.loanTern}}</text>特向贵行提供承诺本承诺系我公司同意与贵行签订保证合同的意向一切权利义务以保证合同为准
</text>
<view v-show="operationType !== 2">
<view class="mat15">
<text>客户名称{{letterValue.name}}</text>
</view>
<view class="mat15">
<text>贷审会日期{{letterValue.passingTime}}</text>
</view>
<view class="mat15">
<text>担保金额(万元){{letterValue.loanMoney}} (大写{{letterValue.guaranteeMoney}})</text>
</view>
<view class="mat15">
<text>担保期限{{letterValue.loanTern}}</text>
</view>
</view>
<view class="mat40 letter-footer">
<view class="mat15">
<text>大庆市工商业融资担保有限公司</text>
</view>
<view class="mat15">
<text>法定代表人</text>
</view>
<view class="mat15">
<text>或授权代理人</text>
</view>
<view class="mat15">
<text>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;</text>
</view>
</view>
</view>
<!-- PC端仅能在确认和担保函查看此附件 -->
<view class="bottom-border" v-show="operationType != 2">
<view class="left-border"><text class="mgl10">附件</text></view>
</view>
<upload v-show="operationType !== 2" @upload="uploadFile" :files="filesArray" :handle="false"></upload>
<uni-forms v-show="operationType !== 2" :value="letterValue" ref="letterValue" validate-trigger="bind" err-show-type="toast">
<uni-forms-item name="name" :label="operationType === 1 ? '审核' : '担保函确认'">
<uni-easyinput :disabled="operationType == 2 " type="textarea" v-model="letterValue.auditOpinion" placeholder="请输入审核意见"></uni-easyinput>
</uni-forms-item>
</uni-forms>
<view class="foot-btn btn-rig pad-bt margin-top" v-show="operationType == 1 || operationType == 3">
<button v-show="operationType == 1" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @tap="sumbitOpinion('提交',2)">提交</button>
<button v-show="operationType == 3" class="mini-btn round suc-btn mar-lr" type="primary" size="mini" @tap="sumbitOpinion('确认',2)">确认</button>
<button class="mini-btn round refuse-btn mar-lr" type="primary" size="mini" @tap="sumbitOpinion('驳回',4)">驳回</button>
<button plain class="mini-btn round plain-btn" type="primary" size="mini" @tap="back()">返回</button>
</view>
<timeline></timeline>
</view>
</template>
<script>
import upload from '../../../components/pretty-uploadFile/pretty-uploadFile.vue'
export default {
components: {
upload
},
data() {
return {
fileList: [],
operationType: '',
letterValue: {},
};
},
destroyed() {//
uni.removeStorageSync('copyData')
},
onLoad(option) {
//
if(uni.getStorageSync('decideIndex')){
let data = JSON.parse(uni.getStorageSync('decideIndex'))
this.letterDetail(data)
if(data.btn==='担保函'){
this.operationType = 1
}else{//
this.operationType = 3
}
uni.removeStorageSync('decideIndex')
}else if(uni.getStorageSync('copyData')){//
let data =JSON.parse(uni.getStorageSync('copyData'))
this.operationType = 2
//
if(uni.getStorageSync('notCopy')){
this.letterDetail(data)
}else{
this.letterValue = {
name:data.clientName,
passingTime:data.passingTime,
loanMoney:data.loanMoney,
loanTern:data.loanTern
}
}
}else{
if(option){
this.fileList = option.fileList
this.operationType = option.operationType
// operationType 1 2 3
this.letterValue = JSON.parse(option.letterValue)
}
}
},
methods: {
// --
letterDetail(data){
this.$http.get('/api-guarantee/dg-guarantee-letter-assign-user/guaranteeLetterDetail',{id:data.detailId}).then(res=>{
let that = this,data = res.data.data
this.letterValue = {
businessId: data.businessId,//id
bank: data.bank,//
name: data.clientName,//
passingTime: data.passingTime,//
loanMoney: data.loanMoney,//
guaranteeMoney: that.core.fMoney3(data.loanMoney),//
file: '',//
loanTern: data.loanTern,//
auditOpinion: '',//
roleId: ''//id
}
})
},
//
sumbitOpinion(text,type){
let _this = this
uni.showModal({
title: '提示',
content: `确定要${text}该审核意见吗?`,
success: function (res) {
if (res.confirm) {
if(type == 4 && _this.letterValue.auditOpinion == ''){
return uni.showToast({title: '请先填写审核意见!',icon:'none'})
}
_this.$http.post('/api-guarantee/dg-guarantee-letter-assign-user/updateStatus',{
auditOpinion: _this.letterValue.auditOpinion?_this.letterValue.auditOpinion:"",
businessId: _this.letterValue.businessId,
status: type
}).then(res => {
uni.showToast({title: `${text}成功`,icon:'none'})
_this.back()
}).catch(()=>{
})
} else if (res.cancel) {}
}
});
},
/* 上传文件 */
uploadFile(e) {
let fileList = e.map(r => r.url)
this.complianceSurveyFrom.fileUrls = fileList
},
}
}
</script>
<style lang="scss" scoped>
.letter-content{
padding: 40rpx 40rpx;
.letter-header{
text-align: center;
font-size: 32rpx;
font-weight: 500;
padding-bottom: 40rpx;
border-bottom: 2rpx solid #e5e5e5;
}
.bank{
font-weight: 700;
}
.letter-main{
letter-spacing: 4rpx;
text-indent: 60rpx;
}
.letter-key{
font-weight: bold;
margin: 0 10px;
}
.letter-footer{
text-align: right;
margin-right: 20rpx;
}
}
</style>

@ -0,0 +1,223 @@
<template>
<view>
<mescroll-body :sticky="true" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback">
<view class="sticky-tabs">
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- 搜索 -->
<view class="cu-bar bg-white search">
<view class="search-form round">
<input v-model="searchVal" @confirm="keywordSearch" type="text" placeholder="输入搜索的关键词" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<view class="add-customer mat15">
<text>{{total}}</text>
</view>
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in processData" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list" @tap="letterData(item,2)">
<view class="charge" :id="'index-' + item.name" :data-index="item.name">
<view class="charge-title flex-between">
<text>{{item.name}}的贷款申请</text>
</view>
<view class="charge-text">
<view class="flex-between">
<text>业务编号</text>
<text class="mgl30">{{item.businessCode}}</text>
</view>
<view class="flex-between">
<text>联系电话</text>
<text class="mgl30">{{item.phone}}</text>
</view>
<view class="flex-between">
<text>业务类别</text>
<text class="mgl30">{{item.businessType}}</text>
</view>
<view class="flex-between">
<text>担保额度(万元)</text>
<text class="mgl30">{{item.loanMoney}}</text>
</view>
<view class="flex-between">
<text>担保期限</text>
<text class="mgl30">{{item.loanTern}}</text>
</view>
<view class="flex-between">
<text>贷审会日期</text>
<text class="mgl30">{{item.passingTime}}</text>
</view>
<view class="flex-between">
<text>审批状态</text>
<text class="mgl30">{{item.statusText}}</text>
</view>
<view class="flex-between">
<text>业务状态</text>
<text class="mgl30">{{item.businessText}}</text>
</view>
<view class="flex-between">
<text>操作状态</text>
<text class="mgl30">{{item.operatingText}}</text>
</view>
</view>
</view>
</view>
<view class="move foot-btn">
<button v-if="item.operatingText !== '已处理'&&item.businessStatus !== 3" @tap="letterData(item,3)" class="mini-btn round suc-btn" type="primary" size="mini">确认</button>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
export default {
mixins: [MescrollMixin], // 使mixin
data() {
return {
tabs:[
{name:'全部', value: ''},
{name:'审核中', value: 1},
{name:'已审核', value: 2},
{name:'已驳回', value: 4}
],
tabIndex: 0, //
processData: [],
total: 0,
page: {
num: 1,
size: 10,
},
status: '',//
searchVal: '', //
listTouchStart: 0,
listTouchDirection: null,
modalName: null
};
},
onShow() {
this.upCallback({
num:1,size:10
})
},
methods: {
//
keywordSearch(e){
this.searchVal = e.detail.value
this.upCallback(this.page)
},
/*下拉刷新的回调 */
downCallback() {
// , ( page.num=1, upCallback )
this.mescroll.resetUpScroll()
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
this.$http.post('/api-guarantee/dg-loan-notice/loanNoticeList',{
page: page.num,
size: page.size,
customerNumberOrName: this.searchVal,
status: this.status
}).then(res => {
if(page.num == 1) this.processData = []; //
res.data.list.map(e =>{
e.statusText = this.core.auditStatus(e.status).text
e.businessText = this.core.businessType(e.businessStatus)
e.operatingText = this.core.operationType(e.operatingStatus)
})
this.total = res.data.totalCount
this.processData = this.processData.concat(res.data.list); //
this.mescroll.endSuccess(res.data.list); //
}).catch(()=>{
//,
this.mescroll.endErr();
})
},
//
tabChange (index) {
this.status = this.tabs[index].value;//
this.searchVal = '';
this.processData = []; // ,
this.mescroll.resetUpScroll()
},
letterData(item,Type){
let operationType = Type //
//
let letterValue = {
businessId: item.businessId,//id
bank: item.bank,//
name: item.name,//
passingTime: item.passingTime,//
loanMoney: this.core.fMoney(item.loanMoney),//
guaranteeMoney : this.core.fMoney3(item.loanMoney),//
file: item.file,//
loanTern: item.loanTern,//
auditOpinion: item.auditOpinion,//
roleId: item.roleId //id
}
letterValue = JSON.stringify(letterValue)
uni.navigateTo({
url: '../noticeSee/noticeSee?operationType='+operationType+'&letterValue='+letterValue
});
},
// ListTouch
ListTouchStart(e) {
this.listTouchStart = e.touches[0].pageX
},
// ListTouch
ListTouchMove(e) {
this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
},
// ListTouch
ListTouchEnd(e) {
if (this.listTouchDirection == 'left') {
this.modalName = e.currentTarget.dataset.target
} else {
this.modalName = null
}
this.listTouchDirection = null
}
}
}
</script>
<style lang="scss" scoped>
.cu-bar .search-form{
border: 2rpx solid #00B9FF;
border-radius: 20rpx;
background-color: #fff;
margin: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
margin-top: 20rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.add-customer{
padding: 0 50rpx;
button{
background-color: #00B9FF;
border-radius: 30rpx;
.mar-icon{
margin-right: 10rpx;
}
}
}
</style>

@ -0,0 +1,184 @@
<template>
<view class="letter-content">
<view class="letter-header">
<text>放款通知</text>
</view>
<view class="mat15 bank">
<text>{{letterValue.bank}}</text>
</view>
<view class="letter-main mat15">
<text>
我公司同意贵行为该公司放款
</text>
<view>
<view class="mat15">
<text>客户名称{{letterValue.name}}</text>
</view>
<view class="mat15">
<text>放款额度(万元){{letterValue.loanMoney}} (大写{{letterValue.guaranteeMoney}})</text>
</view>
<view class="mat15">
<text>担保期限{{letterValue.loanTern}}</text>
</view>
</view>
<view class="mat15">
我公司同意贵行为<text class="letter-key">{{letterValue.name}}</text>放款人民币<text class="letter-key">{{letterValue.loanMoney}}</text>万元整(大写<text class="letter-key">{{letterValue.guaranteeMoney}}</text>)
</view>
<view class="mat40 letter-footer">
<view class="mat15">
<text>大庆市工商业融资担保有限公司</text>
</view>
<view class="mat15">
<text>法定代表人</text>
</view>
<view class="mat15">
<text>或授权代理人</text>
</view>
<view class="mat15">
<text>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;</text>
</view>
</view>
</view>
<uni-forms v-show="operationType !== 2" :value="letterValue" ref="letterValue" validate-trigger="bind" err-show-type="toast">
<uni-forms-item name="name" :label="operationType === 1 ? '审核' : '放款确认'">
<uni-easyinput :disabled="operationType == 2" type="textarea" v-model="letterValue.auditOpinion" placeholder="请输入审核意见"></uni-easyinput>
</uni-forms-item>
</uni-forms>
<!-- v-show="operationType == 1" -->
<view class="foot-btn btn-rig pad-bt margin-top">
<button v-show="operationType == 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @tap="sumbitOpinion('提交',2)">提交</button>
<button v-show="operationType == 3" class="mini-btn round refuse-btn mar-lr" type="primary" size="mini" @tap="sumbitOpinion('驳回',4)">驳回</button>
<button plain class="mini-btn round plain-btn" type="primary" size="mini" @tap="back()">返回</button>
</view>
<timeline></timeline>
</view>
</template>
<script>
export default {
data() {
return {
fileList: [],
operationType: '',
letterValue: {},
};
},
onLoad(option) {
//
if(uni.getStorageSync('decideIndex')){ //
let data = JSON.parse(uni.getStorageSync('decideIndex'))
this.loanNoticeDetail(data)
this.operationType = 3
uni.removeStorageSync('decideIndex')
}else if(uni.getStorageSync('copyData')){
let data =JSON.parse(uni.getStorageSync('copyData')),that = this
this.operationType = 2
if(uni.getStorageSync('notCopy','查看')){
this.loanNoticeDetail(data)
}else{
this.letterValue = {
name:data.clientName,
passingTime:data.passingTime,
loanMoney:data.loanMoney,
loanTern:data.loanTern,
bank:data.bank,
guaranteeMoney:that.core.fMoney3(data.loanMoney)
}
}
}else{
this.fileList = option.fileList
this.operationType = option.operationType
this.letterValue = JSON.parse(option.letterValue)
}
},
methods: {
//
loanNoticeDetail(data){
this.$http.get('/api-guarantee/dg-loan-notice/loanNoticeDetail',{id:data.detailId}).then(res=>{
let that = this,data = res.data.data
this.letterValue = {
businessId: data.businessId,//id
bank: data.bank,//
name: data.name,//
passingTime: data.passingTime,//
loanMoney: data.loanMoney,//
guaranteeMoney: that.core.fMoney3(data.loanMoney),//
file: '',//
loanTern: data.loanTern,//
auditOpinion: '',//
roleId: ''//id
}
})
},
//
ViewImage(e) {
uni.previewImage({
urls: this.imgList,
current: e.currentTarget.dataset.url
});
},
//
sumbitOpinion(text,type){
let _this = this
uni.showModal({
title: '提示',
content: `确定要${text}该审核意见吗?`,
success: function (res) {
if (res.confirm) {
if(type == 4 && _this.letterValue.auditOpinion == ''){
return uni.showToast({title: '请先填写审核意见!',icon:'none'})
}
_this.$http.post('/api-guarantee/dg-loan-notice/updateLoanNotice',{
auditOpinion: _this.letterValue.auditOpinion,
businessId: _this.letterValue.businessId,
status: type
}).then(res => {
uni.showToast({title: `${text}成功`,icon:'none'})
_this.back()
}).catch(()=>{
})
} else if (res.cancel) {}
}
});
}
}
}
</script>
<style lang="scss" scoped>
.letter-content{
padding: 40rpx 40rpx;
.letter-header{
text-align: center;
font-size: 32rpx;
font-weight: 500;
padding-bottom: 40rpx;
border-bottom: 2rpx solid #e5e5e5;
}
.bank{
font-weight: 700;
}
.letter-main{
letter-spacing: 4rpx;
text-indent: 60rpx;
}
.letter-key{
font-weight: bold;
margin: 0 10px;
}
.letter-footer{
text-align: right;
margin-right: 20rpx;
}
}
</style>

@ -0,0 +1,231 @@
<template>
<view>
<mescroll-body :sticky="true" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback">
<view class="sticky-tabs">
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- 搜索 -->
<view class="cu-bar bg-white search">
<view class="search-form round">
<input v-model="searchVal" @confirm="keywordSearch" type="text" placeholder="输入搜索的关键词" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<view class="add-customer mat15">
<text>{{total}}</text>
</view>
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in processData" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list" @tap="letterData(item,2)">
<view class="charge" :id="'index-' + item.name" :data-index="item.name">
<view class="charge-title flex-between">
<text>{{item.name}}的贷款申请</text>
</view>
<view class="charge-text">
<view class="flex-between">
<text>业务编号</text>
<text class="mgl30">{{item.businessCode}}</text>
</view>
<view class="flex-between">
<text>联系电话</text>
<text class="mgl30">{{item.phone}}</text>
</view>
<view class="flex-between">
<text>业务类别</text>
<text class="mgl30">{{item.businessType}}</text>
</view>
<view class="flex-between">
<text>担保额度(万元)</text>
<text class="mgl30">{{item.loanMoney}}</text>
</view>
<view class="flex-between">
<text>担保期限</text>
<text class="mgl30">{{item.loanTern}}</text>
</view>
<view class="flex-between">
<text>贷审会日期</text>
<text class="mgl30">{{item.passingTime}}</text>
</view>
<view class="flex-between">
<text>审批状态</text>
<text class="mgl30">{{item.statusText}}</text>
</view>
<view class="flex-between">
<text>业务状态</text>
<text class="mgl30">{{item.businessText}}</text>
</view>
<view class="flex-between">
<text>操作状态</text>
<text class="mgl30">{{item.operatingText}}</text>
</view>
</view>
</view>
</view>
<view class="move foot-btn">
<button v-if="item.operatingText !== '已处理'&&item.businessStatus !== 3" @tap="letterData(item,3)"
class="mini-btn round suc-btn" type="primary" size="mini">确认</button>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
export default {
mixins: [MescrollMixin], // 使mixin
data() {
return {
tabs:[
{name:'全部', value: ''},
{name:'审核中', value: 1},
{name:'已审核', value: 2},
{name:'已驳回', value: 4}
],
tabIndex: 0, //
processData: [],
total: 0,
page: {
num: 1,
size: 10,
},
status: '',//
searchVal: '', //
listTouchStart: 0,
listTouchDirection: null,
modalName: null
};
},
onShow() {
this.upCallback({
num:1,size:10
})
},
methods: {
//
keywordSearch(e){
this.searchVal = e.detail.value
this.upCallback(this.page)
},
/*下拉刷新的回调 */
downCallback() {
// , ( page.num=1, upCallback )
this.mescroll.resetUpScroll()
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
this.$http.post('/api-guarantee/dg-payment-confirmation-consider/paymentConfirmationList',{
page: page.num,
size: page.size,
customerNumberOrName: this.searchVal,
status: this.status
}).then(res => {
if(page.num == 1) this.processData = []; //
res.data.list.map(e =>{
e.statusText = this.core.auditStatus(e.status).text
e.businessText = this.core.businessType(e.businessStatus)
e.operatingText = this.core.operationType(e.operatingStatus)
})
this.total = res.data.totalCount
this.processData = this.processData.concat(res.data.list); //
this.mescroll.endSuccess(res.data.list); //
}).catch(()=>{
//,
this.mescroll.endErr();
})
},
//
tabChange (index) {
this.status = this.tabs[index].value;//
this.searchVal = '';
this.processData = []; // ,
this.mescroll.resetUpScroll()
},
letterData(item,Type){
let operationType = Type //
//
let letterValue = {
businessId: item.businessId,//id
bank: item.bank,//
name: item.name,//
passingTime: item.passingTime,//
loanMoney: this.core.fMoney(item.loanMoney),//
guaranteeMoney : this.core.fMoney3(item.loanMoney),//
file: item.file,//
loanTern: item.loanTern,//
auditOpinion: item.auditOpinion,//
imgFile: item.imgFile, //
isFgJl: item.isFgJl//
}
let imgListT = []
if(item.imgFile){
imgListT = item.imgFile
}
let fileList = item.file.split(",")
letterValue = JSON.stringify(letterValue)
uni.setStorageSync('businessId',item.businessId)// id--
uni.navigateTo({
url: '../paymentSee/paymentSee?fileList='+fileList+'&imgListT='+imgListT+'&operationType='+operationType+'&letterValue='+letterValue
});
},
// ListTouch
ListTouchStart(e) {
this.listTouchStart = e.touches[0].pageX
},
// ListTouch
ListTouchMove(e) {
this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
},
// ListTouch
ListTouchEnd(e) {
if (this.listTouchDirection == 'left') {
this.modalName = e.currentTarget.dataset.target
} else {
this.modalName = null
}
this.listTouchDirection = null
}
}
}
</script>
<style lang="scss" scoped>
.cu-bar .search-form{
border: 2rpx solid #00B9FF;
border-radius: 20rpx;
background-color: #fff;
margin: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
margin-top: 20rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.add-customer{
padding: 0 50rpx;
button{
background-color: #00B9FF;
border-radius: 30rpx;
.mar-icon{
margin-right: 10rpx;
}
}
}
</style>

@ -0,0 +1,257 @@
<template>
<view class="letter-content">
<view class="letter-header">
<text>担保承诺函</text>
</view>
<view class="mat15 bank">
<text>{{letterValue.bank}}</text>
</view>
<view class="letter-main mat15">
<text v-show="operationType !== 2">
根据<text class="letter-key">{{letterValue.name}}</text>的贷款申请经我公司项目贷审会研究同意为其在贵行申请的贷款提供担保特向贵行提供承诺本承诺系我公司同意与贵行签订保证合同的意向一切权利义务以保证合同为准
</text>
<!-- 查看担保函的文字 -->
<text v-show="operationType === 2">
根据<text class="letter-key">{{letterValue.name}}</text>的贷款申请经我公司<text class="letter-key">{{letterValue.passingTime}}</text>项目贷审会研究同意为其在贵行申请的贷款提供<text class="letter-key">{{letterValue.loanMoney}}</text>万元担保期限<text class="letter-key">{{letterValue.loanTern}}</text>特向贵行提供承诺本承诺系我公司同意与贵行签订保证合同的意向一切权利义务以保证合同为准
</text>
<view v-show="operationType !== 2">
<view class="mat15">
<text>客户名称{{letterValue.name}}</text>
</view>
<view class="mat15">
<text>贷审会日期{{letterValue.passingTime}}</text>
</view>
<view class="mat15">
<text>担保金额(万元){{letterValue.loanMoney}} (大写{{letterValue.guaranteeMoney}})</text>
</view>
<view class="mat15">
<text>担保期限{{letterValue.loanTern}}</text>
</view>
</view>
<view class="mat40 letter-footer">
<view class="mat15">
<text>大庆市工商业融资担保有限公司</text>
</view>
<view class="mat15">
<text>法定代表人</text>
</view>
<view class="mat15">
<text>或授权代理人</text>
</view>
<view class="mat15">
<text>&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;&emsp;</text>
</view>
</view>
</view>
<view class="bottom-border">
<view class="left-border"><text class="mgl10">附件</text></view>
</view>
<upload :files="filesArray" :handle="false"></upload>
<!-- operationType !== 2 -->
<view class="bottom-border" v-show="operationType !== 2">
<view class="left-border"><text class="mgl10">上传银行回单</text></view>
</view>
<!-- 仅财务部可以上传 -->
<upload @upload="uploadFile" :files="imgArray" :handle="operationType != 2&&letterValue.isFgJl !== 1"></upload>
<uni-forms v-show="operationType != 2" :value="letterValue" ref="letterValue" validate-trigger="bind" err-show-type="toast">
<uni-forms-item name="name" :label="operationType === 1 ? '审核' : '财务确认'">
<uni-easyinput type="textarea" v-model="letterValue.auditOpinion" placeholder="请输入审核意见"></uni-easyinput>
</uni-forms-item>
</uni-forms>
<view class="foot-btn btn-rig pad-bt margin-top">
<button v-show="operationType == 3" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @tap="sumbitOpinion('提交',2)">提交</button>
<button v-show="operationType == 3" class="mini-btn round refuse-btn mar-lr" type="primary" size="mini" @tap="sumbitOpinion('驳回',4)">驳回</button>
<button plain class="mini-btn round plain-btn" type="primary" size="mini" @tap="back()">返回</button>
</view>
<timeline></timeline>
</view>
</template>
<script>
import upload from '../../../components/pretty-uploadFile/pretty-uploadFile.vue'
export default {
components: {
upload
},
data() {
return {
// fileList: [],
// imgListT: [],
operationType: '',
letterValue: {},
imgArray:[],//
filesArray:[],//
roleArr:[],
getImg:[],//
isFgJl:''
};
},
destroyed() {//
uni.removeStorageSync('copyData')
},
onLoad(option) {
//
if(uni.getStorageSync('decideIndex')){ //
let data = JSON.parse(uni.getStorageSync('decideIndex'))
this.paymentDetail(data)
this.operationType = 3
uni.removeStorageSync('decideIndex')
}else if(uni.getStorageSync('copyData')){
let data =JSON.parse(uni.getStorageSync('copyData'))
this.operationType = 2
if(uni.getStorageSync('notCopy','查看')){
this.paymentDetail(data)
}else{
this.letterValue = {
name:data.clientName,
passingTime:data.passingTime,
loanMoney:data.loanMoney,
loanTern:data.loanTern
}
if(data.file) this.filesArray = this.handleFiles(data.file.split(','))
if(data.imgFile) this.imgArray = this.handleFiles(data.imgFile.split(','))
}
}else{
this.roleArr = uni.getStorageSync('roleArr').split(",")
//
if(option.fileList)this.filesArray = this.handleFiles(option.fileList.split(','))
//
if(option.imgListT) this.imgArray = this.handleFiles(option.imgListT.split(','))
if(option.imgListT) this.getImg = option.imgListT.split(',')
// operationType 23
this.operationType = option.operationType
this.letterValue = JSON.parse(option.letterValue)
}
},
methods: {
// --
paymentDetail(data){
this.$http.get('/api-guarantee/dg-payment-confirmation-consider/paymentConfirmationDetail',{id:data.detailId}).then(res=>{
let that = this,data = res.data.data
this.letterValue = {
businessId: data.businessId,//id
bank: data.bank,//
name: data.clientName,//
passingTime: data.passingTime,//
loanMoney: data.loanMoney,//
guaranteeMoney: that.core.fMoney3(data.loanMoney),//
file: '',//
loanTern: data.loanTern,//
auditOpinion: '',//
roleId: ''//id
}
if(data.file) this.filesArray = this.handleFiles(data.file.split(','))
if(data.imgFile) this.imgArray = this.handleFiles(data.imgFile.split(','))
})
},
//
ViewImage(e) {
uni.previewImage({
urls: this.imgList,
current: e.currentTarget.dataset.url
});
},
//
sumbitOpinion(text,type){
let _this = this
uni.showModal({
title: '提示',
content: `确定要${text}该审核意见吗?`,
success: function (res) {
if (res.confirm) {
if(type == 4 && _this.letterValue.auditOpinion == ''){
return uni.showToast({title: '请先填写审核意见!',icon:'none'})
}
// let imgFile = _this.imgArray.map(e =>e.url).join()
_this.$http.post('/api-guarantee/dg-payment-confirmation-consider/updatePaymentConfirmation',{
auditOpinion: _this.letterValue.auditOpinion,
businessId: _this.letterValue.businessId,
status: type,
imgFile: _this.getImg.join()
}).then(res => {
uni.showToast({title: `${text}成功`,icon:'none'})
_this.back()
}).catch(()=>{
})
} else if (res.cancel) {}
}
});
},
// data ['url1','url2']
// index
handleFiles(data,num){
if(!data||!data.length) return []
let arr = []
data.map(e=>{
if(e=='') return
let obj = {},
num= e.lastIndexOf("."),
ext = e.substr(num+1);
// --
if(e.toLowerCase().includes('jpg','jpeg','png','bmp','gif')){
obj.book = 'img'
}else{
obj.book = "file"
if(ext=='png'){
obj.book = 'img'
}
}
//
obj.ext = ext
obj.url = e
arr.push(obj)
// // ,12
// num==1?this.filesArray.push(obj):this.imgArray.push(obj)
})
return arr
},
/* 上传文件 */
uploadFile(e) {
let fileList = e.map(r => r.url)
this.getImg = fileList
},
}
}
</script>
<style lang="scss" scoped>
.letter-content{
padding: 40rpx 40rpx;
.letter-header{
text-align: center;
font-size: 32rpx;
font-weight: 500;
padding-bottom: 40rpx;
border-bottom: 2rpx solid #e5e5e5;
}
.bank{
font-weight: 700;
}
.letter-main{
letter-spacing: 4rpx;
text-indent: 60rpx;
}
.letter-key{
font-weight: bold;
margin: 0 10px;
}
.letter-footer{
text-align: right;
margin-right: 20rpx;
}
}
</style>

@ -0,0 +1,338 @@
<template>
<view>
<!-- 菜单 (悬浮,预先隐藏)-->
<me-tabs v-if="isShowSticky" v-model="tabIndex" :fixed="true" :tabs="tabs" @change="tabChange"></me-tabs>
<mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption" @scroll="scroll" @topclick="topClick">
<!-- 菜单 (在mescroll-uni中不能使用fixed,否则iOS滚动时会抖动, 所以需在mescroll-uni之外存在一个一样的菜单) -->
<view id="tabInList">
<me-tabs v-model="tabIndex" :tabs="tabs" @change="tabChange"></me-tabs>
</view>
<!-- 搜索 -->
<view class="cu-bar bg-white search" :class="scrolltop ? 'fixed' : ''" :style="[{top:CustomBar + 'px'}]">
<view class="search-form round">
<input @input="searchList" type="text" placeholder="输入搜索的关键词" v-model="listParams.codeOrName" confirm-type="search"></input>
<text class="cuIcon-search"></text>
</view>
</view>
<!-- 数据列表 -->
<!-- <index-list :list="chargeList"></index-list> -->
<!-- <touch-list :list="chargeList" :listTouchDirection="listTouchDirection" :listTouchStart="listTouchStart" :modalName="modalName"
@TouchStart="ListTouchStart" @TouchMove="ListTouchMove" @TouchEnd="ListTouchEnd"
></touch-list> -->
<view class="cu-list menu-avatar">
<view class="cu-item" :class="modalName=='move-box-'+ index?'move-cur':''" v-for="(item,index) in listData" :key="index"
@touchstart="ListTouchStart" @touchmove="ListTouchMove" @touchend="ListTouchEnd" :data-target="'move-box-' + index">
<view class="good-list">
<view class="charge" :id="'index-' + item.name" :data-index="item.name">
<navigator v-if="[59,60,68,69,70].includes(item.roleId)" @click="handleGe(item,'watch')" hover-class='none' :url="'/guaranteePages/pages/regulationSee/regulationSee'"
class="nav-li" navigateTo :style="[{animation: 'show ' + ((index+1)*0.2+1) + 's 1'}]">
<view class="charge-title flex-between">
<text>{{item.name}}的贷款申请</text>
</view>
<view class="charge-text">
<view>
<text>业务编号</text>
<text class="mgl30">{{item.businessCode}}</text>
</view>
<view>
<text>客户名称</text>
<text class="mgl30">{{item.name}}</text>
</view>
<view>
<text>联系电话</text>
<text class="mgl30">{{item.phone}}</text>
</view>
<view>
<text>业务类别</text>
<text class="mgl30">{{item.businessType}}</text>
</view>
<view>
<text>申请额度万元</text>
<text class="mgl30">{{item.applyAmount}}</text>
</view>
<view>
<text>申请期限</text>
<text class="mgl30">{{item.applyTime}}</text>
</view>
<view>
<text>申请日期</text>
<text class="mgl30">{{item.createTime}}</text>
</view>
<view>
<text>审批状态</text>
<text class="mgl30">{{item.statusVal}}</text>
</view>
<view>
<text>业务状态</text>
<text class="mgl30">{{item.businessStatusVal}}</text>
</view>
<view>
<text>操作状态</text>
<text class="mgl30">{{item.operationType}}</text>
</view>
<!-- <view class="flex-between">
<text class="time-text">{{item.time}}</text>
<text class="status-text" :style="'background-color:'+item.status.bgColor+';color:'+item.status.textColor">{{item.status.text}}</text>
</view> -->
</view>
</navigator>
</view>
</view>
<view class="move foot-btn sideBtn">
<button v-if="[68].includes(item.roleId)&&item.businessStatus !== 3" v-show="item.auditStatus===0&&item.operationStatus == 1" class="mini-btn round def-btn mar-lr" type="primary" size="mini" @click="handleGe(item,'none',true)">指派</button>
<button v-if="[69].includes(item.roleId)&&item.businessStatus !== 3" v-show="item.auditStatus===1&&item.operationStatus == 1||item.auditStatus===4&&item.operationStatus == 1" class="mini-btn round suc-btn mar-lr" type="primary" size="mini" @click="handleGe(item,'survey')">调查</button>
<view class="margin-top">
<button v-if="[68,70].includes(item.roleId)&&item.businessStatus !== 3" v-show="item.auditStatus===1&&item.operationStatus == 1||item.auditStatus===4&&item.operationStatus == 1" class="mini-btn round def-btn" type="primary" size="mini" @click="handleGe(item,'audit')">审核</button>
</view>
</view>
</view>
</view>
</mescroll-body>
</view>
</template>
<script>
import MescrollMixin from "@/components/mescroll-uni/mescroll-mixins.js";
export default {
mixins: [MescrollMixin], // 使mixin
data() {
return {
upOption: {
// mescroll-uni onScroll: true, @scroll="scroll"; mescroll-bodyonPageScroll
// onScroll: true // , false (true,@scroll="scroll")
},
tabs:[
{name:'全部', num:1, y:0, curPageLen:0, hasNext:true},
{name:'待指派', num:1, y:0, curPageLen:0, hasNext:true},
{name:'审核中', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已审核', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已拒绝', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已驳回', num:1, y:0, curPageLen:0, hasNext:true},
{name:'已撤销', num:1, y:0, curPageLen:0, hasNext:true},
],
tabIndex: 0, //
preIndex: 0, //
navTop: null, // nav (,)
isShowSticky: false, //
TabCur: 0,
scrollLeft: 0,
CustomBar: this.CustomBar,
listCurID: '',
listData: [],
total: 1,
scrolltop: false,
listTouchStart: 0,
listTouchDirection: null,
modalName: null,
listParams:{
customerNumberOrName:"",//
page:1,
size:10
},
status:'',//
};
},
onShow() {
this.upCallback({num:1,size:10,search:''})//
},
mounted() {
// this.listData.map(e =>{
// e.status = this.core.auditStatus(e.status)
// })
},
methods: {
changeTab(index){
this.TabCur = index;
},
/*下拉刷新的回调 */
downCallback() {
// ,
// loadSwiper();
// , ( page.num=1, upCallback )
this.mescroll.resetUpScroll()
},
/*上拉加载的回调: 其中page.num:当前页 从1开始, page.size:每页数据条数,默认10 */
upCallback(page) {
this.$http.get('/api-guarantee/compliance/investigation/list',{
page: page.num,
size: page.size,
status:this.status?this.status:'',
codeOrName:page.search?page.search:''
}).then(res => {
if(page.num == 1) this.listData = []; //
res.data.list.map(e =>{
e.statusVal = this.core.statusVal(e.auditStatus)
e.businessStatusVal = this.core.businessType(e.businessStatus)
e.operationType = this.core.operationType(e.operationStatus)
})
this.listData = this.listData.concat(res.data.list); //
this.total = res.data.totalCount
this.mescroll.endSuccess(res.data.list); //
}).catch(()=>{
//,
this.mescroll.endErr();
})
},
// --
searchList(e){
let that = this
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
this.timer = setTimeout(function () {
that.upCallback({num:1,size:10,search:e.detail.value})
},800)
},
// nav (0, navTop)
setNavTop(){
let view = uni.createSelectorQuery().select('#tabInList');
view.boundingClientRect(data => {
this.navTop = data.top //
}).exec();
},
// mescroll-uni (uponScroll:true)
// mescroll-bodyonPageScroll
scroll(){
// : ,
if (this.mescroll.getScrollTop() >= this.navTop) {
this.isShowSticky = true //
} else {
this.isShowSticky = false //
}
},
// ,,
topClick(){
this.isShowSticky = false
},
//
tabChange (index) {
if(index==0){
this.status = ''
}else{
this.status = `${index-1}`
}
this.upCallback({
num:1,size:10,search:''
})
//
let preTab = this.tabs[this.preIndex]
preTab.y = this.mescroll.getScrollTop(); //
this.preIndex = index;
//
let curTab = this.tabs[index]
if (!curTab.goods) {
// ,
this.isChangeTab = true;
this.mescroll.resetUpScroll()
} else{
// ,
this.mescroll.setPageNum(curTab.num + 1); //
this.mescroll.endSuccess(curTab.curPageLen, curTab.hasNext); //
this.$nextTick(()=>{
this.mescroll.scrollTo(curTab.y, 0) //
})
}
},
// ListTouch
ListTouchStart(e) {
this.listTouchStart = e.touches[0].pageX
},
// ListTouch
ListTouchMove(e) {
this.listTouchDirection = e.touches[0].pageX - this.listTouchStart > 0 ? 'right' : 'left'
},
// ListTouch
ListTouchEnd(e) {
if (this.listTouchDirection == 'left') {
this.modalName = e.currentTarget.dataset.target
} else {
this.modalName = null
}
this.listTouchDirection = null
},
// //
// getList(val){
// if(val==='search'){
// this.listParams.page = 1
// this.listParams.size = 10
// }
// this.$http.get('/api-guarantee/compliance/investigation/list',this.listParams).then(res=>{
// if(res.data&&res.data.list&&res.data.list.length!==0){
// res.data.list.map(e =>{
// e.statusVal = this.core.statusVal(e.status)
// e.businessStatusVal = this.core.businessType(e.businessStatus)
// e.operationType = this.core.operationType(e.operatingStatus)
// })
// this.listData = res.data.list
// }else{
// this.listData = []
// }
// })
// },
//
handleGe(item,str,assign){
if(assign){// -
this.goto('/guaranteePages/pages/assignRegulation/assignRegulation')//
uni.removeStorageSync('handleGe')
}else{
uni.setStorageSync('handleGe',str)//
}
if(!assign&&str!=='watch'){//
this.goto('/guaranteePages/pages/regulationSee/regulationSee')
}
uni.setStorageSync('applyMsg',JSON.stringify(item))// --
uni.setStorageSync('businessId',item.businessId)// id
},
},
// 使mescroll-bodyonPageScroll
onPageScroll(e){
if (e.scrollTop >= this.navTop) {
this.isShowSticky = true //
} else {
this.isShowSticky = false //
}
}
}
</script>
<style lang="scss" scoped>
.cu-bar .search-form{
border: 2rpx solid #00B9FF;
border-radius: 20rpx;
background-color: #fff;
margin: 0 50rpx;
height: 72rpx;
line-height: 72rpx;
margin-top: 20rpx;
input{
height: 72rpx;
line-height: 72rpx;
padding: 0 40rpx;
}
.cuIcon-search{
margin: 0 20rpx;
}
}
.add-customer{
padding: 0 50rpx;
button{
background-color: #00B9FF;
border-radius: 30rpx;
.mar-icon{
margin-right: 10rpx;
}
}
}
.sideBtn{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
</style>

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save