gcz 1 vuosi sitten
sitoutus
f21e6c0b37
100 muutettua tiedostoa jossa 18026 lisäystä ja 0 poistoa
  1. 17 0
      .gitignore
  2. 43 0
      App.vue
  3. 276 0
      center/center.vue
  4. 146 0
      center/factorauth.vue
  5. 315 0
      center/invoice.vue
  6. 250 0
      center/memberinfo.vue
  7. 253 0
      center/mycoupon.vue
  8. 656 0
      center/order.vue
  9. 874 0
      center/orderdetails.vue
  10. 64 0
      center/paysuccess.vue
  11. 207 0
      center/people.vue
  12. 241 0
      center/refund.vue
  13. 117 0
      center/viewRefund.vue
  14. 215 0
      center/viewrecord.vue
  15. 197 0
      common/apiurl.js
  16. 61 0
      common/config.js
  17. 37 0
      common/http.api.js
  18. 157 0
      common/request.js
  19. 412 0
      components/ay-qrcode/ay-qrcode.vue
  20. 872 0
      components/ay-qrcode/qrcode_wx.js
  21. 263 0
      components/ay-qrcode/weapp-qrcode.js
  22. 83 0
      components/cartfixed.vue
  23. 131 0
      components/tabbar.vue
  24. 21 0
      index.html
  25. 88 0
      main.js
  26. 111 0
      manifest.json
  27. 18 0
      mixin.js
  28. 16 0
      package-lock.json
  29. 28 0
      package.json
  30. 174 0
      pages.json
  31. 112 0
      pages/actors.vue
  32. 924 0
      pages/bookticket.vue
  33. 178 0
      pages/chosenposition.vue
  34. 172 0
      pages/index/index.vue
  35. 226 0
      pages/login/login.vue
  36. 332 0
      pages/login/loginh5.vue
  37. 60 0
      pages/login/regulation.vue
  38. 230 0
      pages/news.vue
  39. 127 0
      pages/newsdetails.vue
  40. 226 0
      pages/searchpage.vue
  41. 888 0
      pages/ticketlist.vue
  42. 380 0
      static/css/common.scss
  43. 71 0
      static/css/flex.scss
  44. 419 0
      static/logo.svg
  45. 29 0
      store/$u.mixin.js
  46. 87 0
      store/index.js
  47. 212 0
      uni.scss
  48. 6 0
      uni_modules/mescroll-uni/changelog.md
  49. 19 0
      uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.css
  50. 400 0
      uni_modules/mescroll-uni/components/mescroll-body/mescroll-body.vue
  51. 47 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.css
  52. 39 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/components/mescroll-down.vue
  53. 360 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-body.vue
  54. 49 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni-option.js
  55. 437 0
      uni_modules/mescroll-uni/components/mescroll-diy/beibei/mescroll-uni.vue
  56. 44 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.css
  57. 53 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-down.vue
  58. 32 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.css
  59. 40 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/components/mescroll-up.vue
  60. 380 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-body.vue
  61. 64 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni-option.js
  62. 462 0
      uni_modules/mescroll-uni/components/mescroll-diy/xinlang/mescroll-uni.vue
  63. 116 0
      uni_modules/mescroll-uni/components/mescroll-empty/mescroll-empty.vue
  64. 55 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.css
  65. 47 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-down.vue
  66. 83 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-top.vue
  67. 47 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.css
  68. 39 0
      uni_modules/mescroll-uni/components/mescroll-uni/components/mescroll-up.vue
  69. 15 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-i18n.js
  70. 57 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js
  71. 64 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni-option.js
  72. 36 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.css
  73. 799 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.js
  74. 477 0
      uni_modules/mescroll-uni/components/mescroll-uni/mescroll-uni.vue
  75. 47 0
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js
  76. 66 0
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more-item.js
  77. 74 0
      uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-more.js
  78. 109 0
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/mixins.js
  79. 92 0
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/renderjs.js
  80. 268 0
      uni_modules/mescroll-uni/components/mescroll-uni/wxs/wxs.wxs
  81. 80 0
      uni_modules/mescroll-uni/package.json
  82. 45 0
      uni_modules/mescroll-uni/readme.md
  83. 21 0
      uni_modules/uview-ui/LICENSE
  84. 66 0
      uni_modules/uview-ui/README.md
  85. 357 0
      uni_modules/uview-ui/changelog.md
  86. 78 0
      uni_modules/uview-ui/components/u--form/u--form.vue
  87. 47 0
      uni_modules/uview-ui/components/u--image/u--image.vue
  88. 73 0
      uni_modules/uview-ui/components/u--input/u--input.vue
  89. 44 0
      uni_modules/uview-ui/components/u--text/u--text.vue
  90. 48 0
      uni_modules/uview-ui/components/u--textarea/u--textarea.vue
  91. 54 0
      uni_modules/uview-ui/components/u-action-sheet/props.js
  92. 278 0
      uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue
  93. 59 0
      uni_modules/uview-ui/components/u-album/props.js
  94. 259 0
      uni_modules/uview-ui/components/u-album/u-album.vue
  95. 44 0
      uni_modules/uview-ui/components/u-alert/props.js
  96. 243 0
      uni_modules/uview-ui/components/u-alert/u-alert.vue
  97. 52 0
      uni_modules/uview-ui/components/u-avatar-group/props.js
  98. 103 0
      uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue
  99. 78 0
      uni_modules/uview-ui/components/u-avatar/props.js
  100. 58 0
      uni_modules/uview-ui/components/u-avatar/u-avatar.vue

+ 17 - 0
.gitignore

@@ -0,0 +1,17 @@
+.DS_Store
+
+unpackage/
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+node_modules/
+
+# Editor directories and files
+.hbuilderx
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln

+ 43 - 0
App.vue

@@ -0,0 +1,43 @@
+<script>
+	export default {
+		globalData: {
+		    statusBarHeight: 0, // 状态导航栏高度
+		    navHeight: 44, // 总体高度
+		    navigationBarHeight: 0, // 导航栏高度(标题栏高度)
+		},
+		onLaunch: function() {
+			// console.log('App Launch')
+			 // 状态栏高度
+			this.globalData.statusBarHeight = uni.getSystemInfoSync().statusBarHeight
+		 
+			// #ifdef MP-WEIXIN
+			// 获取微信胶囊的位置信息 width,height,top,right,left,bottom
+			const custom = wx.getMenuButtonBoundingClientRect()
+			// console.log(custom)
+		 
+			// 导航栏高度(标题栏高度) = 胶囊高度 + (顶部距离 - 状态栏高度) * 2
+			this.globalData.navigationBarHeight = custom.height + (custom.top - this.globalData.statusBarHeight) * 2
+			// console.log("导航栏高度:"+this.globalData.navigationBarHeight)
+		 
+			// 总体高度 = 状态栏高度 + 导航栏高度
+			this.globalData.navHeight = this.globalData.navigationBarHeight + this.globalData.statusBarHeight
+		 
+			// #endif
+		 
+			// console.log('this.globalData==========',this.globalData)
+			// this.$wxApi.config()
+		},
+		onShow: function() {
+			// console.log('App Show')
+		},
+		onHide: function() {
+			// console.log('App Hide')
+		}
+	}
+</script>
+
+<style lang="scss">
+	/*每个页面公共css */
+	@import "@/uni_modules/uview-ui/index.scss";
+	@import "@/static/css/common.scss";
+</style>

+ 276 - 0
center/center.vue

@@ -0,0 +1,276 @@
+<template>
+	<view class="pages">
+		<!-- <view class="" :style="{height: navHeight+'px' }"></view> -->
+		<!-- <view class="page-bg">
+			<img class="img" :src="staticUrl+'/img/center-index-bg.png'" alt="">
+		</view> -->
+		<view class="box">
+			<view class="home_top">
+				<view class="base-info block-wrap u-flex u-row-between" @click="$u.route('/center/memberinfo',{type:'redirectTo'})" v-if="vuex_member_info.name">
+					<view class="left u-flex">
+						<u-avatar :src="avatar||staticUrl+'/img/avatar.png'" size="140rpx"></u-avatar>
+						<view class="info">
+							<view class="name ellipsis-1">{{vuex_member_info.name||vuex_member_info.nickName}}</view>
+							<view class="mobile u-flex">
+								<text class="mobile">{{vuex_member_info.mobile|hidePhoneNumber}}</text>
+							</view>
+						</view>
+					</view>
+					<!-- <u-icon @click="$u.route('/center/memberinfo',{type:'redirectTo'})" name="setting-fill" color="#333333" size="38rpx"></u-icon> -->
+				</view>
+				<view class="base-info block-wrap u-flex u-row-between" @click="goLogin" v-else>
+					<view class="left u-flex">
+						<u-avatar :src="staticUrl+'/img/avatar.png'" size="140rpx"></u-avatar>
+						<view class="info">
+							<view class="name ellipsis-1">登录/注册</view>
+							<text class="mobile">登录查看更多</text>
+						</view>
+					</view>
+					<!-- <u-icon name="setting-fill" color="#333333" size="38rpx"></u-icon> -->
+				</view>
+			</view>
+		</view>
+		<view class="tools block-wrap">
+			<!-- <view class="title">常用工具</view> -->
+			<view class="tools-wrap">
+				<view class="" 
+					  v-for="(item,index) in tools" 
+					  @click="toolsClick(item)"
+					  :key="index">
+					  <view class="tool-item u-flex u-row-between" v-if="!item.chat">
+					  	<view class="left u-flex">
+					  		<u-icon :name="item.ico" color="#333333" size="55rpx"></u-icon>
+							<text class="name">{{item.name}}</text>
+					  	</view>
+					  	<u-icon name="arrow-right" color="#ddd" size="40rpx"></u-icon>
+					  </view>
+					  <view class="tool-item" v-if="item.chat==1">
+					  	<button class="button-item u-flex u-row-between" type="default" open-type="contact">
+							<view class="left u-flex">
+								<u-icon :name="item.ico" color="#333333" size="55rpx"></u-icon>
+								<text class="name">{{item.name}}</text>
+							</view>
+							<u-icon name="arrow-right" color="#ddd" size="40rpx"></u-icon>
+						</button>
+					  </view>
+				</view>
+			</view>
+		</view>
+		<tabbar :tabbarIndexProps="2" />
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	import tabbar from "../components/tabbar.vue"
+	export default {
+		components:{
+			tabbar
+		},
+		mixins:[systemInfo],
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				avatar:this.$commonConfig.staticUrl+'/img/avatar.png',
+				memberInfo:{},
+				customerMobile:'',
+				tools:[
+					// {name:'观看记录',url:'/center/viewrecord',ico:this.$commonConfig.staticUrl+'/img/center-record.png',checkauth:true},
+					// {name:'领券中心',url:'/center/mycoupon',ico:this.$commonConfig.staticUrl+'/img/center-coupon.png',checkauth:true},
+					// {name:'开具发票',url:'center/invoice',ico:this.$commonConfig.staticUrl+'/img/center-ticket.png',checkauth:true},
+					{name:'我的订单',url:'center/order',ico:this.$commonConfig.staticUrl+'/img/center-order.png',checkauth:true},
+					{name:'客服热线',url:'',ico:this.$commonConfig.staticUrl+'/img/center-call.png',checkauth:true,phone:''},
+					// {name:'在线客服',chat:'1',ico:this.$commonConfig.staticUrl+'/img/center-call.png'},
+				]
+			}
+		},
+		onShow() {	
+			if(this.vuex_member_info.name){
+				this.getMemberInfo();
+			}
+			
+		},
+		onLoad() {
+			this.getSystemInfo();
+		},
+		methods: {
+			getMemberInfo(){
+				this.$u.api.personalIndex({userid:this.vuex_member_info.id}).then(res=>{
+					this.memberInfo = res.data.memberInfo;
+					this.avatar =  res.data.avatar;
+					this.tools.forEach(item => {
+					  if (item.name === '客服热线') {
+					    item.phone = res.data.customerMobile;
+					  }
+					});
+					this.$u.vuex('vuex_member_info', res.data.memberInfo);
+					const isExist = this.tools.some(item => item.name === '实名认证');
+					if(!res.data.memberInfo.isAuth&&!isExist){
+						this.tools.push({name:'实名认证',url:'center/factorauth',ico:this.$commonConfig.staticUrl+'/img/center-ticket.png',checkauth:true})
+					}
+					// console.log('memberInfo',this.memberInfo);
+					}).catch(err=>{
+					console.log('memberInfo',err.data);
+				})
+			},
+			toolsClick(item){
+				let that = this;
+				console.log('item',item);
+				
+				if(item.name!=='在线客服'&&item.name!=='客服热线'&&item.name!=='实名认证'&&item.name!=='我的订单'){
+					uni.showToast({
+						title:'开发中',
+						icon:"none"
+					})
+					return
+				}
+				
+				if ('phone' in item) {
+					if(!item.phone){
+						uni.showToast({
+							title:'电话号码为空',
+							icon:"none"
+						})
+						return
+					}
+				  uni.makePhoneCall({
+				  	phoneNumber: item.phone,
+					success() {
+						console.log('success');
+					},
+					fail() {
+						console.log('fail');
+					}
+				  });
+				  return
+				}
+				if ('chat' in item) {
+					return
+				}
+				console.log('item.checkauth',item.checkauth);
+				if(item.checkauth){
+					this.checkauth(item.url)
+				}else{
+					uni.$u.route(item.url);
+				}
+				
+			},
+			goLogin(){
+				uni.setStorage({
+					key: 'backUrl',
+					data:'center/center' ,
+					success: function () {
+						uni.$u.route('/pages/login/login')
+					}
+				});
+			},
+			checkauth(pageUrl){
+				if(this.vuex_member_info.name){
+					uni.$u.route(pageUrl)
+				}else{
+					uni.showModal({
+					  title: '提示',
+					  content: '你需要登录后,才可使用此功能!',
+					  success: res => {
+					    if (res.confirm) {
+							uni.setStorage({
+								key: 'backUrl',
+								data:pageUrl ,
+								success: function () {
+									uni.$u.route('/pages/login/login')
+								}
+							});
+					    }
+					  }
+					})
+				}
+			}
+		}
+	}
+</script>
+<style>
+page{
+	background-color: #fff;
+	padding-top: 0;
+}
+</style>
+<style lang="scss" scoped>
+$boxHeight:418rpx;
+.box { width: 100%;height:$boxHeight; margin: auto; overflow: hidden; }
+.home_top {
+	position: relative; 
+	width: 100%; 
+	z-index: 2;
+	height: $boxHeight;
+	.base-info{
+		position: absolute;
+		left: 32rpx;
+		right: 32rpx;
+		bottom: 90rpx;
+		color: #fff;
+	}
+}
+.home_top:after { width: 180%; height: $boxHeight; position: absolute; left: -40%; top: 0; z-index: -1; content: ''; border-radius: 0 0 50% 50%; background: linear-gradient(180deg, #EE0C0C 0%, #F39D9F 100%); }
+.block-wrap{
+	margin: 24rpx 32rpx;
+}
+.title{
+	font-size: 32rpx;
+	font-weight: 600;
+	color: #333333;
+	line-height: 45rpx;
+	margin-bottom: 30rpx;
+}
+.base-info{
+	margin-bottom: 10rpx;
+	.info{
+		margin-left: 30rpx;
+		.name{
+			font-size: 36rpx;
+			font-weight: bold;
+			color: #FFFFFF;
+			line-height: 54rpx;
+			margin-bottom: 10rpx;
+		}
+		.mobile{
+			width: fit-content;
+			font-size: 28rpx;
+			font-weight: 400;
+			color: #FFFFFF;
+			line-height: 42rpx;
+		}
+	}
+}
+
+
+
+.tools{
+	.tools-wrap{
+		
+	}
+	.tool-item{
+		padding: 12rpx 18rpx;
+		background-color: #FBFBFB;
+		margin-bottom: 20rpx;
+		border-radius: 20rpx;
+		overflow: hidden;
+		.name{
+			font-size: 32rpx;
+			font-weight: 400;
+			color: #363636;
+			margin-left: 12rpx;
+		}
+	}
+	.button-item{
+		background-color: transparent;
+		line-height: 1;
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #666666;
+		border: 0;
+		padding: 0;
+		top: -2px;
+		&::after{border: initial;}
+	}
+}
+</style>

+ 146 - 0
center/factorauth.vue

@@ -0,0 +1,146 @@
+<template>
+	<view class="pages">
+		<u-navbar
+			title="实名认证"
+			:placeholder="true"
+			:autoBack="true"
+			@leftClick="leftClick"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+		<view class="page-wrap">
+			<view class="login-box">
+				<u--form labelPosition="left" labelWidth="200rpx" :model="form" :rules="rules" ref="uForm" >
+					<u-form-item label="真实姓名" prop="name" borderBottom ref="name" >
+						<u--input
+							v-model="form.name"
+							placeholder="真实姓名"
+							border="none"
+							:customStyle="inputCustomStyle"
+						></u--input>
+					</u-form-item>
+					<u-form-item label="身份证号码" prop="idcard" ref="idcard" >
+						<u--input
+							v-model="form.idcard"
+							border="none"
+							placeholder="身份证号码"
+							:customStyle="inputCustomStyle"
+						></u--input>
+					</u-form-item>
+				</u--form>
+				<u-button 
+					@click="submit"
+					text="提交认证" 
+					type="error" 
+					shape="circle" 
+					:customStyle="{'margin-top':'60rpx',height:'98rpx','box-sizing':'border-box'}">
+				</u-button>
+			</view>
+		</view>
+		<u-toast ref="uToast"></u-toast>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				backUrl:'',
+				form:{
+					name:'',
+					idcard:''
+				},
+				rules: {
+					name: {
+						type: 'string',
+						required: true,
+						message: '请填写真实姓名',
+						trigger: ['blur', 'change']
+					},
+					idcard:[
+						{
+							type: 'string',
+							required: true,
+							message: '请填身份证号码',
+							trigger: ['blur', 'change']
+						},
+						{
+							validator: (rule, value, callback) => {
+								// 上面有说,返回true表示校验通过,返回false表示不通过
+								// uni.$u.test.mobile()就是返回true或者false的
+								return uni.$u.test.idCard(value);
+							},
+							message: '身份证号码不正确',
+							// 触发器可以同时用blur和change
+							trigger: ['change','blur'],
+						}
+					]
+				},
+				inputCustomStyle:{}
+			}
+		},
+		onShow() {
+		},
+		onReady() {
+			//onReady 为uni-app支持的生命周期之一
+			this.$refs.uForm.setRules(this.rules)
+		},
+		onLoad(page) {
+			console.log('page',page);
+			this.backUrl =  decodeURIComponent(page.backUrl)||'/center/center';
+			console.log('this.backUrl',this.backUrl);
+		},
+		methods: {
+			leftClick(){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			submit(){
+				// console.log('form',this.form);
+				this.$refs.uForm.validate().then(res => {
+					// uni.$u.toast('校验通过')
+					this.$u.api.factorAuth(this.form).then(res=>{
+						this.getMemberInfo()
+						// console.log('res',res.data);
+					}).catch(err=>{
+						// this.$refs.uToast.show({
+						// 	type: 'error',
+						// 	icon: false,
+						// 	title: '认证失败',
+						// 	message: err.msg,
+						// })
+						console.log('login',err);
+					})
+				}).catch(errors => {
+					uni.$u.toast('请正确填写表单')
+				})
+			},
+			getMemberInfo(){
+				let that = this;
+				this.$u.api.memberInfo({id:this.vuex_user_info.userid}).then(res=>{
+					this.$u.vuex('vuex_member_info', res.data);
+					uni.showToast({
+						title:'认证成功',
+						icon:'success'
+					})
+					setTimeout(()=>{
+						// console.log('this.backUrl',this.backUrl);
+						// uni.reLaunch({url: this.backUrl});
+						that.leftClick();
+					},1000)
+					// console.log('memberInfo',this.memberInfo);
+					}).catch(err=>{
+					// console.log('memberInfo',err.data);
+				})
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+
+</style>

+ 315 - 0
center/invoice.vue

@@ -0,0 +1,315 @@
+<template>
+	<view class="pages">
+		<view class="" :style="{height: navHeight+'px' }"></view>
+		<view class="navbar-box">
+			<u-navbar title="开具发票" :safeAreaInsetTop="true" @leftClick="leftClick" :titleStyle="{color:'#fff'}" leftIconColor="#fff" bgColor="#EF1818"></u-navbar>
+		</view>
+		<view class="page-wrap">
+			<mescroll-body class="" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback">
+				<!-- :down="downOption" :up="upOption" -->
+				<view class="list">
+					<u-checkbox-group placement="column" @change="checkboxChange" >
+					<view v-for="(item,index) in dataList" class="list-item u-flex" :key="item.id">
+						<view class="left">
+							<u-checkbox shape="circle" :disabled="item.quantity>item.stock" activeColor="#ED0000" :key="index" :name="item.name" :checked="item.checked" @change="toggleCheck(index)" class="checkbox" />
+						</view>
+						<view class="right">
+							<view class="order-number">订单号:360123456789079797</view>
+							<view class="info u-flex">
+								<image class="img" :src="item.showImg||staticUrl+'/img/newsdetails-banner.png'"></image>
+								<view class="text">
+									<view class="name">《伟大转折》-【成人票】</view>
+									<view class="time">2023-11–03 14:00-16:00</view>
+									<view class="position">5排6座</view>
+									<view class="statistics">
+										<text>共1张</text>
+										<text class="label">合计:</text>
+										<text class="price">¥120.00</text>
+									</view>
+								</view>
+							</view>
+						</view>
+					</view>
+					</u-checkbox-group>
+				</view>
+			</mescroll-body>
+		</view>
+		<view class="page-bottom">
+			<view class="inner u-flex u-row-between">
+				<view class="left">
+					<u-checkbox-group @change="allCheckboxChange">
+						<u-checkbox shape="circle" :checked="allChecked" activeColor="#ED0000" :name="allCheckbox.name" label="全选"></u-checkbox>
+					</u-checkbox-group>
+				</view>
+				<view class="right u-flex">
+					<view class="total-price u-flex">
+						<text>合计:</text>
+						<text class="num">{{totalPrice}}</text>
+					</view>
+					<view class="btn active" v-if="totalPrice>0&&cansubmit" @click="submitorder">申请开票</view>
+					<view class="btn" v-else>申请开票</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	export default {
+		mixins: [MescrollMixin,systemInfo], // 使用mixin
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				allCheckbox:{name: '全选'},
+				dataList:[
+					{name:'路线推荐',checked:false,pic:`${this.$commonConfig.staticUrl}/img/indexnav-luxian.png`,id:1},
+					{name:'路线推荐',checked:false,pic:`${this.$commonConfig.staticUrl}/img/indexnav-luxian.png`,id:2},
+				],
+			}
+		},
+		onShow() {
+		},
+		onLoad() {
+			this.getSystemInfo();
+
+		},
+		computed: {
+		    // 是否全选
+		    allChecked() {
+		        return this.dataList.every(item => item.checked)
+		    },
+		    // 商品合计价格
+		    selectGoods() {
+				let selectGoods = [];
+		        this.dataList.forEach(item => {
+		            if (item.checked) {
+		               selectGoods.push(item)
+		            }
+		        })
+		        return selectGoods
+		    },
+			totalPrice() {
+			  let that = this;
+			  return this.dataList.reduce((total, item) => {
+				if (item.checked) {
+					let price = null;
+					// if(that.vuex_member_info.priceType>1){
+					// 	price = item.vipPrice
+					// }else{
+					// 	price = item.salePrice
+					// }
+					price = item.levelPrice
+				  total += price * item.quantity;
+				}
+				return total;
+			  }, 0).toFixed(2);
+			}
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			/*下拉刷新的回调, 重置列表为第一页 (此处可删,mixins已默认)
+			downCallback(){
+				this.mescroll.resetUpScroll();
+			},
+			/*上拉加载的回调*/
+			upCallback(page) {
+				// 此处可以继续请求其他接口
+				// if(page.num == 1){
+				// 	// 请求其他接口...
+				// }
+			
+				// 如果希望先请求其他接口,再触发upCallback,可参考以下写法
+				// if(!this.hasTypeId){
+				// 	this.mescroll.endErr();//没有接口暂时不调用
+				// 	return // 此处return,先获取xx
+				// }
+			
+				let pageNum = page.num; // 页码, 默认从1开始
+				let pageSize = page.size; // 页长, 默认每页10条
+			
+				let params = {
+					pageNum : page.num,
+					pageSize :  page.size,
+					status:this.status
+				}
+				// console.log('this.params',params);
+				this.$u.api.orderList(params).then(data => {
+					this.hasfetch = true;
+					console.log('data',JSON.parse(JSON.stringify(data)));
+					// 接口返回的当前页数据列表 (数组)
+					let curPageData = data.data.rows;
+					curPageData = curPageData.map( item =>{
+						item.checked = false
+						return  item
+					})
+					// console.log('curPageData',JSON.parse(JSON.stringify(curPageData)));
+					// 接口返回的当前页数据长度 (如列表有26个数据,当前页返回8个,则curPageLen=8)
+					let curPageLen = curPageData.length; 
+					// 接口返回的总页数 (如列表有26个数据,每页10条,共3页; 则totalPage=3)
+					// let totalPage =  data.data.data.totalPage; 
+					// 接口返回的总数据量(如列表有26个数据,每页10条,共3页; 则totalSize=26)
+					let totalSize = data.data.total; 
+					// 接口返回的是否有下一页 (true/false)
+					// let hasNext = data.xxx; 
+					// console.log('totalPage',totalPage,'curPageLen',curPageLen);
+					//设置列表数据
+					if(page.num == 1) this.dataList = []; //如果是第一页需手动置空列表
+					this.dataList = this.dataList.concat(curPageData); //追加新数据
+					// 请求成功,隐藏加载状态
+					//方法一(推荐): 后台接口有返回列表的总页数 totalPage
+					// this.mescroll.endByPage(curPageLen, totalPage); 
+					//方法二(推荐): 后台接口有返回列表的总数据量 totalSize
+					this.mescroll.endBySize(curPageLen, totalSize); 
+				}).catch(err => {
+					this.mescroll.endErr()
+					console.log(err)
+				});	
+			
+			},
+			/*若希望重新加载列表,只需调用此方法即可(内部会自动page.num=1,再主动触发up.callback)*/
+			reloadList() {
+				this.mescroll.resetUpScroll();
+			},
+			search(e){
+				this.reloadList();
+			},
+			checkboxChange(n){
+				// console.log('checkboxChange',n);
+			},
+			toggleCheck(index) {
+				console.log('toggleCheck',index);
+			    this.dataList[index].checked = !this.dataList[index].checked
+			},
+			checkboxClick(){
+				console.log('checkboxClick',this.allChecked);
+				this.dataList.forEach(item => {
+					if(item.quantity<=item.stock){
+						item.checked = !this.allChecked
+					}
+				})
+			},
+			// 切换全选状态
+			allCheckboxChange(n){
+				// console.log('allCheckboxChange',n[0]);
+				// console.log('allCheckboxChange',n);
+				let selectAll = n[0]?true:false;
+				this.dataList.forEach(item => {
+					item.checked = selectAll
+				});
+				 console.log('selectGoods',this.selectGoods);
+			},
+
+		}
+	}
+</script>
+<style>
+	page{background-color: #F7F7F9;}
+</style>
+<style lang="scss" scoped>
+.list-item{
+	background: #FFFFFF;
+	box-shadow: 0rpx 2rpx 12rpx 0rpx rgba(221,221,221,0.5);
+	border-radius: 20rpx;
+	margin-bottom: 24rpx;
+	padding: 34rpx 32rpx 50rpx 20rpx;
+	.right{
+		flex: 1;
+		padding-left: 24rpx;
+		.order-number{
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			line-height: 36rpx;
+			margin-bottom: 36rpx;
+		}
+		.info{
+			.img{
+				width: 180rpx;
+				height: 160rpx;
+			}
+			.text{
+				font-size: 24rpx;
+				font-weight: 400;
+				color: #7F7F7F;
+				padding-left: 26rpx;
+				.name{
+					font-size: 28rpx;
+					font-weight: bold;
+					color: #363636;
+					margin-bottom: 20rpx;
+				}
+				.time{
+					margin-bottom: 20rpx;
+				}
+				.position{
+					margin-bottom: 20rpx;
+				}
+				.statistics{
+					text-align: right;
+				}
+				.label{
+					font-size: 24rpx;
+					font-weight: 400;
+					color: #363636;
+					margin-left: 10rpx;
+				}
+				.price{
+					font-size: 32rpx;
+					font-weight: bold;
+					color: #ED0000;
+				}
+			}
+		}
+	}
+}
+.page-bottom{
+	position: relative;
+	z-index: 1001;
+	height: 98rpx;
+	padding: 24rpx 20rpx 50rpx;
+	.inner{
+		position: fixed;
+		background-color: #fff;
+		height: 98rpx;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		padding: 24rpx 20rpx 50rpx;
+		box-shadow: 0rpx -4rpx 12rpx 0rpx rgba(215,215,215,0.5);
+		.total-price{
+			font-size: 28rpx;
+			font-weight: 400;
+			color: #313131;
+			margin-right: 32rpx;
+			.num{
+				font-size: 40rpx;
+				font-weight: bold;
+				color: #ED0000;
+			}
+		}
+		.btn{
+			font-size: 28rpx;
+			height: 80rpx;
+			line-height: 80rpx;
+			border-radius: 50rpx;
+			padding: 0 50rpx;
+			background-color: #eee;
+			color: #333;
+			text-align: center;
+			&.active{
+				background: linear-gradient(90deg, #FF7979 0%, #ED0000 100%);
+				color: #fff;
+			}
+		}
+	}
+}
+</style>

+ 250 - 0
center/memberinfo.vue

@@ -0,0 +1,250 @@
+<template>
+	<view class="pages">
+		<u-navbar
+			title="个人信息"
+			:placeholder="true"
+			:autoBack="true"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+		<view class="page-wrap">
+			<view class="cell-title">个人信息</view>
+			<u-cell-group :border="false" :customStyle="{background:'#fff','border-radius':'20rpx'}">
+				<!-- <u-cell  title="头像">
+					<u-avatar slot="right-icon" :src="memberInfo.avatar" size="80rpx"></u-avatar>
+				</u-cell> -->
+				<view class="avatar u-flex u-row-between u-border-bottom">
+					<text>头像</text>
+					<view class="right">
+						<u--image :src="memberInfo.avatar" shape="circle" width="80rpx" height="80rpx"></u--image>
+						<u-upload
+							 width="80rpx"
+							 height="80rpx"
+						    :fileList="fileList"
+						    @afterRead="afterRead"
+							:previewImage="false"
+							multiple
+						    name="name"
+						    :maxCount="2"
+						></u-upload>
+					</view>
+				</view>
+				<u-cell title="姓名" v-if="memberInfo.isAuth==1" @click="$u.toast('不允许修改')" :value="vuex_member_info.name" :border="true"></u-cell>
+				<u-cell title="昵称" @click="nameShow=true" :value="vuex_member_info.nickName" :border="true"></u-cell>
+				<!-- <u-cell title="性别" @click="handleChangeSex" :value="memberInfo.sex|filterSex"></u-cell> -->
+				<!-- <u-cell title="生日" :value="memberInfo.birthdayTime||'未设置'" @click="handleChangeBirthday" :isLink="true"></u-cell> -->
+				<!-- <u-cell title="会员等级" :value="memberInfo.levelName"></u-cell> -->
+				<u-cell title="实名制认证" @click="factorAuth" :value="memberInfo.isAuth==1?'已认证':'去认证'"></u-cell>
+			</u-cell-group>
+			<u-button text="取消登录" type="error" @click="logOut" :customStyle="{'margin-top': '120rpx'}"></u-button>
+			<u-datetime-picker
+				:show="timeShow"
+				:minDate="new Date().getTime()-365*100*24*3600*1000"
+				v-model="birthday"
+				:maxDate="new Date().getTime()"
+				@confirm="updateBirthday"
+				mode="date"
+				:closeOnClickOverlay="true"
+				@close="timeShow = false"
+				@cancel="timeShow = false"
+			></u-datetime-picker>
+		</view>
+		<u-modal :show="nameShow"  title="修改昵称" @confirm="changeName" @cancel="nameShow=false" :showCancelButton="true" >
+			<view class="slot-content">
+				<u--input
+				    placeholder="请输入内容"
+				    border="surround"
+				    v-model="tempName"
+					maxlength="8"
+				  >
+				</u--input>
+			</view>
+		</u-modal>
+		<u-action-sheet
+				:show="showSex"
+				:actions="actions"
+				title="请选择性别"
+				@close="showSex = false"
+				@select="sexSelect"
+		>
+		</u-action-sheet>
+		<u-toast ref="uToast"></u-toast>
+	</view>
+</template>
+
+<script>
+	import {uploadImg} from '../utils/uploadImg.js'
+	export default {
+		components:{
+			
+		},
+		data() {
+			return {
+				showSex:false,
+				sex:'',
+				nameShow:false,
+				tempName:'',
+				uploadFileUrl:this.$commonConfig.uploadFileUrl,
+				timeShow:false,
+				birthday:'',
+				memberInfo:{},
+				fileList:[],
+				avatarUrl:'',
+				actions: [
+					{name: '男',val:1},
+					{name: '女',val:2},
+					{name: '保密',val:0},
+				],
+			}
+		},
+		onShow() {	
+			this.getMemberInfo();
+			// console.log('1111',this.uploadFileUrl);
+		},
+		onLoad() {
+
+		},
+		methods: {
+			logOut(){
+				this.$u.vuex('vuex_member_info', {});
+				this.$u.vuex('vuex_user_info', {});
+				uni.$u.route('/pages/index/index')
+				// this.getMemberInfo()
+			},
+			getMemberInfo(){
+				this.$u.api.memberInfo({id:this.vuex_user_info.userId}).then(res=>{
+					this.memberInfo = res.data;
+					this.avatar =  res.data.avatar;
+					// console.log('memberInfo',this.memberInfo);
+					this.$u.vuex('vuex_member_info', res.data);
+					}).catch(err=>{
+					console.log('memberInfo',err);
+				})
+			},
+			handleChangeSex(){
+				if(this.memberInfo.isAuth==1){
+					this.$refs.uToast.show({
+						type: 'default',
+						message: "实名信息不能修改!",
+					})
+				}else{
+					this.showSex = true
+				}
+			},
+			handleChangeBirthday(){
+				if(this.memberInfo.isAuth==1){
+					this.$refs.uToast.show({
+						type: 'default',
+						message: "实名信息不能修改!",
+					})
+				}else{
+					this.timeShow = true
+				}
+			},
+			updateBirthday(e){
+				// console.log('000',e);
+				let birthday = uni.$u.timeFormat(e.value, 'yyyy-mm-dd');
+				this.birthday = birthday;
+				this.updateMemberInfo('birthday');
+				this.timeShow = false;
+			},
+			// 新增图片
+			async afterRead(event){
+				//使用这个封装
+				const result = await uploadImg(event,this.fileList)
+				let parseResult = JSON.parse(result.data).data;
+				console.log('urlurl',parseResult.url);
+				this.avatarUrl = parseResult.url;
+				if(!this.avatarUrl){
+					uni.showToast({
+						title: '上传失败!',
+						icon: "error"
+					});
+					return
+				};
+				this.updateMemberInfo('avatar');
+				let item = this.fileList[result.fileListLen]
+				this.fileList.splice(result.fileListLen, 1, Object.assign(item, {
+					status: 'success',
+					message: '成功',
+					url: JSON.parse(result.data).data
+				}));
+			},
+			updateMemberInfo(type){
+				let params ={
+					id:this.vuex_user_info.userid
+				};
+				if(type == 'avatar'){
+					params.avatar = this.avatarUrl;
+				}else if(type == 'birthday'){
+					params.birthdayTime = this.birthday;
+				}else if(type == 'nickName'){
+					if(!this.tempName){
+						uni.showToast({
+							title:'请输入昵称',
+							icon:'error'
+						})
+						return
+					}
+					params.nickName = this.tempName;
+				}else if(type == 'sex'){
+					params.sex = this.sex;
+				}
+				this.$u.api.updateMemberInfo(params).then(res=>{
+					this.getMemberInfo();
+					this.nameShow = false;
+				}).catch(err=>{
+					console.log('err',err);
+				})
+			},
+			changeName(){
+				this.updateMemberInfo('nickName');
+				console.log('memberInfo',this.memberInfo);
+			},
+			sexSelect(e){
+				this.sex = e.val;
+				this.updateMemberInfo('sex')
+				console.log('sexSelect',e);
+			},
+			factorAuth(){
+				if(this.memberInfo.isAuth==1){
+					uni.showToast({
+						title:'已认证',
+						icon:'none'
+					})
+					return
+				}
+				uni.$u.route('/center/factorauth', {
+					backUrl: '/center/center'
+				});
+			}
+		}
+	}
+</script>
+<style>
+page{
+	background-color: #F7F7F9;
+}
+</style>
+<style lang="scss" scoped>
+.cell-title{
+	font-size: 28rpx;
+	font-weight: 400;
+	color: #7F7F7F;
+	margin-bottom: 34rpx;
+}
+.avatar{
+	padding: 10px 15px;
+	font-size: 15px;
+	color: #303133;
+	.right{
+		position: relative;
+		/deep/ .u-upload{
+			position: absolute;
+			left: 0;
+			top: 0;
+			opacity: 0;
+		}
+	}
+}
+</style>

+ 253 - 0
center/mycoupon.vue

@@ -0,0 +1,253 @@
+<template>
+	<view class="pages">
+		<u-navbar
+			title="我的优惠券"
+			:placeholder="true"
+			:autoBack="false"
+			 @leftClick="leftClick"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+		<view class="tabs-wrap">
+			<view class="inner">
+				<u-tabs
+					:list="tabsArr"  
+					@click="tabsClick"
+					lineColor="#ED0000" 
+					lineWidth="82rpx"
+					:activeStyle="{color: '#2D2D2D',fontWeight: 'bold'}"
+					itemStyle="width:33%; height: 46px;box-sizing:border-box"
+				>
+				</u-tabs>
+			</view>
+		</view>
+		<view class="page-wrap">
+			<mescroll-body class="" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback">
+				<!-- :down="downOption" :up="upOption" -->
+				<view class="list">
+					<view v-for="(item,index) in dataList" class="list-item u-flex" :class="{hotel:index==1}" :key="item.id" :style="{backgroundImage:`url(${staticUrl}/img/coupon-bg.png)`}">
+						<view class="left u-flex u-row-center u-col-top">
+							<text class="icon">¥</text>
+							<text class="num">30</text>
+						</view>
+						<view class="right u-flex u-row-between">
+							<view class="text">
+								<view class="name">演出购票折扣券</view>
+								<view class="time">有限期:2023.10.22-2023.10.22</view>
+								<view class="rule u-flex">
+									使用规则
+									<u-icon name="arrow-right" color="#D56442" size="30rpx"></u-icon>
+								</view>
+							</view>
+							<view class="btn">去使用</view>
+						</view>
+					</view>
+				</view>
+			</mescroll-body>
+		</view>
+	</view>
+</template>
+
+<script>
+	// 引入mescroll-mixins.js
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		components: {
+			
+		},
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				// // 下拉刷新的配置(可选, 绝大部分情况无需配置)
+				// downOption: {
+				// },
+				// // 上拉加载的配置(可选, 绝大部分情况无需配置)
+				// upOption: {
+				// 	page: {
+				// 		size: 10 // 每页数据的数量,默认10
+				// 	},
+				// 	noMoreSize: 5, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+				// 	empty: {
+				// 		tip: '暂无相关数据'
+				// 	}
+				// },
+				params:{
+				},
+				dataList:[
+					{name:'路线推荐',pic:`${this.$commonConfig.staticUrl}/img/indexnav-luxian.png`,id:1},
+					{name:'路线推荐',pic:`${this.$commonConfig.staticUrl}/img/indexnav-luxian.png`,id:1},
+				],
+				tabsArr:[
+					{name:'待使用',status:0},
+					{name:'已使用',status:1},
+					{name:'已过期',status:2}
+				],
+				status:0,
+				
+			}
+		},
+		onShow() {
+		},
+		onLoad() {
+
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			/*下拉刷新的回调, 重置列表为第一页 (此处可删,mixins已默认)
+			downCallback(){
+				this.mescroll.resetUpScroll();
+			},
+			/*上拉加载的回调*/
+			upCallback(page) {
+				// 此处可以继续请求其他接口
+				// if(page.num == 1){
+				// 	// 请求其他接口...
+				// }
+			
+				// 如果希望先请求其他接口,再触发upCallback,可参考以下写法
+				// if(!this.hasTypeId){
+				// 	this.mescroll.endErr();//没有接口暂时不调用
+				// 	return // 此处return,先获取xx
+				// }
+			
+				let pageNum = page.num; // 页码, 默认从1开始
+				let pageSize = page.size; // 页长, 默认每页10条
+			
+				let params = {
+					pageNum : page.num,
+					pageSize :  page.size,
+					status:this.status
+				}
+				// console.log('this.params',params);
+				this.$u.api.orderList(params).then(data => {
+					this.hasfetch = true;
+					console.log('data',JSON.parse(JSON.stringify(data)));
+					// 接口返回的当前页数据列表 (数组)
+					let curPageData = data.data.rows;
+					// console.log('curPageData',JSON.parse(JSON.stringify(curPageData)));
+					// 接口返回的当前页数据长度 (如列表有26个数据,当前页返回8个,则curPageLen=8)
+					let curPageLen = curPageData.length; 
+					// 接口返回的总页数 (如列表有26个数据,每页10条,共3页; 则totalPage=3)
+					// let totalPage =  data.data.data.totalPage; 
+					// 接口返回的总数据量(如列表有26个数据,每页10条,共3页; 则totalSize=26)
+					let totalSize = data.data.total; 
+					// 接口返回的是否有下一页 (true/false)
+					// let hasNext = data.xxx; 
+					// console.log('totalPage',totalPage,'curPageLen',curPageLen);
+					//设置列表数据
+					if(page.num == 1) this.dataList = []; //如果是第一页需手动置空列表
+					this.dataList = this.dataList.concat(curPageData); //追加新数据
+					// 请求成功,隐藏加载状态
+					//方法一(推荐): 后台接口有返回列表的总页数 totalPage
+					// this.mescroll.endByPage(curPageLen, totalPage); 
+					//方法二(推荐): 后台接口有返回列表的总数据量 totalSize
+					this.mescroll.endBySize(curPageLen, totalSize); 
+				}).catch(err => {
+					this.mescroll.endErr()
+					console.log(err)
+				});	
+			
+			},
+			/*若希望重新加载列表,只需调用此方法即可(内部会自动page.num=1,再主动触发up.callback)*/
+			reloadList() {
+				this.mescroll.resetUpScroll();
+			},
+			search(e){
+				this.reloadList();
+			},
+			tabsClick(e){
+				console.log('tabsClick',e);
+				this.status = e.status;
+				this.reloadList();
+				// this.tabsIndex = e.index;
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+.tabs-wrap{
+	position: relative;
+	margin-bottom: 24rpx;
+	box-shadow: 0rpx 4rpx 8rpx 0rpx rgba(226,226,226,0.5);
+}
+.list{
+	.list-item{
+		width: 100%;
+		height: 158rpx;
+		background-repeat: no-repeat;
+		background-size: 100% 100%;
+		margin-bottom: 24rpx;
+		.left{
+			width: 190rpx;
+			.icon{
+				width: 26rpx;
+				height: 26rpx;
+				line-height: 26rpx;
+				text-align: center;
+				font-size: 10px;
+				color: #fff;
+				border-radius: 50%;
+				background: linear-gradient(360deg, #ED0000 0%, #F27C7D 100%);
+			}
+			.num{
+				font-size: 100rpx;
+				font-family: Helvetica, Helvetica;
+				font-weight: bold;
+				color: #000000;
+				background: linear-gradient(180deg, #F28082 0%, #ED0000 100%);
+				-webkit-background-clip: text;
+				-webkit-text-fill-color: transparent;
+			}
+		}
+		.right{
+			flex: 1;
+			padding: 32rpx 20rpx;
+			.text{
+				padding-left: 10rpx;
+				color: #D56442;
+				font-size: 20rpx;
+				font-weight: 400;
+			}
+			.name{
+				font-size: 28rpx;
+				font-weight: bold;
+				color: #C5412B;
+				margin-bottom: 16rpx;
+			}
+			.time{
+				margin-bottom: 16rpx;
+			}
+			.btn{
+				height: 50rpx;
+				line-height: 50rpx;
+				background: #ED0000;
+				border-radius: 25rpx;
+				font-size: 24rpx;
+				font-weight: bold;
+				color: #FFFFFF;
+				padding: 0 24rpx;
+			}
+		}
+		&.hotel{
+			.num{
+				background: linear-gradient(180deg, #F77941 0%, #E84816 100%);
+				background: linear-gradient(180deg, #F77941 0%, #E84816 100%);
+				-webkit-background-clip: text;
+				-webkit-text-fill-color: transparent;
+			}
+			.icon{
+				background: linear-gradient(360deg, #E84917 0%, #F97E45 100%);
+			}
+		}
+	}
+}
+</style>

+ 656 - 0
center/order.vue

@@ -0,0 +1,656 @@
+<template>
+	<view class="pages">
+		<u-navbar
+			title="我的订单"
+			:placeholder="true"
+			:autoBack="false"
+			 @leftClick="leftClick"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+		<view class="tabs-wrap">
+			<u-tabs 
+			:list="tabsList" 
+			lineColor="#EE0D0D" 
+			:current="tabsCurrent"
+			:activeStyle="{color:'#EE0D0D','font-weight': '600','font-size':'30rpx'}"
+			:inactiveStyle="{color:'#7F7F7F'}"
+			itemStyle="width:20%;box-sizing:border-box;padding:24rpx"
+			@click="tabsClick"></u-tabs>
+		</view>
+		<view class="page-wrap">
+			<mescroll-body class="" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :down="downOption" :up="upOption">
+				<view class="order">
+					<view v-for="(item,index) in orderListWithClass" class="order-item" 
+						:class="item.class"
+						@click="goOrderDetails(item)" :key="item.id">
+						<view class="top u-flex u-row-between">
+							<text>{{item.theatreName}}</text>
+							<text>{{item.status|filterOrderState}}</text>
+						</view>
+						<view class="info u-flex u-col-top">
+							<image class="img" :src="item.performImg"></image>
+							<view class="text">
+								<view class="name">{{item.performName}} - {{item.goodsName}}</view>
+								<view class="time">{{item.performDate}} {{item.performTimeStart}}</view>
+								<!-- <view class="position">{{item.performDate}}</view> -->
+								<view class="statistics">
+									<text>共{{item.viewerNum}}张</text>
+									<text class="label">合计:</text>
+									<text class="price">¥{{item.realPrice}}</text>
+								</view>
+							</view>
+						</view>
+						<view class="btn-wrap u-flex u-row-right">
+							<view
+								class="btn"
+								:class="btn.class"
+								@click.stop="clickEven(btn.fun,item)"
+								v-for="(btn,index) in statusBtn[item.status]" :key="index">
+								{{btn.name}}
+							</view>
+						</view>
+					</view>
+				</view>
+			</mescroll-body>
+		</view>
+		<u-toast ref="uToast"></u-toast>
+		<tabbar :tabbarIndexProps="1" />
+	</view>
+</template>
+
+<script>
+	import tabbar from "../components/tabbar.vue";
+	// 引入mescroll-mixins.js
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	// #ifdef H5
+	import wxH5 from "weixin-jsapi";
+	// #endif
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		components:{
+			tabbar
+		},
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				goodsKey:1,
+				hasfetch:false,
+				tabsCurrent:1,
+				tabsList:[
+					{name:'全部',status:null,},
+					{name:'待支付',status:'0',badge:{isDot:false,value:null}},
+					{name:'待使用',status:'3',badge:{isDot:false,value:null}},
+					{name:'已完成',status:'7',badge:{isDot:false,value:null}},
+					{name:'售后',status:'4,5,6',badge:{isDot:false,value:null}}
+				],
+				status:'',
+				dataList:[],
+				statusBtn:{// 状态(0待支付,2超时取消,3支付完成,待使用,4退款中,5己退款,6退款失败,7己使用,8己超期 9关闭)
+					0:[{name:'取消订单',fun:'cancelOrder',class:''},{name:'去支付',fun:'pay',class:'red'}],
+					1:[],
+					2:[],
+					3:[{name:'出示二维码',fun:'goOrderDetails',class:'red'}],
+					//,{name:'评价',fun:'evaluate',class:'green'}
+					4:[],
+					5:[],
+					6:[],
+					// ,{name:'查看评价',fun:'viewEvaluate',class:'green'}
+					7:[],
+					8:[],
+					9:[]
+				},
+				orderBadge:{
+					noPayNum:0,
+					deliverNum:0,
+					collectNum:0,
+					commentNum:0,
+					refundNum:0,
+				},
+				templateIdList:[],//微信小程序订阅消息
+			}
+		},
+		computed: {
+		  orderListWithClass() {
+		      return this.dataList.map(order => {
+				  // let spliceData = order.detailList.splice(0,2);
+				  
+				  // let moreData = order.detailList.splice(2,order.detailList.length);
+				  // let moreData = order.detailList.slice(2);
+				  // if(order.detailList.length>2){
+					 //  moreData =  order.detailList;
+				  // }
+		        return {
+		          ...order,
+				  // spliceData:spliceData,
+				  // moreData:moreData,
+				  // showMore:false,
+		          class: {
+					0: 'status-0',
+		            1: 'status-1',
+		            2: 'status-2',
+		            3: 'status-3',
+		            4: 'status-4',
+		            5: 'status-5',
+		            6: 'status-6',
+		            7: 'status-7',
+		            8: 'status-8'
+		          }[order.status] || ''
+		        }
+		      })
+		    }
+		},
+		onLoad(page) {
+			// console.log('page',page);
+			const status = page.status;
+			const index = this.tabsList.findIndex(item => item.status === status);
+			this.tabsCurrent = index>=0?index:0;
+			
+			this.getTemplateIdList();//获取模板列表
+			
+		},
+		onShow() {
+			setTimeout(()=>{
+				this.hasfetch&&this.reloadList()
+			},500);
+			// this.statisticsOrder();
+		},
+		methods: {
+			leftClick(){
+				uni.reLaunch({url: '/center/center'});
+			},
+			/*下拉刷新的回调, 重置列表为第一页 (此处可删,mixins已默认)
+			downCallback(){
+				this.mescroll.resetUpScroll();
+			},
+			/*上拉加载的回调*/
+			upCallback(page) {
+				// this.statisticsOrder();
+				// 此处可以继续请求其他接口
+				// if(page.num == 1){
+				// 	// 请求其他接口...
+				// }
+			
+				// 如果希望先请求其他接口,再触发upCallback,可参考以下写法
+				// if(!this.hasTypeId){
+				// 	this.shopNewsType();
+				// 	return // 此处return,先获取xx
+				// }
+			
+				let pageNum = page.num; // 页码, 默认从1开始
+				let pageSize = page.size; // 页长, 默认每页10条
+				
+				let params = {
+					pageNum : page.num,
+					pageSize :  page.size,
+					statusIn : this.tabsList[this.tabsCurrent]?.status||'',
+					userid:this.vuex_member_info.id
+					// status : this.tabsList[this.tabsCurrent].status,
+				}
+				// console.log('this.params',params);
+				this.$u.api.orderList(params).then(data => {
+					this.hasfetch = true;
+					console.log('data',JSON.parse(JSON.stringify(data)));
+					// 接口返回的当前页数据列表 (数组)
+					let curPageData = data.data.rows;
+					// console.log('curPageData',JSON.parse(JSON.stringify(curPageData)));
+					// 接口返回的当前页数据长度 (如列表有26个数据,当前页返回8个,则curPageLen=8)
+					let curPageLen = curPageData.length; 
+					// 接口返回的总页数 (如列表有26个数据,每页10条,共3页; 则totalPage=3)
+					// let totalPage =  data.data.data.totalPage; 
+					// 接口返回的总数据量(如列表有26个数据,每页10条,共3页; 则totalSize=26)
+					let totalSize = data.data.total; 
+					// 接口返回的是否有下一页 (true/false)
+					// let hasNext = data.xxx; 
+					// console.log('totalPage',totalPage,'curPageLen',curPageLen);
+					//设置列表数据
+					if(page.num == 1) this.dataList = []; //如果是第一页需手动置空列表
+					this.dataList = this.dataList.concat(curPageData); //追加新数据
+					// 请求成功,隐藏加载状态
+					//方法一(推荐): 后台接口有返回列表的总页数 totalPage
+					// this.mescroll.endByPage(curPageLen, totalPage); 
+					//方法二(推荐): 后台接口有返回列表的总数据量 totalSize
+					this.mescroll.endBySize(curPageLen, totalSize); 
+				}).catch(err => {
+					this.mescroll.endErr()
+					console.log(err)
+				});	
+			
+			},
+			/*若希望重新加载列表,只需调用此方法即可(内部会自动page.num=1,再主动触发up.callback)*/
+			reloadList() {
+				this.mescroll.resetUpScroll();
+			},
+			tabsClick(item){
+				// this.status = item.status;
+				this.tabsCurrent = item.index;
+				this.reloadList()
+				// console.log('item',item);
+			},
+			goOrderDetails(item){
+				uni.$u.route('/center/orderdetails', {
+					id: item.id
+				});
+			},
+			toggleMore(item,index){
+				// console.log('toggleMore',item);
+				this.orderListWithClass[index].showMore = !this.orderListWithClass[index].showMore
+				this.goodsKey++;
+			},
+			clickEven(fun,item){
+				// console.log('fun',fun);
+				let funObj = {
+				  pay: this.pay,
+				  goOrderDetails:this.goOrderDetails,
+				  evaluate:this.evaluate,
+				  refund:this.refund,
+				  cancelOrder:this.cancelOrder,
+				  viewRefund:this.viewRefund,
+				  confirmReceipt:this.confirmReceipt,
+				  viewEvaluate:this.viewEvaluate
+				};
+				// console.log('funObj[fun]',funObj[fun]);
+				if (fun in funObj) {
+				  funObj[fun](item);
+				}
+			},
+			getTemplateIdList(){
+				this.$u.api.templateIdList({templateLabel:'order_pay'}).then(res=>{
+					console.log('getTemplateIdList',res.data);
+					this.templateIdList = res.data.list.map(item=>{
+						return item.templateId
+					});
+					// if(this.templateIdList.length>0){
+					// 	this.templateEven();
+					// }
+				}).catch(err=>{
+					console.log('getTemplateIdList',err);
+				})
+			},
+			pay(item){
+				console.log('pay',item);
+				// #ifdef H5
+				this.gotoPay(item.id);
+				// #endif
+				// #ifdef MP
+				this.setTemplate(item.id);
+				// #endif
+				// uni.$u.route('/shopping/pay', {
+				// 	orderId: item.id,
+				// 	// openid: that.vuex_wechatOpenid,
+				// 	payAmount:item.orderPrice
+				// });
+			},
+			evaluate(item){
+				// console.log('logistics',item);
+				uni.$u.route('/shopping/evaluate', {
+					id: item.id
+				});
+			},
+			comment(orderId,orderDetailId,goodsId){
+				uni.$u.route('/shopping/addcomment', {
+					orderId,
+					orderDetailId,
+					goodsId
+				});
+			},
+			viewComment(orderDetailId,goodsId,goods){
+				uni.$u.route('/shopping/viewcomment', {
+					orderDetailId,
+					goodsId,
+					goods:JSON.stringify(goods)
+				});
+			},
+			refund(item){
+				// console.log('logistics',item);
+				uni.$u.route('/shopping/refund', {
+					id: item.id
+				});
+			},
+			cancelOrder(item){
+				let that = this;
+				uni.showModal({
+				    title: '提示',
+				    content: '确认取消吗!',
+				    success: res => {
+				        if (res.confirm) {
+				            this.$u.api.cancelOrder({orderId:item.id}).then(res=>{
+				            	this.$refs.uToast.show({
+				            		message:res.msg,
+				            		complete() {
+				            			that.reloadList();
+				            		}
+				            	});
+				            	// uni.$u.toast(res.msg);
+				            	console.log('res',res.data);
+				            }).catch(err=>{
+				            	console.log('cancelOrder',err);
+				            })
+				        }
+				    }
+				});
+				// console.log('logistics',item);
+				
+			},
+			viewRefund(item){
+				uni.$u.route('/center/viewRefund', {
+					orderId: item.id
+				});
+			},
+			confirmReceipt(item){
+				let that = this;
+				uni.showModal({
+				    title: '提示',
+				    content: '确认收货吗!',
+				    success: res => {
+				        if (res.confirm) {
+				            this.$u.api.confirmReceipt({orderId:item.id}).then(res=>{
+								uni.showToast({
+									title:res.msg,
+									duration:2000,
+									complete() {
+										that.reloadList();
+									}
+								});
+				            	console.log('res',res.data);
+				            }).catch(err=>{
+				            	console.log('confirmReceipt',err);
+				            })
+				        }
+				    }
+				})
+			},
+			viewEvaluate(item){
+				uni.$u.route('/shopping/orderdetails', {
+					id: item.id
+				});
+			},
+			// 设置小程序订阅消息
+			setTemplate(orderId) {
+				let that = this;
+				console.log('templateIdList',this.templateIdList);
+				uni.requestSubscribeMessage({  
+					tmplIds: that.templateIdList,  
+					success (res) {  
+						// that.gotoPay();
+						console.log("success:",res);  
+					},  
+					fail (res) {  
+						console.log("fail:",res);  
+					},  
+					complete (res) {  
+						that.gotoPay(orderId);
+						console.log("complete:",res);  
+					}  
+				})  
+			},
+			gotoPay(orderId){
+				this.$u.api.gotoPay({orderId:orderId,openid:''}).then(res=>{
+					this.payResult = res.data.payInfo;
+					this.payResult.package = res.data.payInfo.packageValue;
+					// #ifdef H5
+					this.initConfig(this.payResult)
+					// #endif 
+					// #ifdef MP
+					this.wxPay()
+					// #endif
+					// if(this.params.paymentMode==1||this.params.paymentMode==4){
+					// 	this.wxPay()
+					// }else{
+					// 	uni.$u.route('/shopping/paysuccess');
+					// }
+					console.log('gotoPayres',res.data);
+				}).catch(err=>{
+					this.paypass = '';
+					this.checkPassShow = false;
+					console.log('gotoPay',err);
+				})				
+			},
+			wxPay(){
+				let that = this;
+				uni.requestPayment({
+					... this.payResult,
+				    "provider": "wxpay", 
+				    "orderInfo": {
+				        // "appid": "wx499********7c70e",  // 微信开放平台 - 应用 - AppId,注意和微信小程序、公众号 AppId 可能不一致
+				        // "noncestr": "c5sEwbaNPiXAF3iv", // 随机字符串
+				        // "package": "Sign=WXPay",        // 固定值
+				        // "partnerid": "148*****52",      // 微信支付商户号
+				        // "prepayid": "wx202254********************fbe90000", // 统一下单订单号 
+				        // "timestamp": 1597935292,        // 时间戳(单位:秒)
+				        // "sign": "A842B45937F6EFF60DEC7A2EAA52D5A0" // 签名,这里用的 MD5/RSA 签名
+				    },
+				    success(res) {
+						setTimeout(()=>{
+							this.tabsCurrent = 2;
+							this.reloadList();
+						},1500)
+						// that.payQuery();
+					},
+				    fail(e) {
+						console.log('wxPayfail',e);
+					}
+				})				
+			},
+			payQuery(){
+				let that = this;
+				let retryCount = 0;
+				let maxRetryCount = 5; // 设置最大重试次数
+				let interval = 2000; // 设置间隔时间为2秒
+				let timeout = 10000; // 设置超时时间为10秒
+				let timer;
+				uni.showLoading({
+					title:'支付结果查询中'
+				})
+				timer = setInterval(() => {
+					retryCount++;
+					if (retryCount > maxRetryCount || retryCount * interval > timeout) {
+						clearInterval(timer);
+						  uni.hideLoading();
+						console.log("支付查询超时或达到最大重试次数");
+						// 在这里添加超时或达到最大重试次数的处理逻辑
+						this.reloadList()
+					} else {
+						console.log("第" + retryCount + "次查询");
+						// 调用查询支付状态的方法
+						// 如果支付状态为成功,则清除定时器并处理成功的逻辑
+						// 如果支付状态为失败,则清除定时器并处理失败的逻辑
+						this.$u.api.payQuery({orderId:this.orderId}).then(res=>{
+							// 0-未支付 1-已支付 2-支付中 3-支付失败 4-支付退款
+							let payStatus = res.data.payStatus;
+							if(payStatus===1){
+								uni.$u.route('/center/paysuccess');
+							}else if(payStatus===0||payStatus===2){
+								this.payQuery()
+							}else if(payStatus===3){
+								uni.toast('支付失败')
+							}
+							clearInterval(timer);
+						}).catch(err=>{
+							console.log('payQuery',err);
+						}).finally(()=>{
+							uni.hideLoading()
+						})
+					}
+				}, interval);
+			},
+			statisticsOrder(){
+				this.$u.api.statisticsOrder().then(res=>{
+					let data = res.data || {};
+					this.orderBadge = Object.assign(this.orderBadge,data);
+					let {noPayNum,deliverNum,collectNum,commentNum,refundNum} = res.data;
+					let noPayNumindex = this.tabsList.findIndex(item => item.status == 0);
+
+						this.tabsList[1].badge.isDot = false;
+						this.tabsList[1].badge.value = noPayNum;
+
+
+						this.tabsList[2].badge.isDot = false;
+						this.tabsList[2].badge.value = deliverNum;
+
+
+						this.tabsList[3].badge.isDot = false;
+						this.tabsList[3].badge.value = collectNum;
+
+
+						this.tabsList[4].badge.isDot = false;
+						this.tabsList[4].badge.value = commentNum;
+
+
+						this.tabsList[5].badge.isDot = false;
+						this.tabsList[5].badge.value = refundNum;
+
+					// console.log('statisticsOrder',res.data);
+					// console.log('this.orderBadge',this.orderBadge);
+					}).catch(err=>{
+					console.log('memberInfo',err.data);
+				})
+			},
+			/**
+			 * 公众号微信支付
+			 */
+			initConfig() {
+				// #ifdef H5
+				let that = this
+				wxH5.config({
+					debug: false, // 这里一般在测试阶段先用ture,等打包给后台的时候就改回false, 
+					appId: that.payResult.appId, // 必填,公众号的唯一标识 
+					timestamp: that.payResult.timeStamp, // 必填,生成签名的时间戳     
+					nonceStr: that.payResult.nonceStr, // 必填,生成签名的随机串 
+					signature: that.payResult.paySign, // 必填,签名 
+					jsApiList: ['chooseWXPay', 'checkJsApi'] // 必填,需要使用的JS接口列表 
+				})
+				wxH5.ready(() => {
+					wxH5.chooseWXPay({
+						timestamp: that.payResult.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符     
+						nonceStr: that.payResult.nonceStr, // 支付签名随机串,不长于 32 位         
+						package: that.payResult.packageValue, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)     
+						signType: 'SHA1', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'     
+						paySign: that.payResult.paySign, // 支付签名     
+						success: () => {
+							setTimeout(()=>{
+								that.tabsCurrent = 2;
+								that.reloadList();
+							},1500)
+							// that.payQuery();
+						},
+						fail: (e) => {
+							uni.$u.route('/center/order', {
+								status: 0
+							});
+							console.log('wxPayfail', e);
+						},
+						cancel: () => {
+							uni.$u.route('/center/order', {
+								status: 0
+							});
+							that.cansubmit = true;
+						}
+					})
+				})
+				// #endif
+			}
+		}
+	}
+</script>
+<style>
+page{
+	background-color: #F5F5F5;
+}
+</style>
+<style lang="scss" scoped>
+.tabs-wrap{
+	background-color: #fff;
+	margin-bottom: 10rpx;
+}
+.list-item{
+	overflow: hidden;
+	width: 48%;
+	margin-bottom: 30rpx;
+	background-color: #fff;
+	.image-wrap{
+		margin-bottom: 20rpx;
+	}
+}
+.order-item{
+	margin-bottom: 20rpx;
+	background: #FFFFFF;
+	box-shadow: 0rpx 2rpx 12rpx 0rpx rgba(221,221,221,0.5);
+	border-radius: 20rpx;
+	padding: 32rpx;
+	content-visibility: auto;
+	contain-intrinsic-size: 346rpx;
+	.top{
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #7F7F7F;
+		line-height: 36rpx;
+		margin-bottom: 36rpx;
+	}
+	.info{
+		margin-bottom: 30rpx;
+		.img{
+			width: 180rpx;
+			height: 160rpx;
+		}
+		.text{
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			padding-left: 26rpx;
+			flex: 1;
+			.name{
+				font-size: 28rpx;
+				font-weight: bold;
+				color: #363636;
+				margin-bottom: 20rpx;
+			}
+			.time{
+				margin-bottom: 20rpx;
+			}
+			.position{
+				margin-bottom: 20rpx;
+			}
+			.statistics{
+				text-align: right;
+			}
+			.label{
+				font-size: 24rpx;
+				font-weight: 400;
+				color: #363636;
+				margin-left: 10rpx;
+			}
+			.price{
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #ED0000;
+			}
+		}
+	}
+	.btn-wrap{
+		
+	}
+	.btn{
+		padding: 12rpx 40rpx;
+		border: 1px solid #E5E5E5;
+		color: #7F7F7F;
+		text-align: center;
+		border-radius: 8rpx;
+		font-size: 24rpx;
+		&.red{border-color: #ED0000;color: #ED0000;}
+		&.red.solid{border-color: #ED0000;background-color:#ED0000;color: #fff;}
+		&.green{color: #00A447;border-color: #00A447;}
+		&:not(:first-child){
+			margin-left: 20rpx;
+		}
+	}
+	&.status-0{.status{color:#FF3C3F;}}
+	&.status-1{.status{color:#FF3C3F;}}
+	&.status-2{.status{color:#0099EB;}}
+	&.status-3{.status{color:#00A447;}}
+	&.status-4{.status{color:#FF3C3F;}}
+	&.status-5{.status{color:#FF3C3F;}}
+	&.status-6{.status{color:#FF3C3F;}}
+	&.status-7{.status{color:#FFB100;}}
+}
+</style>

+ 874 - 0
center/orderdetails.vue

@@ -0,0 +1,874 @@
+<template>
+	<view class="pages">
+		<view class="" :style="{height: navHeight+'px' }"></view>
+		<view class="navbar-box">
+			<u-navbar title="订单详情" :safeAreaInsetTop="true" @leftClick="leftClick" :titleStyle="{color:'#fff'}" leftIconColor="#fff" bgColor="transparent"></u-navbar>
+		</view>
+		<u-loading-page :loading="loadingPage"></u-loading-page>
+		<view class="page-wrap">
+			<view class="status-content">
+				<view class="status-item" v-if="orderDetails.status==0">
+					<view class="text"><text v-if="countdown!='支付超期'"> 请尽快完成付款,还剩 </text>{{countdown}}</view>
+					<view class="btn-wrap u-flex u-row-center" v-if="countdown!='支付超期'">
+						<view class="btn cancel" @click="cancelOrder">取消订单</view>
+						<view class="btn" @click="gotoPay(orderDetails.id)">立即支付</view>
+					</view>
+				</view>
+				<view class="status-item" v-else-if="orderDetails.status==3">
+					<view class="text"><text v-if="countdown!='演出已开始'">距离演出开始,还剩 </text>{{countdown}}</view>
+				</view>
+				<view class="status-item" v-else-if="orderDetails.status==6">
+					<view class="text"><text>退款失败: </text>{{refundInfo.errReason}}</view>
+				</view>
+				<view class="status-item" v-else>
+					<view class="text">{{orderDetails.status|filterOrderState}}</view>
+				</view>
+			</view>
+			<view class="base-info">
+				<view class="top u-flex u-row-between">
+					<view class="name">{{orderDetails.theatreName}}</view>
+					<!-- <view class="icon-wrap u-flex">
+						<u-icon name="phone-fill" color="#999999" size="42rpx"></u-icon>
+						<u-icon name="map-fill" color="#999999" size="42rpx"></u-icon>
+					</view> -->
+				</view>
+				<view class="info u-flex u-row-between">
+					<image class="img" :src="orderDetails.performImg"></image>
+					<view class="text">
+						<view class="name u-flex u-row-between">
+							{{orderDetails.performName}}
+							<!-- <text class="status">{{orderDetails.status|filterOrderState}}</text> -->
+						</view>
+						<view class="time">{{orderDetails.performDate}} {{orderDetails.performTimeStart}}</view>
+						<view class="position u-flex u-row-between">
+							<view class="">{{orderDetails.goodsName}}/{{orderDetails.seatTypeName}}</view>
+							<view class="">X {{orderDetails.viewerNum}}</view>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="box qr-wrap" v-if="orderDetails.parentQrcodeStatus==0">
+				<view class="title">二维码</view>
+				<view class="qr-content">
+					<view class="img-wrap">
+						<!-- <image class="img" :src="orderDetails.performImg"></image> -->
+						<view class="ayQrcode" @click="refreshCode">
+							<ayQrcode v-show="!showSingleQR" ref="qrcode" :modal="modal_qr" :url="qrContent" @hideQrcode="hideQrcode" :height="180" :width="180" />
+						</view>
+						<!-- <image v-if="orderDetails.status==4" class="refund-ico" :src="staticUrl+'/img/refund-ico.png'"></image> -->
+					</view>
+					<!-- <view class="">{{orderDetails.viewersList.length}}张演出票</view> -->
+					 <!-- order-num del-line -->
+					<view class="order-num">取票号:{{orderDetails.parentQrcodeNo}}</view>
+				</view>
+			</view>
+			<view class="box viewers-list">
+				<view class="title">观影人员</view>
+				<view class="item u-flex u-row-around" v-for="(item,index) in orderDetails.viewersList" :key="index">
+					<text>观影人员{{index + 1}}</text>
+					<text style="width: 96rpx;">{{item.name}}</text>
+					<text>{{item.idcard|maskID}}</text>
+					<text v-if="orderDetails.status==3" @click="singleQR(item,index)" class="qrbtn">查看二维码</text>
+				</view>
+			</view>
+			<view class="box order-info">
+				<view class="title u-flex u-row-between">
+					订单信息
+					<text class="btn" v-if="orderDetails.status==3" @click="refund">申请退款</text>
+				</view>
+				<view class="order-info-item" v-for="(item,index) in orderInfo[orderDetails.status]" :key="index">
+					<text class="til">{{item.name}}</text>
+					<text class="con">
+						<text v-if="item.key=='payType'">{{orderDetails[item.key]|filterPayType}}</text>
+						<text v-else-if="item.key=='realPrice'"> ¥ {{orderDetails[item.key]}}</text>
+						<text v-else>{{orderDetails[item.key]}}</text>
+						<text class="copy-btn" @click="copyOrderNum(orderDetails[item.key])" v-if="item.key=='id'">复制</text>
+					</text>
+				</view>
+			</view>
+			<view class="box order-info refundInfo" v-if="orderDetails.status==4||orderDetails.status==5||orderDetails.status==6">
+				<view class="title u-flex u-row-between">
+					退款信息
+				</view>
+				<view class="order-info-item">
+					<text class="til">退款单号</text>
+					<text class="con">{{refundInfo.id}}</text>
+					<text class="copy-btn" @click="copyOrderNum(refundInfo.id)" >复制</text>
+				</view>
+				<view class="order-info-item">
+					<text class="til">申请时间</text>
+					<text class="con">{{refundInfo.refundTime}}</text>
+				</view>
+				<view class="order-info-item">
+					<text class="til">订单金额</text>
+					<text class="con">¥ {{refundInfo.orderAmount}}</text>
+				</view>
+				<view class="order-info-item">
+					<text class="til">退款金额</text>
+					<text class="con">¥ {{refundInfo.refundAmount}}</text>
+				</view>
+				<view class="order-info-item">
+					<text class="til">退款原因</text>
+					<text class="con">{{refundInfo.refundReason}}</text>
+				</view>
+				<view class="order-info-item" v-if="orderDetails.status==6">
+					<text class="til">失败原因</text>
+					<text class="con">{{refundInfo.errReason}}</text>
+				</view>
+				<view class="order-info-item">
+					<text class="til">退款状态</text>
+					<text class="con">{{refundInfo.status|filterRefundState}}</text>
+				</view>
+			</view>
+			<view class="box tips parse-content">
+				<view class="title">观影须知</view>
+				<u-parse :content="formerNotice"></u-parse>
+				<!-- <view class="tips-item" v-for="(item,index) in tipsArr" :key="index">
+					{{item}}
+				</view> -->
+			</view>
+		</view>
+		<u-popup :show="showSingleQR" @close="closeSingleQR" @open="openSingleQR" @touchmove.stop.prevent="handleMoveQrcode" :round="10" mode="center">
+			<view class="singleQR-wrap">
+				<view class="title">
+					二维码
+					<!-- <u-icon class="close" @click="closeSingleQR" name="close" color="#333" size="36rpx"></u-icon> -->
+				</view>
+				<view class="single-info">
+					<view class="item">姓名:{{singleInfo.name}}</view>
+					<view class="item">身份证号码:{{singleInfo.idcard|maskID}}</view>
+					<view class="item">座位:{{singleInfo.seatName}}</view>
+					<view class="item">状态:{{singleInfo.status|filterSingleState}}</view>
+				</view>
+				<view class="singleQrcode">
+					<ayQrcode ref="singleqrcode" :modal="singleModalQr" :url="singleQrContent" @hideQrcode="hideSingleQrcode" :height="180" :width="180" />
+				</view>
+				<view class="order-num">取票号:{{singleInfo.qrcodeNo}}</view>
+			</view>
+		</u-popup>
+		<u-toast ref="uToast"></u-toast>
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	import ayQrcode from "@/components/ay-qrcode/ay-qrcode.vue"
+	// #ifdef H5
+	import wxH5 from "weixin-jsapi";
+	// #endif
+	export default {
+		mixins:[systemInfo],
+		components:{
+			ayQrcode
+		},
+		data() {
+			return {
+				timer: null,
+				modal_qr: false,
+				qrContent: {}, // 要生成的二维码值
+				id:'',
+				loadingPage:true,
+				staticUrl:this.$commonConfig.staticUrl,
+				orderDetails:{},
+				deadline: null, // 设置截止时间为某个日期的时间戳
+				countdown: '', // 用于显示倒计时时间
+				// 状态(0待支付,2超时取消,3支付完成,待使用,4退款中,5己退款,6退款失败,7己使用,8己超期)
+				orderInfo:{
+					0:[{name:'需付金额',key:'realPrice'},{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'}],
+					1:[{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'支付方式',key:'payWay'},{name:'支付时间',key:'payTime'}],
+					2:[{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'支付方式',key:'payWay'},{name:'支付时间',key:'payTime'}],
+					3:[{name:'订单金额',key:'realPrice'},{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'支付方式',key:'payWay'},{name:'支付时间',key:'payTime'}],
+					4:[{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'支付方式',key:'payWay'},{name:'支付时间',key:'payTime'}],
+					5:[{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'支付方式',key:'payWay'},{name:'支付时间',key:'payTime'},{name:'备注信息',key:'remark'}],
+					6:[{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'支付方式',key:'payWay'},{name:'支付时间',key:'payTime'},{name:'备注信息',key:'remark'}],
+					7:[{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'支付方式',key:'payWay'},{name:'支付时间',key:'payTime'}],
+					8:[{name:'订单金额',key:'realPrice'},{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'备注信息',key:'remark'}],
+					9:[{name:'订单金额',key:'realPrice'},{name:'订单编号',key:'id'},{name:'下单时间',key:'createTime'},{name:'备注信息',key:'remark'}],
+				},
+				tipsArr:[
+					'1.请提前到达影院现场,找到自助取票机,打印纸质电影票,完成取票',
+					'2.如现场自助取票机无法打印电影票,请联系影院工作人员处理',
+					'3.凭打印好的纸质电影票、检票入场观影',
+					'4.如果订单使用了兑换券,或购买了特殊场次,暂不支持退票和改签'
+				],
+				params:{
+					
+				},
+				singleInfo:{idcard:''},//观影人信息
+				showSingleQR:false,//显示单人二维码
+				singleModalQr:false,
+				singleQrContent:{},
+				formerNotice:'',
+				countdownTimer:null,//订单倒计时
+				templateIdList:[],//微信小程序订阅消息
+				refundInfo:{status:null},//退款信息
+				
+			}
+		},
+		onShow() {
+			this.getOrderDetails(this.id);
+		},
+		onLoad(page) {
+			console.log('page',page);
+			this.id = page.id;
+			this.getSystemInfo();
+			
+			this.getTemplateIdList();//获取模板列表
+
+		},
+		onUnload() {
+		    // 页面离开时停止计时器
+		    // clearInterval(this.timer)
+			clearInterval(this.countdownTimer)
+		},
+		mounted() {
+			// 每秒更新倒计时时间
+			this.countdownTimer = setInterval(() => {
+			  const now = new Date().getTime();
+			  const distance = this.deadline - now;
+			  // console.log('distance',distance);
+			  if(this.orderDetails.status==0 && distance<0){
+				  this.countdown = '支付超期';
+				  return
+			  }else if(this.orderDetails.status==3 && distance<0){
+				  this.countdown = '演出已开始';
+				  return
+			  }
+		
+			  // 计算倒计时时间,可以根据需求自定义显示方式
+			  const days = Math.floor(distance / (1000 * 60 * 60 * 24));
+			  const hours = Math.floor((distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
+			  const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60));
+			  const seconds = Math.floor((distance % (1000 * 60)) / 1000);
+		
+			  // 更新倒计时时间
+			  this.countdown = `${days}天 ${hours}小时 ${minutes}分钟 ${seconds}秒`;
+			}, 1000);
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			getTemplateIdList(){
+				this.$u.api.templateIdList({templateLabel:'order_pay'}).then(res=>{
+					console.log('getTemplateIdList',res.data);
+					this.templateIdList = res.data.list.map(item=>{
+						return item.templateId
+					});
+					// if(this.templateIdList.length>0){
+					// 	this.templateEven();
+					// }
+				}).catch(err=>{
+					console.log('getTemplateIdList',err);
+				})
+			},
+			// 展示二维码
+			showQrcode() {
+				let _this = this;
+				this.modal_qr = true;
+				// uni.showLoading()
+				setTimeout(function() {
+					// uni.hideLoading()
+					_this.$refs.qrcode.crtQrCode()
+				}, 50)
+			},
+			//传入组件的方法
+			hideQrcode() {
+				this.modal_qr = false;
+			},
+			refreshCode(){
+				this.qrContent = {};
+				this.qrContent.qrcode = this.orderDetails.parentQrcodeNo;
+				this.qrContent.time = Date.now();
+				this.qrContent = JSON.stringify(this.qrContent);
+				this.showQrcode()
+			},
+			hideSingleQrcode(){
+				this.singleModalQr = false;
+			},
+			getOrderDetails(id){
+				this.$u.api.orderDetails({id:id}).then(res=>{
+					this.loadingPage = false;
+					this.orderDetails = res.data;
+					this.refundInfo =  res.data.refundInfo||{};
+					if(res.data.status==0){//待支付
+						this.deadline = new Date(`${res.data.cancelDateTime}`).getTime();
+					}else if(res.data.status==3){//待使用
+						this.deadline = new Date(`${res.data.performDateTime}`).getTime();
+					}
+					if(res.data.parentQrcodeStatus==0){
+						this.qrContent = {};
+						this.qrContent.qrcode = res.data.parentQrcodeNo;
+						this.qrContent.time = Date.now();
+						this.qrContent = JSON.stringify(this.qrContent);
+						let that = this;
+						that.showQrcode();//一加载生成二维码
+						// this.refreshCode()
+					}
+					
+					this.getPerformerNotice();
+					// console.log('this.deadline',this.deadline);
+					// console.log('orderDetails',JSON.parse(JSON.stringify(res.data)));
+				}).catch(err=>{
+					uni.$u.toast('获取数据失败,!请重试')
+					uni.navigateBack();
+					console.log('getOrderDetails',err);
+				})
+			},
+			singleQR(item,index){
+				let that = this;
+				console.log('singleQR',item);
+				this.showSingleQR = true;
+				this.singleInfo = item;
+				
+				this.singleQrContent = {};
+				this.singleQrContent.qrcode = item.qrcodeNo;
+				this.singleQrContent.time = Date.now();
+				this.singleQrContent = JSON.stringify(this.singleQrContent);
+				that.showSingleQrcode();//一加载生成二维码
+			},
+			// 展示二维码
+			showSingleQrcode() {
+				let _this = this;
+				this.singleModalQr = true;
+				// uni.showLoading()
+				setTimeout(function() {
+					// uni.hideLoading()
+					_this.$refs.singleqrcode.crtQrCode()
+				}, 50)
+			},
+			closeSingleQR(){
+				this.showSingleQR = false;
+			},
+			openSingleQR(){
+				
+			},
+			clickEven(fun,item){
+				// console.log('fun',fun);
+				let funObj = {
+				  pay: this.pay,
+				  logistics:this.logistics,
+				  evaluate:this.evaluate,
+				  refund:this.refund,
+				  cancelOrder:this.cancelOrder,
+				  viewRefund:this.viewRefund,
+				  confirmReceipt:this.confirmReceipt
+				};
+				// console.log('funObj[fun]',funObj[fun]);
+				if (fun in funObj) {
+				  funObj[fun](item);
+				}
+			},
+			pay(item){
+				// #ifdef H5
+					this.gotoPay(this.orderDetails.id);
+				// #endif
+				// #ifdef MP
+					this.setTemplate(this.orderDetails.id);
+				// #endif
+				// console.log('pay',item);
+				// uni.$u.route('/center/pay', {
+				// 	orderId: item.id,
+				// 	// openid: that.vuex_wechatOpenid,
+				// 	payAmount:item.orderPrice
+				// });
+			},
+			logistics(item){
+				// console.log('logistics',item);
+				uni.$u.route('/center/distribution', {
+					orderId: item.id
+				});
+			},
+			evaluate(item){
+				// console.log('logistics',item);
+				uni.$u.route('/shopping/evaluate', {
+					id: item.id
+				});
+			},
+			refund(item){
+				console.log('this.orderDetails',this.orderDetails);
+				uni.$u.route('/center/refund', {
+					id: this.orderDetails.id,
+					realPrice:this.orderDetails.realPrice
+				});
+			},
+			cancelOrder(item){
+				let that = this;
+				uni.showModal({
+				    title: '提示',
+				    content: '确认取消吗!',
+				    success: res => {
+				        if (res.confirm) {
+				            this.$u.api.cancelOrder({orderId:that.orderDetails.id}).then(res=>{
+				            	this.$refs.uToast.show({
+				            		message:res.msg,
+				            		complete() {
+				            			that.getOrderDetails(that.orderDetails.id);
+				            		}
+				            	});
+				            	// uni.$u.toast(res.msg);
+				            	console.log('res',res.data);
+				            }).catch(err=>{
+				            	console.log('cancelOrder',err);
+				            })
+				        }
+				    }
+				});
+				// console.log('logistics',item);
+				
+			},
+			confirmReceipt(item){
+				let that = this;
+				uni.showModal({
+				    title: '提示',
+				    content: '确认收货吗!',
+				    success: res => {
+				        if (res.confirm) {
+				            this.$u.api.confirmReceipt({orderId:item.id}).then(res=>{
+								uni.showToast({
+									title:res.msg,
+									duration:2000,
+									complete() {
+										that.evaluate(item)
+										that.getOrderDetails(that.id);
+									}
+								});
+				            	console.log('res',res.data);
+				            }).catch(err=>{
+				            	console.log('confirmReceipt',err);
+				            })
+				        }
+				    }
+				})
+			},
+			goMap(){
+				uni.openLocation({
+				  latitude: Number(this.pickupInfo.pickupLatitude),	//维度
+				  longitude: Number(this.pickupInfo.pickupLongitude), //经度
+				  name: "商行地址",	//目的地定位名称
+				  scale: 15,	//缩放比例
+				  address: this.pickupInfo.pickupAddress	//导航详细地址
+				})
+			},
+			copyOrderNum(orderNum){
+				// console.log('copyOrderNum',orderNum);
+				uni.setClipboardData({
+				    data: orderNum,
+				    success: function () {
+				      uni.showToast({
+				        title: '复制成功'
+				      });
+				    },
+					fail(e) {
+						// console.log('copyOrderNumfail',e);
+						uni.showToast({
+						  title: '复制失败',
+						  icon:"error"
+						});
+					}
+				});
+			},
+			// 设置小程序订阅消息
+			setTemplate(orderId) {
+				let that = this;
+				console.log('templateIdList',this.templateIdList);
+				uni.requestSubscribeMessage({  
+					tmplIds: that.templateIdList,  
+					success (res) {  
+						// that.gotoPay();
+						console.log("success:",res);  
+					},  
+					fail (res) {  
+						console.log("fail:",res);  
+					},  
+					complete (res) {  
+						that.gotoPay(orderId);
+						console.log("complete:",res);  
+					}  
+				})  
+			},
+			gotoPay(orderId){
+				this.$u.api.gotoPay({orderId:orderId,openid:''}).then(res=>{
+					this.payResult = res.data.payInfo;
+					this.payResult.package = res.data.payInfo.packageValue;
+					// #ifdef H5
+					this.initConfig(this.payResult)
+					// #endif 
+					// #ifdef MP
+					this.wxPay()
+					// #endif 
+					// if(this.params.paymentMode==1||this.params.paymentMode==4){
+					// 	this.wxPay()
+					// }else{
+					// 	uni.$u.route('/shopping/paysuccess');
+					// }
+					console.log('gotoPayres',res.data);
+				}).catch(err=>{
+					this.paypass = '';
+					this.checkPassShow = false;
+					console.log('gotoPay',err);
+				})				
+			},
+			wxPay(){
+				let that = this;
+				uni.requestPayment({
+					... this.payResult,
+				    "provider": "wxpay", 
+				    "orderInfo": {
+				        // "appid": "wx499********7c70e",  // 微信开放平台 - 应用 - AppId,注意和微信小程序、公众号 AppId 可能不一致
+				        // "noncestr": "c5sEwbaNPiXAF3iv", // 随机字符串
+				        // "package": "Sign=WXPay",        // 固定值
+				        // "partnerid": "148*****52",      // 微信支付商户号
+				        // "prepayid": "wx202254********************fbe90000", // 统一下单订单号 
+				        // "timestamp": 1597935292,        // 时间戳(单位:秒)
+				        // "sign": "A842B45937F6EFF60DEC7A2EAA52D5A0" // 签名,这里用的 MD5/RSA 签名
+				    },
+				    success(res) {
+						that.payQuery();
+					},
+				    fail(e) {
+						console.log('wxPayfail',e);
+					}
+				})				
+			},
+			payQuery(){
+				let that = this;
+				let retryCount = 0;
+				let maxRetryCount = 5; // 设置最大重试次数
+				let interval = 2000; // 设置间隔时间为2秒
+				let timeout = 10000; // 设置超时时间为10秒
+				let timer;
+				uni.showLoading({
+					title:'支付结果查询中'
+				})
+				timer = setInterval(() => {
+					retryCount++;
+					if (retryCount > maxRetryCount || retryCount * interval > timeout) {
+						clearInterval(timer);
+						  uni.hideLoading();
+						console.log("支付查询超时或达到最大重试次数");
+						// 在这里添加超时或达到最大重试次数的处理逻辑
+						this.reloadList()
+					} else {
+						console.log("第" + retryCount + "次查询");
+						// 调用查询支付状态的方法
+						// 如果支付状态为成功,则清除定时器并处理成功的逻辑
+						// 如果支付状态为失败,则清除定时器并处理失败的逻辑
+						this.$u.api.payQuery({orderId:this.orderId}).then(res=>{
+							// 0-未支付 1-已支付 2-支付中 3-支付失败 4-支付退款
+							let payStatus = res.data.payStatus;
+							if(payStatus===1){
+								uni.$u.route('/center/paysuccess');
+							}else if(payStatus===0||payStatus===2){
+								this.payQuery()
+							}else if(payStatus===3){
+								uni.toast('支付失败')
+							}
+							clearInterval(timer);
+						}).catch(err=>{
+							console.log('payQuery',err);
+						}).finally(()=>{
+							uni.hideLoading()
+						})
+					}
+				}, interval);
+			},
+			getPerformerNotice(){
+				this.$u.api.performerNotice({performId:this.orderDetails.performId}).then(res=>{
+					// console.log('getPerformerNotice',res.data);
+					this.formerNotice =  res.data.performNotice;
+				}).catch(err=>{
+					console.log('getPerformerNotice',err);
+				})
+			},
+			handleMoveQrcode(){
+				console.log('handleMoveQrcode');
+				return false
+			},
+			/**
+			 * 公众号微信支付
+			 */
+			initConfig() {
+				// #ifdef H5
+				let that = this
+				wxH5.config({
+					debug: false, // 这里一般在测试阶段先用ture,等打包给后台的时候就改回false, 
+					appId: that.payResult.appId, // 必填,公众号的唯一标识 
+					timestamp: that.payResult.timeStamp, // 必填,生成签名的时间戳     
+					nonceStr: that.payResult.nonceStr, // 必填,生成签名的随机串 
+					signature: that.payResult.paySign, // 必填,签名 
+					jsApiList: ['chooseWXPay', 'checkJsApi'] // 必填,需要使用的JS接口列表 
+				})
+				wxH5.ready(() => {
+					wxH5.chooseWXPay({
+						timestamp: that.payResult.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符     
+						nonceStr: that.payResult.nonceStr, // 支付签名随机串,不长于 32 位         
+						package: that.payResult.packageValue, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)     
+						signType: 'SHA1', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'     
+						paySign: that.payResult.paySign, // 支付签名     
+						success: () => {
+							that.payQuery();
+						},
+						fail: (e) => {
+							uni.$u.route('/center/order', {
+								status: 0
+							});
+							console.log('wxPayfail', e);
+						},
+						cancel: () => {
+							uni.$u.route('/center/order', {
+								status: 0
+							});
+							that.cansubmit = true;
+						}
+					})
+				})
+				// #endif
+			}
+
+		}
+	}
+</script>
+<style>
+	page{
+		background: linear-gradient(180deg, #ED0000 0%, #F9FBFD 50%,#F9FBFD 100%);
+		background-repeat: no-repeat;
+	}
+</style>
+<style lang="scss" scoped>
+.status-content{
+	text-align: center;
+	padding-top: 30rpx;
+	margin-bottom: 52rpx;
+	.text{
+		font-size: 32rpx;
+		font-weight: 400;
+		color: #FFFFFF;
+		margin-bottom: 52rpx;
+	}
+	.btn-wrap{
+		margin-bottom: 40rpx;
+		.btn{
+			height: 64rpx;
+			line-height: 64rpx;
+			padding: 0 42rpx;
+			border-radius: 8rpx;
+			border: 2rpx solid #FFFFFF;
+			background-color: #ffffff;
+			font-size: 24rpx;
+			font-weight: bold;
+			color: #EE0B0B;
+			&.cancel{
+				border-color: #FFE2E2;
+				color: #FFE2E2;
+				background-color: transparent;
+			}
+			&:not(:last-of-type){
+				margin-right: 100rpx;
+			}
+		}
+	}
+}
+.base-info{
+	background: #FFFFFF;
+	box-shadow: 0rpx 2rpx 12rpx 0rpx rgba(221,221,221,0.5);
+	border-radius: 20rpx;
+	overflow: hidden;
+	margin-bottom: 24rpx;
+	.top{
+		padding: 36rpx 24rpx;
+		background: #F9FAFD;
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #7F7F7F;
+	}
+	.info{
+		padding: 28rpx 20rpx 30rpx 24rpx;
+		.img{
+			width: 180rpx;
+			height: 160rpx;
+		}
+		.text{
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			padding-left: 26rpx;
+			flex: 1;
+			.name{
+				font-size: 28rpx;
+				font-weight: bold;
+				color: #363636;
+				margin-bottom: 20rpx;
+			}
+			.time{
+				margin-bottom: 20rpx;
+			}
+			.position{
+				margin-bottom: 20rpx;
+			}
+			.status{
+				font-size: 24rpx;
+				font-weight: 400;
+				color: #EE0606;
+			}
+		}
+	}
+}
+.box{
+	margin-bottom: 24rpx;
+	background: #FFFFFF;
+	box-shadow: 0rpx 0rpx 20rpx 2rpx rgba(221,221,221,0.5);
+	border-radius: 20rpx;
+	padding: 36rpx 24rpx;
+	.title{
+		font-size: 28rpx;
+		font-weight: bold;
+		color: #2D2D2D;
+		padding-bottom: 20rpx;
+		margin-bottom: 24rpx;
+		border-bottom: 1px solid #F4F4F4;
+		.btn{
+			height: 40rpx;
+			line-height: 40rpx;
+			padding: 0 20rpx;
+			border-radius: 8rpx;
+			border: 2rpx solid #606060;
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #606060;
+			cursor: pointer;
+		}
+	}
+}
+.order-info{
+	.order-info-item{
+		font-size: 30rpx;
+		font-weight: 400;
+		color: #333333;
+		padding: 10rpx 0;
+		// &:not(:last-child){
+		// 	border-bottom: 0.5px solid #eee;
+		// }
+		.til{
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #999999;
+			margin-right: 40rpx;
+		}
+		.copy-btn{
+			margin-left: 32rpx;
+			height: 30rpx;
+			line-height: 30rpx;
+			padding: 0 14rpx;
+			border-radius: 8rpx;
+			font-size: 20rpx;
+			font-weight: 400;
+			color: #999999;
+			background: #EDEDED;
+		}
+	}
+}
+.tips{
+	.tips-item{
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #7F7F7F;
+		line-height: 36rpx;
+		margin-bottom: 20rpx;
+	}
+}
+.qr-wrap{
+	.qr-content{
+		text-align: center;
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #7F7F7F;
+	}
+	.img-wrap{
+		position: relative;
+	}
+	.ayQrcode,.img{
+		width: 180px;
+		height: 180px;
+		display: block;
+		margin: 0 auto 20rpx;
+		
+	}
+	.refund-ico{
+		width: 200rpx;
+		height: 152rpx;
+		position: absolute;
+		bottom: -10rpx;
+		right: 60rpx;
+	}
+	
+}
+.order-num{
+		position: relative;
+		margin: 30rpx auto 0;
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #363636;
+		border: 1px solid #363636;
+		padding: 5rpx 20rpx;
+		border-radius: 8rpx;
+		width: fit-content;
+		&.del-line{
+			color: #BCBCBC;
+			border: 1px solid #BCBCBC;
+		}
+		&.del-line::after{
+			content: '';
+			width: 110%;
+			height: 1px;
+			background-color: #BCBCBC;
+			position: absolute;
+			top: 50%;
+			left: -5%;
+		}
+	}
+.viewers-list{
+	.item{
+		font-size: 24rpx;
+		color: #999;
+		margin-bottom: 10px;
+		.qrbtn{
+			color: #EE0606;
+		}
+	}
+}
+.singleQR-wrap{
+	padding: 0 24rpx 50rpx;
+	box-sizing: border-box;
+	min-width: 80vw;
+	.title{
+		text-align: center;
+		height: 94rpx;
+		line-height: 94rpx;
+		font-size: 28rpx;
+		font-weight: bold;
+		color: #2D2D2D;
+		border-bottom: 2rpx solid #F4F4F4;
+		margin-bottom: 24rpx;
+	}
+	.single-info{
+		margin-bottom: 24rpx;
+		.item{
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #363636;
+			margin-bottom: 10rpx;
+		}
+	}
+	.singleQrcode{
+		width: 160px;
+		height: 160px;
+		margin: 24rpx auto 54rpx;
+	}
+}
+</style>

+ 64 - 0
center/paysuccess.vue

@@ -0,0 +1,64 @@
+<template>
+	<view class="pages">
+		<view class="icon-wrap u-flex u-row-center">
+			<u-icon name="checkmark" color="#fff" size="80"></u-icon>
+		</view>
+		<view class="title">{{title}}</view>
+		<view class="full-btn" @click="btnClick">查看报名记录</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				fromPage:'',
+				title:'提交成功!请耐心等待工作人员审核,约1-2个工作日'
+			}
+		},
+		onShow() {		
+		},
+		onLoad(page) {
+			this.fromPage = page.fromPage;
+			if(this.fromPage=='nocash'){
+				this.title = '兑换成功'
+			}
+		},
+		methods: {
+			btnClick(){
+				uni.reLaunch({url: '/center/order'});
+			}
+		}
+	}
+</script>
+<style>
+	page{
+		background-color: #F5F5F5;
+	}
+</style>
+<style lang="scss" scoped>
+.pages{
+	text-align: center;
+}
+
+.icon-wrap{
+	width: 180rpx;
+	height: 180rpx;
+	margin: 120rpx auto 20rpx;
+	padding: 20rpx;
+	background-color: #F01414;
+	border-radius: 50%;
+}
+.title{
+	font-size: 32rpx;
+	font-weight: 600;
+	color: #333333;
+	line-height: 40rpx;
+	margin-bottom: 120rpx;
+	padding: 40rpx 100rpx;
+	line-height: 1.5;
+}
+.full-btn{
+	margin: 20rpx 10%;
+}
+</style>

+ 207 - 0
center/people.vue

@@ -0,0 +1,207 @@
+<template>
+	<view class="pages">
+		<view class="" :style="{height: navHeight+'px' }"></view>
+		<!-- 自定义导航 -->
+		<view class="navbar-box">
+			<u-navbar title="信息填写" :safeAreaInsetTop="true" @leftClick="leftClick"></u-navbar>
+		</view>
+		<view class="page-wrap">
+			<view class="form-wrap">
+				<u--form labelPosition="left" :model="form" :rules="rules" ref="uForm" labelWidth="120rpx" >
+					<u-form-item label="姓名" prop="name" borderBottom>
+						<u--input v-model="form.name" placeholder="与证件名字一致" border="none"></u--input>
+					</u-form-item>
+					<u-form-item label="身份证" prop="idcard" borderBottom>
+						<u--input v-model="form.idcard" placeholder="身份证号码" border="none"></u--input>
+					</u-form-item>
+					<u-form-item label="手机号" prop="mobile" borderBottom>
+						<u--input v-model="form.mobile" placeholder="常用手机号" border="none"></u--input>
+					</u-form-item>
+				</u--form>
+			<!-- 	<view class="set-default u-flex u-row-between">
+					<view class="left">
+						<view class="up">设置为默认信息</view>
+						<view class="down">提醒:每次下单会默认使用该信息</view>
+					</view>
+					<view class="right">
+						<u-switch v-model="form.default" activeValue='1' inactiveValue='0'></u-switch>
+					</view>
+				</view> -->
+			</view>
+			<view class="btn-wrap">
+				<view class="full-btn" @click="submit">保存</view>
+				<view class="full-btn red" v-if="type=='editVisitor'" @click="delVisitor">删除</view>
+				<view class="full-btn white" @click="cancel">取消</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	export default {
+		mixins:[systemInfo],
+		data() {
+			return {
+				type:'',
+				fromPage:'',
+				form: {
+					id:null,
+					name: '',
+					idcard:'',
+					mobile:'',
+					// default:'0',
+				},
+				rules: {
+					'name': [
+						{
+							type: 'string',
+							required: true,
+							message: '请填写姓名',
+							trigger: ['blur', 'change']
+						}
+					],
+					'idcard': [
+						{
+							type: 'string',
+							required: true,
+							message: '请输入身份证',
+							trigger: ['blur', 'change']
+						},
+						{
+							validator: (rule, value, callback) => {
+								// 上面有说,返回true表示校验通过,返回false表示不通过
+								// this.$u.test.mobile()就是返回true或者false的
+								return this.$u.test.idCard(value);
+							},
+							message: '请输入正确身份证',
+							// 触发器可以同时用blur和change
+							trigger: ['change', 'blur'],
+						}
+					],
+					mobile: [
+						{
+							type: 'string',
+							required: true,
+							message: '请输入手机号',
+							trigger: ['blur', 'change']
+						},
+						{
+							validator: (rule, value, callback) => {
+								// 上面有说,返回true表示校验通过,返回false表示不通过
+								// this.$u.test.mobile()就是返回true或者false的
+								return this.$u.test.mobile(value);
+							},
+							message: '请输入正确手机号',
+							// 触发器可以同时用blur和change
+							trigger: ['change', 'blur'],
+						}
+					],
+				},
+			}
+		},
+		onLoad(page) {
+			this.getSystemInfo();
+			this.type = page.type;
+			this.fromPage = page.fromPage;
+			this.form.id = page.id;
+			this.form.name = page.name;
+			this.form.idcard = page.idcard;
+			this.form.mobile = page.mobile;
+		},
+		onReady() {
+			this.$refs.uForm.setRules(this.rules)
+		},
+		methods: {
+			leftClick() {
+			   let pages = getCurrentPages();
+			   if(pages.length==1){
+			   	uni.$u.route('/pages/index/index')
+			   }else{
+			   	uni.navigateBack()
+			   };
+			},
+			submit() {
+				this.$refs.uForm.validate().then(res => {
+					uni.$u.toast('表单校验成功')
+					console.log('this.type',this.type);
+					console.log('form',this.form);
+					this.$u.api.insertOrUpdateMember(this.form).then(res=>{
+						uni.navigateBack()
+						// if(this.type=='addVisitor'){
+						// 	// this.addVisitor()
+						// }else if(this.type=='editVisitor'){
+						// 	// this.editVisitor()
+						// }
+					}).catch(err=>{
+						console.log('insertOrUpdateMember',err);
+					})
+				}).catch(errors => {
+					console.log('errors',errors);
+					uni.$u.toast('表单校验失败')
+				})
+			},
+			addVisitor(){
+				console.log('form',this.form);
+				this.$u.api.insertOrUpdateMember(this.form).then(res=>{
+					// console.log('getSettlement',res.data);
+					this.pageContent =  res.data;
+				}).catch(err=>{
+					console.log('insertOrUpdateMember',err);
+				})
+			},
+			editVisitor(){
+				console.log('form',this.form);
+			},
+			delVisitor(){
+				console.log('form',this.form);
+				uni.showModal({
+				    title: '提示',
+				    content: '确认删除吗!',
+				    success: res => {
+				        if (res.confirm) {
+				            this.$u.api.deleteMember({id:this.form.id}).then(res=>{
+				            	uni.navigateBack()
+				            }).catch(err=>{
+				            	console.log('deleteMember',err);
+				            })
+				        }
+				    }
+				});
+			},
+			cancel(){
+				this.leftClick();
+			}
+		}
+	}
+</script>
+<style>
+page{
+	background-color: #F4F5F7;
+}	
+</style>
+<style lang="scss" scoped>
+.form-wrap{
+	background: #FFFFFF;
+	box-shadow: 0rpx 4rpx 8rpx 0rpx rgba(226,226,226,0.5);
+	border-radius: 24rpx;
+	margin-bottom: 70rpx;
+	padding: 10rpx 40rpx 24rpx;
+}
+.set-default{
+	padding-top: 26rpx;
+	.up{
+		font-size: 28rpx;
+		font-weight: 500;
+		color: #606060;
+		line-height: 42rpx;
+		margin-bottom: 20rpx;
+	}
+	.down{
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #7F7F7F;
+		line-height: 36rpx;
+	}
+}
+</style>

+ 241 - 0
center/refund.vue

@@ -0,0 +1,241 @@
+<template>
+	<view class="pages">
+		<view class="" :style="{height: navHeight+'px' }"></view>
+		<view class="navbar-box">
+			<u-navbar title="申请退款" :safeAreaInsetTop="true" @leftClick="leftClick" :titleStyle="{color:'#fff'}" leftIconColor="#fff" bgColor="#EF1818"></u-navbar>
+		</view>
+		<view class="page-wrap">
+			<view class="box base-info">
+				<view class="title u-flex u-row-between">
+					<text>申请退款金额</text>
+					<text>¥ {{realPrice}}</text>
+				</view>
+				<view class="con">
+					(预计3个工作日内退回)
+				</view>
+			</view>
+			<view class="box reason">
+				<view class="title">退款原因(必填)</view>
+				<view class="select-reason u-flex u-row-between" @click="reasonshow=true">
+					<text>{{refundReason||'请选择原因'}}</text>
+					<u-icon name="arrow-down" color="#E5E5E5" size="36rpx"></u-icon>
+				</view>
+				<u-picker :show="reasonshow" :columns="reasonList" @confirm="confirmReason" @cancel="reasonshow=false"></u-picker>
+			</view>
+		</view>
+		<view class="btn-wrap">
+			<view class="inner">
+				<view class="btn" @click="submit">确认退款</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	export default {
+		mixins: [systemInfo], // 使用mixin
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				orderId:'',
+				realPrice:'',
+				refundReason:'',
+				reasonshow:false,
+				reasonList:[
+					[
+						'选错场次',
+						'计划有变',
+						'其他'
+					]
+				],
+				params:{
+					
+				},
+				templateIdList:[],//微信小程序订阅消息
+				
+			}
+		},
+		onShow() {
+		},
+		onLoad(page) {
+			console.log('page',page);
+			this.orderId = page.id;
+			this.realPrice = page.realPrice;
+			this.getSystemInfo();
+			this.getTemplateIdList();//获取模板列表
+
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					console.log('1111');
+					uni.$u.route('/pages/index/index')
+				}else{
+					console.log('2222222');
+					uni.navigateBack()
+				};
+			},
+			getTemplateIdList(){
+				this.$u.api.templateIdList({templateLabel:'order_refund'}).then(res=>{
+					console.log('res',res.data);
+					this.templateIdList = res.data.list.map(item=>{
+						return item.templateId
+					});
+					// if(this.templateIdList.length>0){
+					// 	this.templateEven();
+					// }
+				}).catch(err=>{
+					console.log('getTemplateIdList',err);
+				})
+			},
+			confirmReason(e){
+				console.log('confirmReason',e.value[0]);
+				this.refundReason = e.value[0];
+				this.reasonshow = false;
+			},
+			submit(){
+				if(!this.refundReason){
+					uni.$u.toast('请选择原因')
+					return
+				}
+				// this.templateEven();
+				// #ifdef MP
+				this.setTemplate();
+				// #endif
+				// #ifdef H5
+				this.handleSubmit();
+				// #endif
+				
+			},
+			handleSubmit(){
+				let params = {
+					orderId:this.orderId,
+					refundReason:this.refundReason
+				}
+				this.$u.api.refundSubmit(params).then(res=>{
+					uni.$u.toast(res.msg)
+					uni.navigateBack()
+					console.log('res',res.data);
+				}).catch(err=>{
+					console.log('refundSubmit',err);
+				})
+			},
+			// 订阅消息
+			templateEven(){
+				let that = this
+				wx.showModal({
+					  title: '温馨提示',
+					  content: '为更好的促进与您的交流,服务号需要实时向您发送消息',
+					  confirmText:"同意",
+					  cancelText:"拒绝",
+					  success: function (res) {
+						  if (res.confirm) {
+							 //调用订阅消息
+							  console.log('用户点击确定');
+							  //调用订阅
+							  that.setTemplate();
+						  } else if (res.cancel) {
+							  console.log('用户点击取消');
+							  ///显示第二个弹说明一下
+							  wx.showModal({
+								title: '温馨提示',
+								content: '拒绝后您将无法获取实时的消息',
+								confirmText:"知道了",
+								showCancel:false,
+								success: function (res) {
+									that.handleSubmit();
+								  ///点击知道了的后续操作 
+								  ///如跳转首页面 
+								}
+							});
+						  }
+					  }
+				  });
+			},
+			// 设置小程序订阅消息
+			setTemplate() {
+				let that = this;
+				// console.log('templateIdList',this.templateIdList);
+				wx.requestSubscribeMessage({  
+					tmplIds: this.templateIdList,  
+					success (res) {  
+						// that.handleSubmit();
+						console.log("success:",res);  
+					},  
+					fail (res) {  
+						console.log("fail:",res);  
+					},  
+					complete (res) {  
+						that.handleSubmit();
+						console.log("complete:",res);  
+					}  
+				})  
+			},
+
+		}
+	}
+</script>
+<style>
+	page{background-color: #F7F7F9;}
+</style>
+<style lang="scss" scoped>
+.page-wrap{
+	padding: 32rpx 16rpx;
+}
+.box{
+	margin-bottom: 24rpx;
+	background: #FFFFFF;
+	box-shadow: 0rpx 0rpx 20rpx 2rpx rgba(221,221,221,0.5);
+	border-radius: 20rpx;
+	padding: 36rpx 24rpx;
+	.title{
+		font-size: 28rpx;
+		font-weight: 500;
+		color: #2D2D2D;
+		margin-bottom: 38rpx;
+	}
+}
+.base-info{
+	.con{
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #7F7F7F;
+		text-align: right;
+	}
+}
+.reason{
+	.select-reason{
+		height: 64rpx;
+		line-height: 64rpx;
+		border-radius: 8rpx;
+		border: 2rpx solid #E5E5E5;
+		padding: 0 24rpx;
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #363636;
+	}
+}
+.btn-wrap{
+	height: 92rpx;
+	.inner{
+		position: fixed;
+		left: 0;
+		right: 0;
+		bottom: 66rpx;
+	}
+	.btn{
+		height: 92rpx;
+		line-height: 92rpx;
+		width:80%;
+		margin: 0 auto;
+		background: linear-gradient(90deg, #FF7878 0%, #ED0000 100%);
+		border-radius: 46rpx;
+		font-size: 28rpx;
+		font-weight: 400;
+		color: #FFFFFF;
+		text-align: center;
+	}
+}
+</style>

+ 117 - 0
center/viewRefund.vue

@@ -0,0 +1,117 @@
+<template>
+	<view class="">
+		<u-navbar
+			title="退款/售后"
+			:placeholder="true"
+			:autoBack="true"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+		<view class="page-wrap">
+			<view class="status">{{pageData.status|filterRefundState}}</view>
+			<view class="box">
+				<view class="order-product page-wrap">
+					<view class="product u-flex" v-for="(item,index) in pageData.goodsList" :key="item.id">
+						<u--image :showLoading="true" :src="item.mainImg" width="180rpx" height="180rpx"></u--image>
+						<view class="text">
+							<view class="up">
+								<view class="name ellipsis-2">{{item.goodsName}}</view>
+								<view class="info">
+									{{item.specification}}
+									{{item.unit}}
+								</view>
+							</view>
+							<view class="down u-flex u-row-between">
+								<view class="left">
+									<text class="price">¥ <text class="price-num">{{item.price}}</text></text>
+								</view>
+								<u-number-box v-model="item.quantity" :disabled="true" @change="changeQuantity(index, $event)" integer></u-number-box>
+							</view>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="box order-info page-wrap">
+				<view class="order-info-item" v-for="(item,index) in orderInfo" :key="index">
+					<text class="til">{{item.til}}</text>
+					<text class="con">
+						<text v-if="item.isMoney">¥</text>
+						{{pageData[item.con]}}
+					</text>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				orderId:'',
+				pageData:{},
+				orderInfo:[
+					{til:'订单编号',con:'orderNum'},
+					{til:'申请金额',con:'orderAmount',isMoney:true},
+					// {til:'申请件数',con:'quantity'},
+					{til:'申请原因',con:'refundReason'},
+					{til:'问题描述',con:'remark'},
+				]
+			}
+		},
+		onShow() {
+			this.getData()
+		},
+		onLoad(page) {
+			this.orderId =page.orderId
+		},
+		methods: {
+			getData(){
+				this.$u.api.refundInfo({orderId:this.orderId}).then(res=>{
+					this.pageData = res.data
+					console.log('res',res.data);
+				}).catch(err=>{
+					console.log('refundInfo',err);
+				})
+			}
+		}
+	}
+</script>
+<style>
+	page{
+		background-color: #F5F5F5;
+	}
+</style>
+<style lang="scss" scoped>
+.status{
+	text-align: center;
+	font-size: 32rpx;
+	font-weight: 600;
+	color: #FFB100;
+	line-height: 45rpx;
+	margin-bottom: 20rpx;
+}
+.box{
+	background-color: #fff;
+	border-radius: 8rpx;
+	margin-bottom: 20rpx;
+}
+.order-info{
+	.order-info-item{
+		font-size: 30rpx;
+		font-weight: 400;
+		color: #333333;
+		line-height: 42rpx;
+		padding: 30rpx 20rpx;
+		&:not(:last-child){
+			border-bottom: 0.5px solid #eee;
+		}
+		.til{
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #999999;
+			margin-right: 90rpx;
+		}
+	}
+}
+</style>

+ 215 - 0
center/viewrecord.vue

@@ -0,0 +1,215 @@
+<template>
+	<view class="pages">
+		<u-navbar
+			title="观看记录"
+			:placeholder="true"
+			:autoBack="false"
+			 @leftClick="leftClick"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+
+		<view class="page-wrap">
+			<mescroll-body class="" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption">
+				<!-- :down="downOption" :up="upOption" -->
+				<view class="list">
+					<view v-for="(item,index) in dataList" class="item u-flex" :class="{ show: show }" @click="itemClick(item)" :key="item.id">
+						<view class="text">
+							<view class="title u-line-1">{{ item.title }}</view>
+							<view class="con u-line-2">{{ item.centent }}</view>
+							<view class="time">{{ $u.timeFormat(item.onlineTime, 'yyyy-mm-dd') }}</view>
+						</view>
+						<image class="img" :src="item.mainImg||staticUrl+'/img/newsdetails-banner.png'" mode=""></image>
+					</view>
+				</view>
+			</mescroll-body>
+		</view>
+	</view>
+</template>
+
+<script>
+	// 引入mescroll-mixins.js
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		components: {
+			
+		},
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				// // 下拉刷新的配置(可选, 绝大部分情况无需配置)
+				// downOption: {
+				// },
+				// // 上拉加载的配置(可选, 绝大部分情况无需配置)
+				upOption: {
+					page: {
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 5, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					empty: {
+						tip: '暂无相关资讯,请重新搜索'
+					}
+				},
+				params:{
+					keyword:''
+				},
+				dataList:[],
+				observer: null,
+				show:false,
+				
+			}
+		},
+		onShow() {
+		},
+		onLoad() {
+			this.observer = uni.createIntersectionObserver(this,{observeAll: true});
+		},
+		onReady() {
+			this.observer.relativeToViewport().observe('.item', res => {
+			if (res.intersectionRatio > 0) {
+				console.log('resres',res);
+			  // 当元素出现在视口时执行动画
+			  // this.showAnimation(res);
+			  this.show = true;
+			}
+		  })
+		},
+		onUnload() {
+			if (this.observer) {
+				this.observer.disconnect()
+			}
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			/*下拉刷新的回调, 重置列表为第一页 (此处可删,mixins已默认)
+			downCallback(){
+				this.mescroll.resetUpScroll();
+			},
+			/*上拉加载的回调*/
+			upCallback(page) {
+				// 此处可以继续请求其他接口
+				// if(page.num == 1){
+				// 	// 请求其他接口...
+				// }
+			
+				// 如果希望先请求其他接口,再触发upCallback,可参考以下写法
+				// if(!this.hasTypeId){
+				// 	// this.getid();
+				// 	this.mescroll.endErr();//没有接口暂时不调用
+				// 	return // 此处return,先获取xx
+				// }
+			
+				let pageNum = page.num; // 页码, 默认从1开始
+				let pageSize = page.size; // 页长, 默认每页10条
+			
+				let params = {
+					pageNum : page.num,
+					pageSize :  page.size,
+					title:this.params.keyword
+				}
+				// console.log('this.params',params);
+				this.$u.api.newsList(params).then(data => {
+					this.hasfetch = true;
+					// console.log('data',JSON.parse(JSON.stringify(data)));
+					// 接口返回的当前页数据列表 (数组)
+					let curPageData = data.data.rows;
+					// console.log('curPageData',JSON.parse(JSON.stringify(curPageData)));
+					// 接口返回的当前页数据长度 (如列表有26个数据,当前页返回8个,则curPageLen=8)
+					let curPageLen = curPageData.length; 
+					// 接口返回的总页数 (如列表有26个数据,每页10条,共3页; 则totalPage=3)
+					// let totalPage =  data.data.data.totalPage; 
+					// 接口返回的总数据量(如列表有26个数据,每页10条,共3页; 则totalSize=26)
+					let totalSize = data.data.total; 
+					// 接口返回的是否有下一页 (true/false)
+					// let hasNext = data.xxx; 
+					// console.log('totalPage',totalPage,'curPageLen',curPageLen);
+					//设置列表数据
+					if(page.num == 1) this.dataList = []; //如果是第一页需手动置空列表
+					this.dataList = this.dataList.concat(curPageData); //追加新数据
+					// 请求成功,隐藏加载状态
+					//方法一(推荐): 后台接口有返回列表的总页数 totalPage
+					// this.mescroll.endByPage(curPageLen, totalPage); 
+					//方法二(推荐): 后台接口有返回列表的总数据量 totalSize
+					this.mescroll.endBySize(curPageLen, totalSize); 
+				}).catch(err => {
+					this.mescroll.endErr()
+					console.log(err)
+				});	
+			
+			},
+			/*若希望重新加载列表,只需调用此方法即可(内部会自动page.num=1,再主动触发up.callback)*/
+			reloadList() {
+				this.mescroll.resetUpScroll();
+			},
+			search(e){
+				this.reloadList();
+			},
+			itemClick(item){
+				// console.log('itemClick',item);
+				uni.$u.route('/pages/newsdetails', {
+					id: item.id,
+					type:'news'
+				});
+			}
+		}
+	}
+</script>
+<style>
+	page{
+		background-color: #F9FAFD;
+	}
+</style>
+<style lang="scss" scoped>
+.search-wrap{
+	padding: 20rpx 34rpx;
+}
+.list{
+	.item{
+		&.show{
+			animation: slide-top 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
+		}
+		background-color: #fff;
+		border-radius: 24rpx;
+		margin-bottom: 24rpx;
+		overflow: hidden;
+		.img{
+			border-radius: 12rpx;
+			width: 220rpx;
+			height: 200rpx;
+		}
+		.text{
+			height: 200rpx;
+			box-sizing: border-box;
+			padding:24rpx 28rpx;
+			flex: 1;
+			.title{
+				font-size: 28rpx;
+				font-weight: bold;
+				color: #333333;
+				margin-bottom: 16rpx;
+			}
+			.con{
+				font-size: 24rpx;
+				font-weight: 400;
+				color: #6B6B6B;
+				line-height: 36rpx;
+				margin-bottom: 20rpx;
+			}
+			.time{
+				font-size: 20rpx;
+				font-weight: 400;
+				color: #C0C0C0;
+				line-height: 22rpx;
+			}
+		}
+	}
+}
+</style>

+ 197 - 0
common/apiurl.js

@@ -0,0 +1,197 @@
+/*
+	接口统一管理
+*/
+const apiurl = {
+	// 小程序获取openid
+	wxinfo: {
+		url: '/member/wechat/appletGetOpenId',
+		type: 'get'
+	},
+	// 授权获取微信手机号
+	getMobile: {
+		url: '/member/wechat/appletGetMobileV1',
+		type: 'get'
+	},
+	// 小程序openid登录
+	login: {
+		url: '/member/auth/appletLogin',
+		type: 'post'
+	},
+	// 重新登录
+	reLogin: {
+		url: '/auth/login',
+		type: 'post'
+	},
+	// 个人中心首页
+	personalIndex: {
+		url: '/system/client/personalIndex',
+		type: 'get'
+	},
+	// 查询登录用户信息
+	memberInfo: {
+		url: '/member/memberInfo/getMemberInfo',
+		type: 'get'
+	},
+	// 修改用户信息
+	updateMemberInfo: {
+		url: '/member/memberInfo/update',
+		type: 'post'
+	},
+	// 实名认证
+	factorAuth: {
+		url: '/member/memberInfo/realAuth',
+		type: 'post'
+	},
+	// 获取协议
+	getAgreement: {
+		url: '/system/common/getAgreement',
+		type: 'get'
+	},
+	// 首页信息
+	clientIndex: {
+		url: '/system/client/index',
+		type: 'get'
+	},
+	// 分页查询资讯
+	newsList: {
+		url: '/system/sysInformation/pageList',
+		type: 'get'
+	},
+	// ID获取资讯详情
+	newsdetails: {
+		url: '/system/sysInformation/selectById',
+		type: 'get'
+	},
+	// 添加浏览记录
+	addReadRecord: {
+		url: '/system/browseLog/insertOrUpdate',
+		type: 'post',
+	},
+	// 海报二维码
+	performQrcode: {
+		url: '/goods/performInfo/posterByPerformId',
+		type: 'get'
+	},
+	// 获取演艺厅列表
+	performSell: {
+		url: '/goods/performInfo/performSell',
+		type: 'get'
+	},
+	// 获取节目演出时段
+	getAuditoriumTimes: {
+		url: '/merchant/merchantTheatreAuditorium/selectTimes',
+		type: 'get'
+	},
+	// 获取节目演出时段
+	getAuditoriumDate: {
+		url: '/merchant/merchantTheatreAuditorium/selectDate',
+		type: 'get'
+	},
+	// 节目详情
+	performInfo: {
+		url: '/goods/performInfo/selectById',
+		type: 'get'
+	},
+	// 节目演员列表
+	actorsList: {
+		url: '/goods/performInfo/performerList',
+		type: 'get'
+	},
+	// 节目演员列表
+	performerNotice: {
+		url: '/goods/performInfo/performerNotice',
+		type: 'get'
+	},
+	// 选择区域
+	selectRegion: {
+		url: '/merchant/merchantTheatreAuditorium/selectRegion',
+		type: 'post'
+	},
+	// 订单结算信息
+	getSettlement: {
+		url: '/order/orderInfo/getSettlement',
+		type: 'post'
+	},
+	// 观影人历史列表
+	selectMemberAll: {
+		url: '/member/memberInfoFamily/selectMemberAll',
+		type: 'get'
+	},
+	// 添加修改观影人
+	insertOrUpdateMember: {
+		url: '/member/memberInfoFamily/insertOrUpdate',
+		type: 'post'
+	},
+	// 删除观影人
+	deleteMember: {
+		url: '/member/memberInfoFamily/deleteById',
+		type: 'delete'
+	},
+	// 订单提交
+	submitOrder: {
+		url: '/order/orderInfo/submit',
+		type: 'post'
+	},
+	// 订单支付
+	gotoPay: {
+		url: '/order/orderInfo/gotoPay',
+		type: 'post'
+	},
+	// 支付查询
+	payQuery: {
+		url: '/order/orderInfo/payQuery',
+		type: 'post'
+	},
+	// 订单列表
+	orderList: {
+		url: '/order/orderInfo/pageList',
+		type: 'get'
+	},
+	// 订单详情
+	orderDetails: {
+		url: '/order/orderInfo/selectById',
+		type: 'get'
+	},
+	// 提交退款
+	refundSubmit: {
+		url: '/order/orderInfo/refundSubmit',
+		type: 'post'
+	},
+	// 订单关闭
+	cancelOrder: {
+		url: '/order/orderInfo/cancel',
+		type: 'post'
+	},
+	// 获取模板列表
+	templateIdList: {
+		url: '/system/wxTemplateInfo/templateList',
+		type: 'get'
+	},
+	
+	
+	/**
+	 * @author ygh
+	 * @date 2023-12-14
+	 * 公众号获取openid
+	 * 
+	 * */
+	wxinfoH5: {
+		url: '/member/wechat/h5/code/',
+		type: 'get',
+		addUrl: true
+	},
+}
+
+
+/*
+* 特殊处理接口
+*/ 
+const otherApiUrl = {
+	// 文件上传
+	uploadFile: '/file/upload/single/minio',
+}
+
+export {
+	apiurl,
+	otherApiUrl
+}

+ 61 - 0
common/config.js

@@ -0,0 +1,61 @@
+/**
+ * 配置通用 
+ */
+// const node_dev = process.env.H_NODE_ENV;
+// //运行到浏览器用的
+// let baseUrl='/api';
+// let upFileUrl='/api';
+// //打包用的
+// if (node_dev) {
+//  baseUrl = process.env.H_BASE_URL;
+//  upFileUrl = process.env.H_UP_FILE_URL
+// }
+
+/**
+ * 后端环境配置
+ */
+let baseUrl = null // 后端服务接口
+let upFileUrl= null // 后端上传接口
+let staticUrl= null // 静态文件地址
+//64
+// #ifdef MP
+	baseUrl='https://greatadmin.dev.gztjy.top/serviceapi';
+	upFileUrl='http://fileupload.hw.hongweisoft.com/upload/single/minio';
+	// staticUrl='http://res.hw.hongweisoft.com/xushuo/';
+	staticUrl='https://miniores.hw.hongweisoft.com/greattransition/staticfile';
+// #endif
+// #ifdef H5
+	baseUrl='/api/serviceapi';
+	upFileUrl='http://fileupload.hw.hongweisoft.com/upload/single/minio';
+	// staticUrl='http://res.hw.hongweisoft.com/xushuo/';
+	staticUrl='https://miniores.hw.hongweisoft.com/greattransition/staticfile';
+// #endif
+
+//正式
+// #ifdef MP
+	// baseUrl='https://xusapi.gzxsjt.cn/appapi/app';
+	// upFileUrl='https://xusapi.gzxsjt.cn/thirdapi/upload/single/minio';
+	// staticUrl='https://xusapi.gzxsjt.cn/miniores/imgs/app';
+// #endif
+// #ifdef H5
+	// baseUrl='https://xusapi.gzxsjt.cn/appapi/app';
+	// upFileUrl='https://xusapi.gzxsjt.cn/thirdapi/upload/single/minio';
+	// staticUrl='https://xusapi.gzxsjt.cn/miniores/imgs/app';
+// #endif
+
+
+
+const commonConfig = {
+	wxAppid: '', // 测试wxAppid
+	baseUrl: baseUrl, // 服务器地址
+	uploadFileUrl: upFileUrl, // 上传文件路径
+	staticUrl:staticUrl,
+	paginationConfig:{
+		  pageNum: 1,
+		  pageSize: 10
+	},//分页参数
+	successCode:200,//接口返回状态
+}
+export {
+	commonConfig
+}

+ 37 - 0
common/http.api.js

@@ -0,0 +1,37 @@
+import {
+	apiurl
+} from "./apiurl.js"
+
+import queryParams from "../utils/queryParams.js"
+
+// 此处第二个参数vm,就是我们在页面使用的this,你可以通过vm获取vuex等操作,更多内容详见uView对拦截器的介绍部分:
+const install = (Vue, vm) => {
+
+	let httpMap = {}
+	const http = uni.$u.http
+	
+	// 循环请求路径对象生成对应的方式请求
+	Object.keys(apiurl).forEach((key) => {
+		if(apiurl[key]?.type=='get'){
+			if(apiurl[key].addUrl){
+				httpMap[key] = (data = {}, config = {}) => http[apiurl[key]?.type](apiurl[key]?.url+data.code, {}, config);
+			}else {
+				httpMap[key] = (data = {}, config = {}) => http[apiurl[key]?.type](apiurl[key]?.url, {params:data}, config);
+			}
+		} else if (apiurl[key]?.type == 'delete') {
+			httpMap[key] = (data = {}, config = {}) => http[apiurl[key]?.type](apiurl[key]?.url + `${queryParams(data)}`, {}, config);
+		} else{
+			httpMap[key] = (params = {}, config = {}) => http[apiurl[key]?.type](apiurl[key]?.url, params, config);
+		}
+	    
+	});
+	
+	// 将各个定义的接口名称,统一放进对象挂载到vm.$u.api(因为vm就是this,也即this.$u.api)下
+	vm.$u.api = {
+		...httpMap
+	};
+}
+
+export default {
+	install
+}

+ 157 - 0
common/request.js

@@ -0,0 +1,157 @@
+import { commonConfig } from '@/common/config.js';
+import {againToken}  from '../utils/againToken.js'
+import { showFullScreenLoading , tryHideFullScreenLoading } from '../utils/loading.js'
+let showModal = false;
+// 此vm参数为页面的实例,可以通过它引用vuex中的变量
+module.exports = (vm) => {
+    // 初始化请求配置
+    uni.$u.http.setConfig((config) => {
+        /* config 为默认全局配置*/
+        config.baseURL = commonConfig.baseUrl; /* 根域名 */
+        return config
+    })
+	
+	// 请求拦截
+	uni.$u.http.interceptors.request.use((config) => { // 可使用async await 做异步操作
+	// console.log('config========',config);
+	    // 初始化请求拦截器时,会执行此方法,此时data为undefined,赋予默认{}
+	    config.data = config.data || {}
+		// 根据custom参数中配置的是否需要token,添加对应的请求头
+		if(!config?.custom?.auth) {
+			// 可以在此通过vm引用vuex中的变量,具体值在vm.$store.state中
+			// config.header.token = vm.vuex_user_info.token;
+			config.header.Authorization = `Bearer ${vm.vuex_user_info.accessToken}`;
+		}
+		if(!config?.custom?.noload){
+			showFullScreenLoading()
+		}
+	    return config 
+	}, config => { // 可使用async await 做异步操作
+	    return Promise.reject(config)
+	})
+	
+	let unlogin = function(){
+		if(vm.vuex_user_info.userid&&vm.vuex_wechatOpenid) {
+			againToken(vm.$u,vm.vuex_wechatOpenid,vm.vuex_user_info.userid)
+		} else {
+			let pages = getCurrentPages();
+			// console.log('pages',pages);
+			let backUrl = pages[pages.length - 1].route;
+			let options =uni.$u.queryParams( pages[pages.length - 1].options);
+			let fullBackUrl = backUrl+options;
+			
+			// const backArr = ['productdetails'];//需要登录返回的页面
+			// const hasBackArr = backArr.some(backPage => backUrl.includes(backPage));
+			// if(hasBackArr){
+			// 	console.log('包含');
+			// 	uni.setStorage({
+			// 		key: 'backUrl',
+			// 		data: fullBackUrl,
+			// 		success: function () {
+			// 			// console.log('setStorage success');
+			// 		}
+			// 	});
+			// }else{
+			// 	console.log('不包含');
+			// 	uni.removeStorage({
+			// 		key: 'backUrl',
+			// 		success: function (res) {
+			// 			// console.log('success');
+			// 		}
+			// 	});
+			// }
+			uni.setStorage({
+				key: 'backUrl',
+				data: fullBackUrl,
+				success: function () {
+					// console.log('setStorage success');
+				}
+			});
+			tryHideFullScreenLoading()
+			if(showModal){return}
+			showModal = true;
+			uni.showModal({
+			  title: '提示',
+			  content: '你需要登录后,才可使用此功能!',
+			  success: res => {
+			    if (res.confirm) {
+			      uni.$u.route('/pages/login/login');
+			    }else{
+					uni.removeStorage({
+						key: 'backUrl',
+						success: function (res) {
+							// console.log('success');
+						}
+					});
+					let pages = getCurrentPages();
+					// console.log('pages',pages);
+					if(pages.length>1){
+						uni.navigateBack()
+					}
+				}
+			  },
+			  complete() {
+			  	showModal = false
+				uni.$u.vuex('vuex_member_info', {});
+			  }
+			})
+
+		}
+	}
+	
+	// 响应拦截
+	uni.$u.http.interceptors.response.use((response) => {/* 对响应成功做点什么 可使用async await 做异步操作*/
+		// console.log('response====response',response);
+		const data = response.data
+
+		// 自定义参数
+		const custom = response.config?.custom
+		if (data.code !== 200) {
+			console.log('data====',data);
+			// 如果没有显式定义custom的toast参数为false的话,默认对报错进行toast弹出提示
+			if (custom.toast !== false) {
+				const unshowmsg = ['令牌不能为空'];
+				if (!unshowmsg.includes(data.msg)) {
+				  uni.$u.toast(data.msg)
+				}
+				// uni.$u.toast(data.msg)
+			}
+			if(data.msg == "令牌验证失败" || data.msg == "令牌不能为空" || data.code == 401){
+				unlogin()
+			}
+			
+			if(data.msg == "用户不存在!"||data.msg == "用户未注册"){
+				uni.clearStorage();
+				unlogin()
+			}
+			
+			return Promise.reject(data)
+			// 如果需要catch返回,则进行reject
+			// if (custom?.catch) {
+			// 	return Promise.reject(data)
+			// } else {
+			// 	// 否则返回一个pending中的promise,请求不会进入catch中
+			// 	return new Promise(() => { })
+			// }
+		}
+		// console.log('data--',data);
+		tryHideFullScreenLoading()
+		return data === undefined ? {} : data
+	}, (response) => {
+		tryHideFullScreenLoading()
+		// console.log('response==',response);
+		const data = response.data;
+		// console.log('data==',data);
+		// 对响应错误做点什么 (statusCode !== 200)
+		let errMap = {
+			'404':'接口不存在'
+		};
+		if (response.statusCode in errMap) {
+		  uni.$u.toast(errMap[response.statusCode])
+		}
+		if(data.msg == "令牌不能为空" || data.code == 401){
+			unlogin()
+		}
+		return Promise.reject(response)
+	})
+}

+ 412 - 0
components/ay-qrcode/ay-qrcode.vue

@@ -0,0 +1,412 @@
+<template>
+	<view :class="modal?'show-qrcode':'hide-qrcode'">
+		<view class="box-qrcode" :style="{'margin-left':  marginLeft + 'px'}" @longtap="longtapCode">
+			<!-- style="width: 550rpx;height: 550rpx;" -->
+			
+			<canvas class="canvas-qrcode" :style="style_w_h" :canvas-id="qrcode_id">
+				
+				<!-- #ifndef MP -->
+				<view v-if="modal&&is_themeImg" :style="style_w_h" class="box-img-qrcode">
+					<image :style="style_w_h_img" mode="scaleToFill" :src="themeImg"></image>
+				</view>
+				<!-- #endif -->
+				
+			</canvas>
+			
+			<!-- <image mode="scaleToFill" :src="imagePath"></image> -->
+			
+		</view>
+	</view>
+</template>
+
+<script>
+	var qr_we = require("./qrcode_wx.js");
+	const qrCode = require('./weapp-qrcode.js')
+	export default {
+		data() {
+			return {
+				isAndroid : false ,
+				show: true,
+				imagePath: '',
+				// qrcode_id: 'qrcode_id',
+				marginLeft: 0,
+				//一般的安卓app只需加30就能显示全
+				//苹果app的不加就能显示全,加了就要弄margin-left
+				//有些安卓app显示不全
+				add_num : 30 ,
+				add_num_key : 'rectify_code_key',
+			}
+		},
+		props: {
+			modal: {
+				type: Boolean,
+				default: false
+			},
+			url: {
+				type: String,
+				default: ''
+			},
+			height: {
+				type: Number,
+				default: 260
+			},
+			width: {
+				type: Number,
+				default: 260
+			},
+			themeColor: {
+				type: String,
+				default: '#333333',
+			},
+			qrcode_id: {
+				type: String,
+				default: 'qrcode_id',
+			},
+			is_themeImg: {
+				type: Boolean,
+				default: false,
+			},
+			themeImg: {
+				type: String,
+				default: 'https://cdn.pixabay.com/photo/2016/11/29/13/24/balloons-1869816__340.jpg',
+			},
+			h_w_img: {
+				type: Number,
+				default: 30
+			},
+			
+			
+		},
+		watch:{
+			
+		},
+		computed: {
+			style_w_h() {
+				return this.set_style_w_h();
+			},
+			style_w_h_img() {
+				let that = this;
+				var height = parseInt(that.h_w_img);
+				var width = parseInt(that.h_w_img);
+				var style = '';
+				if (height > 0) {
+					style = `height:${height*2}rpx;`;
+				}
+				if (width > 0) {
+					style += `width:${width*2}rpx;z-index: 2;`;
+				}
+			
+				return style;
+			},
+		},
+		created: function() {
+			let that = this;
+			try {
+				//app苹果二维码不居中
+				//#ifndef MP
+				let isAndroid = false ;
+			    const res = uni.getSystemInfoSync();
+			    if(res.platform == 'android'){
+					isAndroid = true ;
+				}else{
+					isAndroid = false ;
+				}
+				
+				
+				if (!isAndroid) {
+					// that.marginLeft = 46;
+					that.marginLeft = 0;
+				}
+				
+				that.isAndroid = isAndroid ;
+				try {
+					const add_num = uni.getStorageSync(that.add_num_key);
+					if (add_num) {
+						that.add_num = add_num;
+					}
+					
+				} catch (e) {
+					// error
+				
+				}
+				// #endif
+
+			} catch (e) {
+			    // error
+			}
+
+			//#ifdef MP
+			//that.marginLeft = 40;
+			// #endif
+
+		},
+		methods: {
+			set_style_w_h(){
+				
+				let that = this;
+				var height = parseInt(that.height);
+				var width = parseInt(that.width);
+				var style = '';
+				var height = height*2 ;
+				var width = width*2 ;
+				
+				//#ifndef MP
+				var add = that.add_num ;
+				
+				height +=  add;
+				width +=  add;
+				// #endif
+				
+				if (height > 0) {
+					style = `height:${height}rpx;`;
+				}
+				if (width > 0) {
+					style += `width:${width}rpx;`;
+				}
+				
+				return style;
+			},
+			hideQrcode() {
+				this.$emit("hideQrcode")
+			},
+			// 二维码生成工具
+			crtQrCode() {
+				let that = this;
+				//#ifndef MP
+				new qrCode(that.qrcode_id, {
+					text: this.url,
+					width: that.width,
+					height: that.height,
+					colorDark: that.themeColor,//#333333
+					colorLight: "#FFFFFF",
+					correctLevel: qrCode.CorrectLevel.H,
+				})
+				// #endif
+				//#ifdef MP
+				that.createQrCode(this.url, that.qrcode_id, that.width, that.height,that.themeColor,that.is_themeImg,that.themeImg,that.h_w_img);
+				// #endif
+
+				//that.createQrCode(this.url, that.qrcode_id, that.width, that.height);
+			},
+			//#ifdef MP
+
+			createQrCode: function(url, canvasId, cavW, cavH,cavColor,haveImg,imgurl,imgsize) {
+				//调用插件中的draw方法,绘制二维码图片
+				qr_we.api.draw(url, canvasId, cavW, cavH,cavColor,haveImg,imgurl,imgsize, this, this.canvasToTempImage);
+				// setTimeout(() => { this.canvasToTempImage();},100);
+
+			},
+			
+			// #endif
+			//获取临时缓存照片路径,存入data中
+			canvasToTempImage: function() {
+				var that = this;
+			},
+			saveImage: function() {
+				var that = this;
+				uni.canvasToTempFilePath({
+					canvasId: that.qrcode_id,
+					success: function(res) {
+						var tempFilePath = res.tempFilePath;
+						console.log(tempFilePath);
+						that.imagePath = tempFilePath;
+						
+						//保存到相册
+						// uni.saveFile({
+						//       tempFilePath: tempFilePath,
+						//       success: function (res2) {
+						//         var savedFilePath = res2.savedFilePath;
+								
+								
+						//       }
+						// });
+						uni.saveImageToPhotosAlbum({
+							filePath : tempFilePath ,
+							success: function (res3) {
+								uni.showModal({
+									title: '提示',
+									content: '保存成功',
+									confirmText: '确定',
+									showCancel: false,
+									confirmColor: '#33CCCC',
+									success(res4) {
+										
+									}
+								}) 
+							},
+						});
+					},
+					fail: function(res) {
+						console.log(res);
+					}
+				}, that);
+			},
+			//微信小程序支持:长按二维码,提示是否保存相册
+			//安卓APP长按校正二维码
+			longtapCode(){
+				var that = this;
+				
+				//#ifndef MP
+				uni.showModal({
+					title: '校正二维码',
+					content: '二维码是否异常',
+					confirmText: '确定',
+					confirmColor: '#33CCCC',
+					success(res) {
+						if (res.confirm) {
+							that.rectify_code();
+						}
+					}
+				})
+				// #endif
+				
+				//#ifdef MP-WEIXIN
+				uni.showModal({
+					title: '提示',
+					content: '是否保存到相册',
+					confirmText: '确定',
+					confirmColor: '#33CCCC',
+					success(res) {
+						if (res.confirm) {
+							that.saveImage();
+						}
+					}
+				})
+				// #endif
+			},
+			//安卓有些手机不正常,长按可选择矫正
+			rectify_code(){
+				var that = this;
+				let add_num = that.add_num ;
+				add_num += 30 ;
+				that.add_num = add_num;
+				that.crtQrCode();//重新生成才会立即覆盖
+				try {
+					//第一次长按校正设置了就不用在设置
+					uni.setStorage({
+						key: that.add_num_key,
+						data: add_num,
+						success: function() {
+							
+						}
+					});
+				} catch (e) {
+					// error
+				
+				}
+			},
+		},
+		mounted() {}
+	}
+</script>
+
+<style scoped lang="scss">
+	// .qrcode-box {
+	// 	position: fixed;
+	// 	left: 0;
+	// 	top: 0;
+	// 	right: 0;
+	// 	bottom: 0;
+	// 	height: 100vh;
+	// 	width: 100vw;
+	// 	background-color: rgba(59, 59, 59, 0.6);
+	// 	// opacity: 0.8;
+	// 	text-align: center;
+	// 	display: flex;
+	// 	align-items: center;
+	// 	display: none;
+
+	// 	.qrcode-item {
+	// 		flex: 1;
+	// 		position: relative;
+	// 		text-align: center;
+
+	// 		.item-box {
+	// 			width: 90%;
+	// 			margin: auto;
+	// 			display: inline-block;
+	// 			margin-top: 30%;
+	// 			padding-bottom: 30rpx;
+
+	// 			// animation: show 0.7s;
+	// 			.title {
+	// 				font-size: 46rpx;
+	// 				text-align: center;
+	// 				margin-bottom: 24rpx;
+	// 			}
+
+	// 			.canvas {
+	// 				margin: auto;
+	// 				display: inline-block;
+	// 				margin: auto;
+	// 			}
+
+	// 			background-color: #FFFFFF;
+	// 		}
+
+	// 	}
+	// }
+	.box-qrcode{
+		text-align: center;
+		position: relative;
+		.box-img-qrcode{
+			position: absolute;
+			display: flex;
+			flex-direction: column;
+			justify-content: center;
+			align-items: center;
+			z-index: 2;
+		}
+	}
+	image{
+		width: 60upx;
+		height: 60upx;
+		border-radius: 50%;
+		
+	}
+	.canvas-qrcode {
+		
+		margin: auto;
+		display: inline-block;
+		float: left;
+	}
+	
+	
+	.opacity-qrcode {
+		opacity: 0;
+		display: block;
+	}
+
+	.show-qrcode {
+		display: block;
+		animation: fade 0.7s;
+
+		// -moz-animation: fade 0.5s; /* Firefox */
+		// -webkit-animation: fade 0.5s; /* Safari 和 Chrome */
+		// -o-animation: fade 0.5s;
+	}
+
+	.hide-qrcode {
+		animation: hide 0.7s;
+	}
+
+	@keyframes fade {
+		from {
+			opacity: 0.8;
+		}
+
+		to {
+			opacity: 1;
+		}
+	}
+
+	@keyframes hide {
+		from {
+			opacity: 1;
+		}
+
+		to {
+			opacity: 0;
+		}
+	}
+</style>

+ 872 - 0
components/ay-qrcode/qrcode_wx.js

@@ -0,0 +1,872 @@
+!(function() {
+
+	// alignment pattern
+	var adelta = [
+		0, 11, 15, 19, 23, 27, 31,
+		16, 18, 20, 22, 24, 26, 28, 20, 22, 24, 24, 26, 28, 28, 22, 24, 24,
+		26, 26, 28, 28, 24, 24, 26, 26, 26, 28, 28, 24, 26, 26, 26, 28, 28
+	];
+
+	// version block
+	var vpat = [
+		0xc94, 0x5bc, 0xa99, 0x4d3, 0xbf6, 0x762, 0x847, 0x60d,
+		0x928, 0xb78, 0x45d, 0xa17, 0x532, 0x9a6, 0x683, 0x8c9,
+		0x7ec, 0xec4, 0x1e1, 0xfab, 0x08e, 0xc1a, 0x33f, 0xd75,
+		0x250, 0x9d5, 0x6f0, 0x8ba, 0x79f, 0xb0b, 0x42e, 0xa64,
+		0x541, 0xc69
+	];
+
+	// final format bits with mask: level << 3 | mask
+	var fmtword = [
+		0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, //L
+		0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, //M
+		0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed, //Q
+		0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b //H
+	];
+
+	// 4 per version: number of blocks 1,2; data width; ecc width
+	var eccblocks = [
+		1, 0, 19, 7, 1, 0, 16, 10, 1, 0, 13, 13, 1, 0, 9, 17,
+		1, 0, 34, 10, 1, 0, 28, 16, 1, 0, 22, 22, 1, 0, 16, 28,
+		1, 0, 55, 15, 1, 0, 44, 26, 2, 0, 17, 18, 2, 0, 13, 22,
+		1, 0, 80, 20, 2, 0, 32, 18, 2, 0, 24, 26, 4, 0, 9, 16,
+		1, 0, 108, 26, 2, 0, 43, 24, 2, 2, 15, 18, 2, 2, 11, 22,
+		2, 0, 68, 18, 4, 0, 27, 16, 4, 0, 19, 24, 4, 0, 15, 28,
+		2, 0, 78, 20, 4, 0, 31, 18, 2, 4, 14, 18, 4, 1, 13, 26,
+		2, 0, 97, 24, 2, 2, 38, 22, 4, 2, 18, 22, 4, 2, 14, 26,
+		2, 0, 116, 30, 3, 2, 36, 22, 4, 4, 16, 20, 4, 4, 12, 24,
+		2, 2, 68, 18, 4, 1, 43, 26, 6, 2, 19, 24, 6, 2, 15, 28,
+		4, 0, 81, 20, 1, 4, 50, 30, 4, 4, 22, 28, 3, 8, 12, 24,
+		2, 2, 92, 24, 6, 2, 36, 22, 4, 6, 20, 26, 7, 4, 14, 28,
+		4, 0, 107, 26, 8, 1, 37, 22, 8, 4, 20, 24, 12, 4, 11, 22,
+		3, 1, 115, 30, 4, 5, 40, 24, 11, 5, 16, 20, 11, 5, 12, 24,
+		5, 1, 87, 22, 5, 5, 41, 24, 5, 7, 24, 30, 11, 7, 12, 24,
+		5, 1, 98, 24, 7, 3, 45, 28, 15, 2, 19, 24, 3, 13, 15, 30,
+		1, 5, 107, 28, 10, 1, 46, 28, 1, 15, 22, 28, 2, 17, 14, 28,
+		5, 1, 120, 30, 9, 4, 43, 26, 17, 1, 22, 28, 2, 19, 14, 28,
+		3, 4, 113, 28, 3, 11, 44, 26, 17, 4, 21, 26, 9, 16, 13, 26,
+		3, 5, 107, 28, 3, 13, 41, 26, 15, 5, 24, 30, 15, 10, 15, 28,
+		4, 4, 116, 28, 17, 0, 42, 26, 17, 6, 22, 28, 19, 6, 16, 30,
+		2, 7, 111, 28, 17, 0, 46, 28, 7, 16, 24, 30, 34, 0, 13, 24,
+		4, 5, 121, 30, 4, 14, 47, 28, 11, 14, 24, 30, 16, 14, 15, 30,
+		6, 4, 117, 30, 6, 14, 45, 28, 11, 16, 24, 30, 30, 2, 16, 30,
+		8, 4, 106, 26, 8, 13, 47, 28, 7, 22, 24, 30, 22, 13, 15, 30,
+		10, 2, 114, 28, 19, 4, 46, 28, 28, 6, 22, 28, 33, 4, 16, 30,
+		8, 4, 122, 30, 22, 3, 45, 28, 8, 26, 23, 30, 12, 28, 15, 30,
+		3, 10, 117, 30, 3, 23, 45, 28, 4, 31, 24, 30, 11, 31, 15, 30,
+		7, 7, 116, 30, 21, 7, 45, 28, 1, 37, 23, 30, 19, 26, 15, 30,
+		5, 10, 115, 30, 19, 10, 47, 28, 15, 25, 24, 30, 23, 25, 15, 30,
+		13, 3, 115, 30, 2, 29, 46, 28, 42, 1, 24, 30, 23, 28, 15, 30,
+		17, 0, 115, 30, 10, 23, 46, 28, 10, 35, 24, 30, 19, 35, 15, 30,
+		17, 1, 115, 30, 14, 21, 46, 28, 29, 19, 24, 30, 11, 46, 15, 30,
+		13, 6, 115, 30, 14, 23, 46, 28, 44, 7, 24, 30, 59, 1, 16, 30,
+		12, 7, 121, 30, 12, 26, 47, 28, 39, 14, 24, 30, 22, 41, 15, 30,
+		6, 14, 121, 30, 6, 34, 47, 28, 46, 10, 24, 30, 2, 64, 15, 30,
+		17, 4, 122, 30, 29, 14, 46, 28, 49, 10, 24, 30, 24, 46, 15, 30,
+		4, 18, 122, 30, 13, 32, 46, 28, 48, 14, 24, 30, 42, 32, 15, 30,
+		20, 4, 117, 30, 40, 7, 47, 28, 43, 22, 24, 30, 10, 67, 15, 30,
+		19, 6, 118, 30, 18, 31, 47, 28, 34, 34, 24, 30, 20, 61, 15, 30
+	];
+
+	// Galois field log table
+	var glog = [
+		0xff, 0x00, 0x01, 0x19, 0x02, 0x32, 0x1a, 0xc6, 0x03, 0xdf, 0x33, 0xee, 0x1b, 0x68, 0xc7, 0x4b,
+		0x04, 0x64, 0xe0, 0x0e, 0x34, 0x8d, 0xef, 0x81, 0x1c, 0xc1, 0x69, 0xf8, 0xc8, 0x08, 0x4c, 0x71,
+		0x05, 0x8a, 0x65, 0x2f, 0xe1, 0x24, 0x0f, 0x21, 0x35, 0x93, 0x8e, 0xda, 0xf0, 0x12, 0x82, 0x45,
+		0x1d, 0xb5, 0xc2, 0x7d, 0x6a, 0x27, 0xf9, 0xb9, 0xc9, 0x9a, 0x09, 0x78, 0x4d, 0xe4, 0x72, 0xa6,
+		0x06, 0xbf, 0x8b, 0x62, 0x66, 0xdd, 0x30, 0xfd, 0xe2, 0x98, 0x25, 0xb3, 0x10, 0x91, 0x22, 0x88,
+		0x36, 0xd0, 0x94, 0xce, 0x8f, 0x96, 0xdb, 0xbd, 0xf1, 0xd2, 0x13, 0x5c, 0x83, 0x38, 0x46, 0x40,
+		0x1e, 0x42, 0xb6, 0xa3, 0xc3, 0x48, 0x7e, 0x6e, 0x6b, 0x3a, 0x28, 0x54, 0xfa, 0x85, 0xba, 0x3d,
+		0xca, 0x5e, 0x9b, 0x9f, 0x0a, 0x15, 0x79, 0x2b, 0x4e, 0xd4, 0xe5, 0xac, 0x73, 0xf3, 0xa7, 0x57,
+		0x07, 0x70, 0xc0, 0xf7, 0x8c, 0x80, 0x63, 0x0d, 0x67, 0x4a, 0xde, 0xed, 0x31, 0xc5, 0xfe, 0x18,
+		0xe3, 0xa5, 0x99, 0x77, 0x26, 0xb8, 0xb4, 0x7c, 0x11, 0x44, 0x92, 0xd9, 0x23, 0x20, 0x89, 0x2e,
+		0x37, 0x3f, 0xd1, 0x5b, 0x95, 0xbc, 0xcf, 0xcd, 0x90, 0x87, 0x97, 0xb2, 0xdc, 0xfc, 0xbe, 0x61,
+		0xf2, 0x56, 0xd3, 0xab, 0x14, 0x2a, 0x5d, 0x9e, 0x84, 0x3c, 0x39, 0x53, 0x47, 0x6d, 0x41, 0xa2,
+		0x1f, 0x2d, 0x43, 0xd8, 0xb7, 0x7b, 0xa4, 0x76, 0xc4, 0x17, 0x49, 0xec, 0x7f, 0x0c, 0x6f, 0xf6,
+		0x6c, 0xa1, 0x3b, 0x52, 0x29, 0x9d, 0x55, 0xaa, 0xfb, 0x60, 0x86, 0xb1, 0xbb, 0xcc, 0x3e, 0x5a,
+		0xcb, 0x59, 0x5f, 0xb0, 0x9c, 0xa9, 0xa0, 0x51, 0x0b, 0xf5, 0x16, 0xeb, 0x7a, 0x75, 0x2c, 0xd7,
+		0x4f, 0xae, 0xd5, 0xe9, 0xe6, 0xe7, 0xad, 0xe8, 0x74, 0xd6, 0xf4, 0xea, 0xa8, 0x50, 0x58, 0xaf
+	];
+
+	// Galios field exponent table
+	var gexp = [
+		0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1d, 0x3a, 0x74, 0xe8, 0xcd, 0x87, 0x13, 0x26,
+		0x4c, 0x98, 0x2d, 0x5a, 0xb4, 0x75, 0xea, 0xc9, 0x8f, 0x03, 0x06, 0x0c, 0x18, 0x30, 0x60, 0xc0,
+		0x9d, 0x27, 0x4e, 0x9c, 0x25, 0x4a, 0x94, 0x35, 0x6a, 0xd4, 0xb5, 0x77, 0xee, 0xc1, 0x9f, 0x23,
+		0x46, 0x8c, 0x05, 0x0a, 0x14, 0x28, 0x50, 0xa0, 0x5d, 0xba, 0x69, 0xd2, 0xb9, 0x6f, 0xde, 0xa1,
+		0x5f, 0xbe, 0x61, 0xc2, 0x99, 0x2f, 0x5e, 0xbc, 0x65, 0xca, 0x89, 0x0f, 0x1e, 0x3c, 0x78, 0xf0,
+		0xfd, 0xe7, 0xd3, 0xbb, 0x6b, 0xd6, 0xb1, 0x7f, 0xfe, 0xe1, 0xdf, 0xa3, 0x5b, 0xb6, 0x71, 0xe2,
+		0xd9, 0xaf, 0x43, 0x86, 0x11, 0x22, 0x44, 0x88, 0x0d, 0x1a, 0x34, 0x68, 0xd0, 0xbd, 0x67, 0xce,
+		0x81, 0x1f, 0x3e, 0x7c, 0xf8, 0xed, 0xc7, 0x93, 0x3b, 0x76, 0xec, 0xc5, 0x97, 0x33, 0x66, 0xcc,
+		0x85, 0x17, 0x2e, 0x5c, 0xb8, 0x6d, 0xda, 0xa9, 0x4f, 0x9e, 0x21, 0x42, 0x84, 0x15, 0x2a, 0x54,
+		0xa8, 0x4d, 0x9a, 0x29, 0x52, 0xa4, 0x55, 0xaa, 0x49, 0x92, 0x39, 0x72, 0xe4, 0xd5, 0xb7, 0x73,
+		0xe6, 0xd1, 0xbf, 0x63, 0xc6, 0x91, 0x3f, 0x7e, 0xfc, 0xe5, 0xd7, 0xb3, 0x7b, 0xf6, 0xf1, 0xff,
+		0xe3, 0xdb, 0xab, 0x4b, 0x96, 0x31, 0x62, 0xc4, 0x95, 0x37, 0x6e, 0xdc, 0xa5, 0x57, 0xae, 0x41,
+		0x82, 0x19, 0x32, 0x64, 0xc8, 0x8d, 0x07, 0x0e, 0x1c, 0x38, 0x70, 0xe0, 0xdd, 0xa7, 0x53, 0xa6,
+		0x51, 0xa2, 0x59, 0xb2, 0x79, 0xf2, 0xf9, 0xef, 0xc3, 0x9b, 0x2b, 0x56, 0xac, 0x45, 0x8a, 0x09,
+		0x12, 0x24, 0x48, 0x90, 0x3d, 0x7a, 0xf4, 0xf5, 0xf7, 0xf3, 0xfb, 0xeb, 0xcb, 0x8b, 0x0b, 0x16,
+		0x2c, 0x58, 0xb0, 0x7d, 0xfa, 0xe9, 0xcf, 0x83, 0x1b, 0x36, 0x6c, 0xd8, 0xad, 0x47, 0x8e, 0x00
+	];
+
+	// Working buffers:
+	// data input and ecc append, image working buffer, fixed part of image, run lengths for badness
+	var strinbuf = [],
+		eccbuf = [],
+		qrframe = [],
+		framask = [],
+		rlens = [];
+	// Control values - width is based on version, last 4 are from table.
+	var version, width, neccblk1, neccblk2, datablkw, eccblkwid;
+	var ecclevel = 2;
+	// set bit to indicate cell in qrframe is immutable.  symmetric around diagonal
+	function setmask(x, y) {
+		var bt;
+		if (x > y) {
+			bt = x;
+			x = y;
+			y = bt;
+		}
+		// y*y = 1+3+5...
+		bt = y;
+		bt *= y;
+		bt += y;
+		bt >>= 1;
+		bt += x;
+		framask[bt] = 1;
+	}
+
+	// enter alignment pattern - black to qrframe, white to mask (later black frame merged to mask)
+	function putalign(x, y) {
+		var j;
+
+		qrframe[x + width * y] = 1;
+		for (j = -2; j < 2; j++) {
+			qrframe[(x + j) + width * (y - 2)] = 1;
+			qrframe[(x - 2) + width * (y + j + 1)] = 1;
+			qrframe[(x + 2) + width * (y + j)] = 1;
+			qrframe[(x + j + 1) + width * (y + 2)] = 1;
+		}
+		for (j = 0; j < 2; j++) {
+			setmask(x - 1, y + j);
+			setmask(x + 1, y - j);
+			setmask(x - j, y - 1);
+			setmask(x + j, y + 1);
+		}
+	}
+
+	//========================================================================
+	// Reed Solomon error correction
+	// exponentiation mod N
+	function modnn(x) {
+		while (x >= 255) {
+			x -= 255;
+			x = (x >> 8) + (x & 255);
+		}
+		return x;
+	}
+
+	var genpoly = [];
+
+	// Calculate and append ECC data to data block.  Block is in strinbuf, indexes to buffers given.
+	function appendrs(data, dlen, ecbuf, eclen) {
+		var i, j, fb;
+
+		for (i = 0; i < eclen; i++)
+			strinbuf[ecbuf + i] = 0;
+		for (i = 0; i < dlen; i++) {
+			fb = glog[strinbuf[data + i] ^ strinbuf[ecbuf]];
+			if (fb != 255) /* fb term is non-zero */
+				for (j = 1; j < eclen; j++)
+					strinbuf[ecbuf + j - 1] = strinbuf[ecbuf + j] ^ gexp[modnn(fb + genpoly[eclen - j])];
+			else
+				for (j = ecbuf; j < ecbuf + eclen; j++)
+					strinbuf[j] = strinbuf[j + 1];
+			strinbuf[ecbuf + eclen - 1] = fb == 255 ? 0 : gexp[modnn(fb + genpoly[0])];
+		}
+	}
+
+	//========================================================================
+	// Frame data insert following the path rules
+
+	// check mask - since symmetrical use half.
+	function ismasked(x, y) {
+		var bt;
+		if (x > y) {
+			bt = x;
+			x = y;
+			y = bt;
+		}
+		bt = y;
+		bt += y * y;
+		bt >>= 1;
+		bt += x;
+		return framask[bt];
+	}
+
+	//========================================================================
+	//  Apply the selected mask out of the 8.
+	function applymask(m) {
+		var x, y, r3x, r3y;
+
+		switch (m) {
+			case 0:
+				for (y = 0; y < width; y++)
+					for (x = 0; x < width; x++)
+						if (!((x + y) & 1) && !ismasked(x, y))
+							qrframe[x + y * width] ^= 1;
+				break;
+			case 1:
+				for (y = 0; y < width; y++)
+					for (x = 0; x < width; x++)
+						if (!(y & 1) && !ismasked(x, y))
+							qrframe[x + y * width] ^= 1;
+				break;
+			case 2:
+				for (y = 0; y < width; y++)
+					for (r3x = 0, x = 0; x < width; x++, r3x++) {
+						if (r3x == 3)
+							r3x = 0;
+						if (!r3x && !ismasked(x, y))
+							qrframe[x + y * width] ^= 1;
+					}
+				break;
+			case 3:
+				for (r3y = 0, y = 0; y < width; y++, r3y++) {
+					if (r3y == 3)
+						r3y = 0;
+					for (r3x = r3y, x = 0; x < width; x++, r3x++) {
+						if (r3x == 3)
+							r3x = 0;
+						if (!r3x && !ismasked(x, y))
+							qrframe[x + y * width] ^= 1;
+					}
+				}
+				break;
+			case 4:
+				for (y = 0; y < width; y++)
+					for (r3x = 0, r3y = ((y >> 1) & 1), x = 0; x < width; x++, r3x++) {
+						if (r3x == 3) {
+							r3x = 0;
+							r3y = !r3y;
+						}
+						if (!r3y && !ismasked(x, y))
+							qrframe[x + y * width] ^= 1;
+					}
+				break;
+			case 5:
+				for (r3y = 0, y = 0; y < width; y++, r3y++) {
+					if (r3y == 3)
+						r3y = 0;
+					for (r3x = 0, x = 0; x < width; x++, r3x++) {
+						if (r3x == 3)
+							r3x = 0;
+						if (!((x & y & 1) + !(!r3x | !r3y)) && !ismasked(x, y))
+							qrframe[x + y * width] ^= 1;
+					}
+				}
+				break;
+			case 6:
+				for (r3y = 0, y = 0; y < width; y++, r3y++) {
+					if (r3y == 3)
+						r3y = 0;
+					for (r3x = 0, x = 0; x < width; x++, r3x++) {
+						if (r3x == 3)
+							r3x = 0;
+						if (!(((x & y & 1) + (r3x && (r3x == r3y))) & 1) && !ismasked(x, y))
+							qrframe[x + y * width] ^= 1;
+					}
+				}
+				break;
+			case 7:
+				for (r3y = 0, y = 0; y < width; y++, r3y++) {
+					if (r3y == 3)
+						r3y = 0;
+					for (r3x = 0, x = 0; x < width; x++, r3x++) {
+						if (r3x == 3)
+							r3x = 0;
+						if (!(((r3x && (r3x == r3y)) + ((x + y) & 1)) & 1) && !ismasked(x, y))
+							qrframe[x + y * width] ^= 1;
+					}
+				}
+				break;
+		}
+		return;
+	}
+
+	// Badness coefficients.
+	var N1 = 3,
+		N2 = 3,
+		N3 = 40,
+		N4 = 10;
+
+	// Using the table of the length of each run, calculate the amount of bad image 
+	// - long runs or those that look like finders; called twice, once each for X and Y
+	function badruns(length) {
+		var i;
+		var runsbad = 0;
+		for (i = 0; i <= length; i++)
+			if (rlens[i] >= 5)
+				runsbad += N1 + rlens[i] - 5;
+		// BwBBBwB as in finder
+		for (i = 3; i < length - 1; i += 2)
+			if (rlens[i - 2] == rlens[i + 2] &&
+				rlens[i + 2] == rlens[i - 1] &&
+				rlens[i - 1] == rlens[i + 1] &&
+				rlens[i - 1] * 3 == rlens[i]
+				// white around the black pattern? Not part of spec
+				&&
+				(rlens[i - 3] == 0 // beginning
+					||
+					i + 3 > length // end
+					||
+					rlens[i - 3] * 3 >= rlens[i] * 4 || rlens[i + 3] * 3 >= rlens[i] * 4)
+			)
+				runsbad += N3;
+		return runsbad;
+	}
+
+	// Calculate how bad the masked image is - blocks, imbalance, runs, or finders.
+	function badcheck() {
+		var x, y, h, b, b1;
+		var thisbad = 0;
+		var bw = 0;
+
+		// blocks of same color.
+		for (y = 0; y < width - 1; y++)
+			for (x = 0; x < width - 1; x++)
+				if ((qrframe[x + width * y] && qrframe[(x + 1) + width * y] &&
+						qrframe[x + width * (y + 1)] && qrframe[(x + 1) + width * (y + 1)]) // all black
+					||
+					!(qrframe[x + width * y] || qrframe[(x + 1) + width * y] ||
+						qrframe[x + width * (y + 1)] || qrframe[(x + 1) + width * (y + 1)])) // all white
+					thisbad += N2;
+
+		// X runs
+		for (y = 0; y < width; y++) {
+			rlens[0] = 0;
+			for (h = b = x = 0; x < width; x++) {
+				if ((b1 = qrframe[x + width * y]) == b)
+					rlens[h]++;
+				else
+					rlens[++h] = 1;
+				b = b1;
+				bw += b ? 1 : -1;
+			}
+			thisbad += badruns(h);
+		}
+
+		// black/white imbalance
+		if (bw < 0)
+			bw = -bw;
+
+		var big = bw;
+		var count = 0;
+		big += big << 2;
+		big <<= 1;
+		while (big > width * width)
+			big -= width * width, count++;
+		thisbad += count * N4;
+
+		// Y runs
+		for (x = 0; x < width; x++) {
+			rlens[0] = 0;
+			for (h = b = y = 0; y < width; y++) {
+				if ((b1 = qrframe[x + width * y]) == b)
+					rlens[h]++;
+				else
+					rlens[++h] = 1;
+				b = b1;
+			}
+			thisbad += badruns(h);
+		}
+		return thisbad;
+	}
+
+	function genframe(instring) {
+		var x, y, k, t, v, i, j, m;
+
+		// find the smallest version that fits the string
+		t = instring.length;
+		version = 0;
+		do {
+			version++;
+			k = (ecclevel - 1) * 4 + (version - 1) * 16;
+			neccblk1 = eccblocks[k++];
+			neccblk2 = eccblocks[k++];
+			datablkw = eccblocks[k++];
+			eccblkwid = eccblocks[k];
+			k = datablkw * (neccblk1 + neccblk2) + neccblk2 - 3 + (version <= 9);
+			if (t <= k)
+				break;
+		} while (version < 40);
+
+		// FIXME - insure that it fits insted of being truncated
+		width = 17 + 4 * version;
+
+		// allocate, clear and setup data structures
+		v = datablkw + (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2;
+		for (t = 0; t < v; t++)
+			eccbuf[t] = 0;
+		strinbuf = instring.slice(0);
+
+		for (t = 0; t < width * width; t++)
+			qrframe[t] = 0;
+
+		for (t = 0; t < (width * (width + 1) + 1) / 2; t++)
+			framask[t] = 0;
+
+		// insert finders - black to frame, white to mask
+		for (t = 0; t < 3; t++) {
+			k = 0;
+			y = 0;
+			if (t == 1)
+				k = (width - 7);
+			if (t == 2)
+				y = (width - 7);
+			qrframe[(y + 3) + width * (k + 3)] = 1;
+			for (x = 0; x < 6; x++) {
+				qrframe[(y + x) + width * k] = 1;
+				qrframe[y + width * (k + x + 1)] = 1;
+				qrframe[(y + 6) + width * (k + x)] = 1;
+				qrframe[(y + x + 1) + width * (k + 6)] = 1;
+			}
+			for (x = 1; x < 5; x++) {
+				setmask(y + x, k + 1);
+				setmask(y + 1, k + x + 1);
+				setmask(y + 5, k + x);
+				setmask(y + x + 1, k + 5);
+			}
+			for (x = 2; x < 4; x++) {
+				qrframe[(y + x) + width * (k + 2)] = 1;
+				qrframe[(y + 2) + width * (k + x + 1)] = 1;
+				qrframe[(y + 4) + width * (k + x)] = 1;
+				qrframe[(y + x + 1) + width * (k + 4)] = 1;
+			}
+		}
+
+		// alignment blocks
+		if (version > 1) {
+			t = adelta[version];
+			y = width - 7;
+			for (;;) {
+				x = width - 7;
+				while (x > t - 3) {
+					putalign(x, y);
+					if (x < t)
+						break;
+					x -= t;
+				}
+				if (y <= t + 9)
+					break;
+				y -= t;
+				putalign(6, y);
+				putalign(y, 6);
+			}
+		}
+
+		// single black
+		qrframe[8 + width * (width - 8)] = 1;
+
+		// timing gap - mask only
+		for (y = 0; y < 7; y++) {
+			setmask(7, y);
+			setmask(width - 8, y);
+			setmask(7, y + width - 7);
+		}
+		for (x = 0; x < 8; x++) {
+			setmask(x, 7);
+			setmask(x + width - 8, 7);
+			setmask(x, width - 8);
+		}
+
+		// reserve mask-format area
+		for (x = 0; x < 9; x++)
+			setmask(x, 8);
+		for (x = 0; x < 8; x++) {
+			setmask(x + width - 8, 8);
+			setmask(8, x);
+		}
+		for (y = 0; y < 7; y++)
+			setmask(8, y + width - 7);
+
+		// timing row/col
+		for (x = 0; x < width - 14; x++)
+			if (x & 1) {
+				setmask(8 + x, 6);
+				setmask(6, 8 + x);
+			}
+		else {
+			qrframe[(8 + x) + width * 6] = 1;
+			qrframe[6 + width * (8 + x)] = 1;
+		}
+
+		// version block
+		if (version > 6) {
+			t = vpat[version - 7];
+			k = 17;
+			for (x = 0; x < 6; x++)
+				for (y = 0; y < 3; y++, k--)
+					if (1 & (k > 11 ? version >> (k - 12) : t >> k)) {
+						qrframe[(5 - x) + width * (2 - y + width - 11)] = 1;
+						qrframe[(2 - y + width - 11) + width * (5 - x)] = 1;
+					}
+			else {
+				setmask(5 - x, 2 - y + width - 11);
+				setmask(2 - y + width - 11, 5 - x);
+			}
+		}
+
+		// sync mask bits - only set above for white spaces, so add in black bits
+		for (y = 0; y < width; y++)
+			for (x = 0; x <= y; x++)
+				if (qrframe[x + width * y])
+					setmask(x, y);
+
+		// convert string to bitstream
+		// 8 bit data to QR-coded 8 bit data (numeric or alphanum, or kanji not supported)
+		v = strinbuf.length;
+
+		// string to array
+		for (i = 0; i < v; i++)
+			eccbuf[i] = strinbuf.charCodeAt(i);
+		strinbuf = eccbuf.slice(0);
+
+		// calculate max string length
+		x = datablkw * (neccblk1 + neccblk2) + neccblk2;
+		if (v >= x - 2) {
+			v = x - 2;
+			if (version > 9)
+				v--;
+		}
+
+		// shift and repack to insert length prefix
+		i = v;
+		if (version > 9) {
+			strinbuf[i + 2] = 0;
+			strinbuf[i + 3] = 0;
+			while (i--) {
+				t = strinbuf[i];
+				strinbuf[i + 3] |= 255 & (t << 4);
+				strinbuf[i + 2] = t >> 4;
+			}
+			strinbuf[2] |= 255 & (v << 4);
+			strinbuf[1] = v >> 4;
+			strinbuf[0] = 0x40 | (v >> 12);
+		} else {
+			strinbuf[i + 1] = 0;
+			strinbuf[i + 2] = 0;
+			while (i--) {
+				t = strinbuf[i];
+				strinbuf[i + 2] |= 255 & (t << 4);
+				strinbuf[i + 1] = t >> 4;
+			}
+			strinbuf[1] |= 255 & (v << 4);
+			strinbuf[0] = 0x40 | (v >> 4);
+		}
+		// fill to end with pad pattern
+		i = v + 3 - (version < 10);
+		while (i < x) {
+			strinbuf[i++] = 0xec;
+			// buffer has room    if (i == x)      break;
+			strinbuf[i++] = 0x11;
+		}
+
+		// calculate and append ECC
+
+		// calculate generator polynomial
+		genpoly[0] = 1;
+		for (i = 0; i < eccblkwid; i++) {
+			genpoly[i + 1] = 1;
+			for (j = i; j > 0; j--)
+				genpoly[j] = genpoly[j] ?
+				genpoly[j - 1] ^ gexp[modnn(glog[genpoly[j]] + i)] : genpoly[j - 1];
+			genpoly[0] = gexp[modnn(glog[genpoly[0]] + i)];
+		}
+		for (i = 0; i <= eccblkwid; i++)
+			genpoly[i] = glog[genpoly[i]]; // use logs for genpoly[] to save calc step
+
+		// append ecc to data buffer
+		k = x;
+		y = 0;
+		for (i = 0; i < neccblk1; i++) {
+			appendrs(y, datablkw, k, eccblkwid);
+			y += datablkw;
+			k += eccblkwid;
+		}
+		for (i = 0; i < neccblk2; i++) {
+			appendrs(y, datablkw + 1, k, eccblkwid);
+			y += datablkw + 1;
+			k += eccblkwid;
+		}
+		// interleave blocks
+		y = 0;
+		for (i = 0; i < datablkw; i++) {
+			for (j = 0; j < neccblk1; j++)
+				eccbuf[y++] = strinbuf[i + j * datablkw];
+			for (j = 0; j < neccblk2; j++)
+				eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))];
+		}
+		for (j = 0; j < neccblk2; j++)
+			eccbuf[y++] = strinbuf[(neccblk1 * datablkw) + i + (j * (datablkw + 1))];
+		for (i = 0; i < eccblkwid; i++)
+			for (j = 0; j < neccblk1 + neccblk2; j++)
+				eccbuf[y++] = strinbuf[x + i + j * eccblkwid];
+		strinbuf = eccbuf;
+
+		// pack bits into frame avoiding masked area.
+		x = y = width - 1;
+		k = v = 1; // up, minus
+		/* inteleaved data and ecc codes */
+		m = (datablkw + eccblkwid) * (neccblk1 + neccblk2) + neccblk2;
+		for (i = 0; i < m; i++) {
+			t = strinbuf[i];
+			for (j = 0; j < 8; j++, t <<= 1) {
+				if (0x80 & t)
+					qrframe[x + width * y] = 1;
+				do { // find next fill position
+					if (v)
+						x--;
+					else {
+						x++;
+						if (k) {
+							if (y != 0)
+								y--;
+							else {
+								x -= 2;
+								k = !k;
+								if (x == 6) {
+									x--;
+									y = 9;
+								}
+							}
+						} else {
+							if (y != width - 1)
+								y++;
+							else {
+								x -= 2;
+								k = !k;
+								if (x == 6) {
+									x--;
+									y -= 8;
+								}
+							}
+						}
+					}
+					v = !v;
+				} while (ismasked(x, y));
+			}
+		}
+
+		// save pre-mask copy of frame
+		strinbuf = qrframe.slice(0);
+		t = 0; // best
+		y = 30000; // demerit
+		// for instead of while since in original arduino code
+		// if an early mask was "good enough" it wouldn't try for a better one
+		// since they get more complex and take longer.
+		for (k = 0; k < 8; k++) {
+			applymask(k); // returns black-white imbalance
+			x = badcheck();
+			if (x < y) { // current mask better than previous best?
+				y = x;
+				t = k;
+			}
+			if (t == 7)
+				break; // don't increment i to a void redoing mask
+			qrframe = strinbuf.slice(0); // reset for next pass
+		}
+		if (t != k) // redo best mask - none good enough, last wasn't t
+			applymask(t);
+
+		// add in final mask/ecclevel bytes
+		y = fmtword[t + ((ecclevel - 1) << 3)];
+		// low byte
+		for (k = 0; k < 8; k++, y >>= 1)
+			if (y & 1) {
+				qrframe[(width - 1 - k) + width * 8] = 1;
+				if (k < 6)
+					qrframe[8 + width * k] = 1;
+				else
+					qrframe[8 + width * (k + 1)] = 1;
+			}
+		// high byte
+		for (k = 0; k < 7; k++, y >>= 1)
+			if (y & 1) {
+				qrframe[8 + width * (width - 7 + k)] = 1;
+				if (k)
+					qrframe[(6 - k) + width * 8] = 1;
+				else
+					qrframe[7 + width * 8] = 1;
+			}
+		return qrframe;
+	}
+
+
+
+
+	var _canvas = null;
+
+	var api = {
+
+		get ecclevel() {
+			return ecclevel;
+		},
+
+		set ecclevel(val) {
+			ecclevel = val;
+		},
+
+		get size() {
+			return _size;
+		},
+
+		set size(val) {
+			_size = val
+		},
+
+		get canvas() {
+			return _canvas;
+		},
+
+		set canvas(el) {
+			_canvas = el;
+		},
+
+		getFrame: function(string) {
+			return genframe(string);
+		},
+		//这里的utf16to8(str)是对Text中的字符串进行转码,让其支持中文
+		utf16to8: function(str) {
+			var out, i, len, c;
+
+			out = "";
+			len = str.length;
+			for (i = 0; i < len; i++) {
+				c = str.charCodeAt(i);
+				if ((c >= 0x0001) && (c <= 0x007F)) {
+					out += str.charAt(i);
+				} else if (c > 0x07FF) {
+					out += String.fromCharCode(0xE0 | ((c >> 12) & 0x0F));
+					out += String.fromCharCode(0x80 | ((c >> 6) & 0x3F));
+					out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+				} else {
+					out += String.fromCharCode(0xC0 | ((c >> 6) & 0x1F));
+					out += String.fromCharCode(0x80 | ((c >> 0) & 0x3F));
+				}
+			}
+			return out;
+		},
+		/**
+		 * 新增$this参数,传入组件的this,兼容在组件中生成
+		 */
+		draw: function(str, canvas, cavW, cavH, cavColor, haveImg, imageUrl, imageSize, $this, cb = function() {}, ecc) {
+			var that = this;
+			ecclevel = ecc || ecclevel;
+			canvas = canvas || _canvas;
+			if (!canvas) {
+				console.warn('No canvas provided to draw QR code in!')
+				return;
+			}
+			
+			
+			let pre_background = "#ffffff";
+			var size = Math.min(cavW, cavH);
+			str = that.utf16to8(str); //增加中文显示
+
+			var frame = that.getFrame(str);
+				// 组件中生成qrcode需要绑定this 
+			var ctx = uni.createCanvasContext(canvas, $this);
+			var px = Math.round(size / (width ));
+			
+			var roundedSize = px * (width);
+			// var px = 1 ;
+			// var roundedSize = px * (width + 8) ;
+			
+			//var roundedSize = 0 ;
+			//var offset = Math.floor((size - roundedSize) / 2);
+			var offset = 0 ;
+			size = roundedSize;
+			//ctx.clearRect(0, 0, cavW, cavW);
+			ctx.setFillStyle(pre_background)
+			ctx.fillRect(0, 0, cavW, cavW);
+			ctx.setFillStyle(cavColor);
+			for (var i = 0; i < width; i++) {
+				for (var j = 0; j < width; j++) {
+					if (frame[j * width + i]) {
+						ctx.fillRect(px * ( i) + offset, px * ( j) + offset, px, px);
+					}
+				}
+			}
+
+			//画图片
+			if (haveImg) {
+				try {
+					var x = Number(((cavW - imageSize - 14) / 2).toFixed(2));
+					var y = Number(((cavH - imageSize -14) / 2).toFixed(2));
+					drawRoundedRect(ctx, x, y, imageSize, imageSize, imageSize / 2, 6, true, true)
+
+					let isNetImg = false;
+
+					isNetImg = imageUrl.substr(0, 4) == 'http' ? true : false;
+
+					if (isNetImg) {
+						//网络图片下载到本地
+						uni.getImageInfo({
+							src: imageUrl,
+							success: function(res) {
+								ctx.drawImage(res.path, x, y, imageSize, imageSize);
+								//--增加绘制完成回调
+								ctx.draw(false, function() {
+									cb();
+								})
+							}
+						})
+					} else {
+						ctx.drawImage(imageUrl, x, y, imageSize, imageSize);
+						//--增加绘制完成回调
+						ctx.draw(false, function() {
+							cb();
+						})
+					}
+
+
+
+
+					// 画圆角矩形
+					function drawRoundedRect(ctxi, x, y, width, height, r, lineWidth, fill, stroke) {
+						ctxi.setLineWidth(lineWidth);
+						ctxi.setFillStyle(pre_background);
+						ctxi.setStrokeStyle(pre_background);
+						ctxi.beginPath(); // draw top and top right corner 
+						ctxi.moveTo(x + r, y);
+						ctxi.arcTo(x + width, y, x + width, y + r, r); // draw right side and bottom right corner 
+						ctxi.arcTo(x + width, y + height, x + width - r, y + height, r); // draw bottom and bottom left corner 
+						ctxi.arcTo(x, y + height, x, y + height - r, r); // draw left and top left corner 
+						ctxi.arcTo(x, y, x + r, y, r);
+						ctxi.closePath();
+						if (fill) {
+							ctxi.fill();
+						}
+						if (stroke) {
+							ctxi.stroke();
+						}
+					}
+				} catch (e) {
+					//TODO handle the exception
+				}
+
+			} else {
+				//--增加绘制完成回调
+				ctx.draw(false, function() {
+					cb();
+				})
+			}
+
+
+
+		}
+	}
+	module.exports = {
+		api
+	}
+})();

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 263 - 0
components/ay-qrcode/weapp-qrcode.js


+ 83 - 0
components/cartfixed.vue

@@ -0,0 +1,83 @@
+<template>
+	<view class="">
+		<view v-if="vuex_member_info.name" class="cart u-flex u-row-center" @click="$u.route('/shopping/cart',{type:'reLaunch'})">
+			<u-icon size="52rpx" class="icon" :name="staticUrl+'/img/cartico.png'"></u-icon>
+			<u-badge type="error" max="99" :value="cartTotal" :absolute="true" :offset="[0,5]"></u-badge>
+		</view>
+		<u-toast ref="uToast"></u-toast>
+	</view>
+</template>
+
+<script>
+	export default {
+		name:'cartfixed',
+		data() {
+			return {
+				cartTotal:0,
+				staticUrl:this.$commonConfig.staticUrl,
+			}
+		},
+		onShow() {
+		},
+		onLoad() {
+		},
+		mounted(){
+			this.getCartList()
+		},
+		methods: {
+			getCartList(isAdd){
+				if(!this.vuex_member_info.name){
+					return
+				}
+				this.$u.api.cartList().then(res=>{
+					if(isAdd){
+						if(res.data.total==this.cartTotal){
+							uni.showToast({
+								title:'添加成功',
+								icon:'success',
+								duration:1000
+							})
+							
+							// this.$refs.uToast.show({
+							// 	type:"success",
+							// 	message:'已在购物车'
+							// });
+						}else{
+							uni.showToast({
+								title:'添加成功',
+								icon:'success',
+								duration:1000
+							})
+							// this.$refs.uToast.show({
+							// 	type:"success",
+							// 	message:'加入成功'
+							// });
+						}
+					}
+					this.cartTotal = res.data.total;
+					console.log('getCartList',res);
+					}).catch(err=>{
+					console.log('getCartList',err.data);
+				})
+			},
+			addToCart(num){
+				this.getCartList()
+				// this.cartTotal += num;
+				console.log('addToCart',num);
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+.cart{
+	position: fixed;
+	width: 100rpx;
+	height: 100rpx;
+	border-radius: 50%;
+	right: 20rpx;
+	bottom: 300rpx;
+	background: #FFFFFF;
+	box-shadow: 0px 0px 10px 0px rgba(0,0,0,0.12);
+}
+</style>

+ 131 - 0
components/tabbar.vue

@@ -0,0 +1,131 @@
+<template>
+	<view class="tabbar">
+		<u-tabbar
+			:value="tabbarValue"
+			@change="tabbarChange"
+			:fixed="true"
+			:placeholder="true"
+			:border="false"
+			inactiveColor="#666"
+			activeColor="#EE0808"
+			:customStyle="{'padding-top':'5px','padding-bottom':'5px','z-index':'30','margin':'0 48rpx 40rpx','border-radius':'50rpx','box-shadow':'rgba(100, 100, 111, 0.2) 0px 7px 29px 0px'}"
+			:safeAreaInsetBottom="false"
+		>
+			<u-tabbar-item text="首页" >
+				<image
+					class="u-page__item__slot-icon"
+					slot="active-icon"
+					:src="staticUrl+'/img/tabbar-home.png'"
+				></image>
+				<image
+					class="u-page__item__slot-icon"
+					slot="inactive-icon"
+					:src="staticUrl+'/img/tabbar-home-gray.png'"
+				></image>
+			</u-tabbar-item>
+			<u-tabbar-item text="订单" >
+				<image
+					class="u-page__item__slot-icon"
+					slot="active-icon"
+					:src="staticUrl+'/img/tabbar-order.png'"
+				></image>
+				<image
+					class="u-page__item__slot-icon"
+					slot="inactive-icon"
+					:src="staticUrl+'/img/tabbar-order-gray.png'"
+				></image>
+			</u-tabbar-item>
+			<u-tabbar-item text="我的" >
+				<image
+					class="u-page__item__slot-icon"
+					slot="active-icon"
+					:src="staticUrl+'/img/tabbar-my.png'"
+				></image>
+				<image
+					class="u-page__item__slot-icon"
+					slot="inactive-icon"
+					:src="staticUrl+'/img/tabbar-my-gray.png'"
+				></image>
+			</u-tabbar-item>
+		</u-tabbar>
+	</view>
+</template>
+
+<script>
+export default {
+	name: 'tabbar',
+	props:{
+		tabbarIndexProps:{
+			type:Number,
+			default:0
+		},
+	},
+	data() {
+		return {
+			staticUrl:this.$commonConfig.staticUrl,
+			tabbarValue:0,
+		}
+	},
+	watch:{
+		'tabbarIndexProps': {
+			handler(newVal, oldVal) {
+				// let pages = getCurrentPages();
+				// console.log('pages============',pages);
+				this.tabbarValue = newVal
+			},
+			immediate: true
+		}
+	},
+	onLoad() {
+		console.log('onLoad tabbarIndex',this.tabbarIndex);
+	},
+	onShow() {
+		console.log('tabbarIndex',this.tabbarIndex);
+		this.tabbarValue = this.tabbarIndex
+	},
+	methods: {
+		tabbarChange(name){
+			console.log('name====',name);
+			const tabBarRoutes = {
+			0: '/pages/index/index',
+			// 1: {
+			//   url: '/shopping/producTypetList',
+			//   query: {
+			// 	navType: 'navigateTo'
+			//   }
+			// },
+			1: '/center/order',
+			// 2: '/shopping/shoppingindex',
+			2: '/center/center'
+		  };
+		  
+		  const targetRoute = tabBarRoutes[name];
+		  
+		  if (typeof targetRoute === 'string') {
+			uni.reLaunch({url: targetRoute});
+		  } else if (typeof targetRoute === 'object') {
+			  if(targetRoute.query.navType=='navigateTo'){
+				  this.$u.route(targetRoute.url, targetRoute.query);
+				  return
+			  }
+			let queryParams = uni.$u.queryParams(targetRoute.query);
+			uni.reLaunch({url: targetRoute.url+queryParams});
+		  }
+			// this.tabbarValue = name
+		}
+	}
+}	
+</script>
+
+<style lang="scss" scoped>
+.u-page__item__slot-icon{
+	width: 62rpx;
+	height: 62rpx;
+	&.big{
+		transform: scale(1.5) translateY(-0.5em);
+	}
+}
+.tabbar /deep/ .u-tabbar-item{
+	height: 100rpx;
+}
+</style>

+ 21 - 0
index.html

@@ -0,0 +1,21 @@
+<!DOCTYPE html>
+<html lang="en">
+  <head>
+    <meta charset="UTF-8" />
+	<link rel="icon" type="image/svg+xml" href="/logo.svg" />
+    <script>
+      var coverSupport = 'CSS' in window && typeof CSS.supports === 'function' && (CSS.supports('top: env(a)') ||
+        CSS.supports('top: constant(a)'))
+      document.write(
+        '<meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0' +
+        (coverSupport ? ', viewport-fit=cover' : '') + '" />')
+    </script>
+    <title></title>
+    <!--preload-links-->
+    <!--app-context-->
+  </head>
+  <body>
+    <div id="app"><!--app-html--></div>
+    <script type="module" src="/main.js"></script>
+  </body>
+</html>

+ 88 - 0
main.js

@@ -0,0 +1,88 @@
+import App from './App'
+
+// import $wxApi from "./wxapi.js";
+// Vue.prototype.$wxApi = $wxApi;
+//微信支付封装
+// import $pay from "./pay.js";
+// Vue.prototype.$pay = $pay
+
+// #ifndef VUE3
+import Vue from 'vue'
+Vue.config.productionTip = false
+App.mpType = 'app'
+
+try {
+  function isPromise(obj) {
+    return (
+      !!obj &&
+      (typeof obj === "object" || typeof obj === "function") &&
+      typeof obj.then === "function"
+    );
+  }
+
+  // 统一 vue2 API Promise 化返回格式与 vue3 保持一致
+  uni.addInterceptor({
+    returnValue(res) {
+      if (!isPromise(res)) {
+        return res;
+      }
+      return new Promise((resolve, reject) => {
+        res.then((res) => {
+          if (res[0]) {
+            reject(res[0]);
+          } else {
+            resolve(res[1]);
+          }
+        });
+      });
+    },
+  });
+} catch (error) { }
+
+import uView from '@/uni_modules/uview-ui'
+Vue.use(uView)
+
+import { commonConfig } from './common/config';
+Vue.prototype.$commonConfig = commonConfig;
+
+import './utils/filter' 
+
+import store from '@/store';
+
+// 引入uView提供的对vuex的简写法文件
+let vuexStore = require('@/store/$u.mixin.js');
+Vue.mixin(vuexStore);
+
+const app = new Vue({
+  ...App,
+  store
+})
+
+//uviewui v1
+// // http拦截器,将此部分放在new Vue()和app.$mount()之间,才能App.vue中正常使用
+// import httpInterceptor from '@/common/http.interceptor.js';
+// Vue.use(httpInterceptor, app);
+
+
+// 引入请求封装,将app参数传递到配置中
+require('./common/request.js')(app)
+// http接口API抽离,免于写url或者一些固定的参数
+import httpApi from '@/common/http.api.js';
+Vue.use(httpApi, app);
+
+app.$mount()
+
+
+
+
+// #endif
+
+// #ifdef VUE3
+import { createSSRApp } from 'vue'
+export function createApp() {
+  const app = createSSRApp(App)
+  return {
+    app
+  }
+}
+// #endif

+ 111 - 0
manifest.json

@@ -0,0 +1,111 @@
+{
+    "name" : "great_group",
+    "appid" : "__UNI__67D09D5",
+    "description" : "",
+    "versionName" : "1.0.0",
+    "versionCode" : "100",
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        /* 模块配置 */
+        "modules" : {},
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
+                ]
+            },
+            /* ios打包配置 */
+            "ios" : {},
+            /* SDK配置 */
+            "sdkConfigs" : {}
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "wx1a07584f15abf1ff",
+        "setting" : {
+            "urlCheck" : false,
+            "minified" : true,
+            "postcss" : true
+        },
+        "usingComponents" : true,
+        "permission" : {},
+        // "scope.userLocation" : {
+        //     "desc" : "你的位置信息将用于小程序位置接口的效果展示"
+        // },
+        // "scope.writePhotosAlbum" : {
+        //     "desc" : "保存图片到相册"
+        // }
+        "requiredPrivateInfos" : [ "getLocation", "chooseLocation" ],
+        "lazyCodeLoading" : "requiredComponents"
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2",
+    "h5" : {
+        "devServer" : {
+			"port" : 8080, //浏览器运行端口
+			"disableHostCheck" : true,
+            "proxy" : {
+                "/api" : {
+                    // "disableHostCheck" : true,
+                    "target" : "https://greath5.dev.gztjy.top/", //请求的目标域名
+                    "changeOrigin" : true,
+                    "secure" : false,
+                    "pathRewrite" : {
+                        //使用代理; 告诉他你这个连接要用代理
+                        "^/api" : ""
+                    }
+                }
+            },
+            "https" : false
+        },
+        "sdkConfigs" : {
+            "maps" : {}
+        },
+        "title" : "伟大转折",
+        "router" : {
+            "mode" : "history"
+        },
+        "template" : ""
+    },
+    "fallbackLocale" : "zh-Hans"
+}

+ 18 - 0
mixin.js

@@ -0,0 +1,18 @@
+export const systemInfo = {
+  data: () => ({
+    statusBarHeight: 0,
+    navigationBarHeight: 0,
+    navHeight: 0,
+    windowHeight: 0, // 可使用窗口高度
+  }),
+ 
+  methods: {
+    // 获取设备信息
+    getSystemInfo() {
+      this.statusBarHeight = getApp().globalData.statusBarHeight
+      this.navigationBarHeight = getApp().globalData.navigationBarHeight
+      this.windowHeight = uni.getSystemInfoSync().windowHeight
+      this.navHeight = getApp().globalData.navHeight
+    },
+  },
+}

+ 16 - 0
package-lock.json

@@ -0,0 +1,16 @@
+{
+  "requires": true,
+  "lockfileVersion": 1,
+  "dependencies": {
+    "moment": {
+      "version": "2.29.4",
+      "resolved": "https://registry.npmmirror.com/moment/-/moment-2.29.4.tgz",
+      "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w=="
+    },
+    "weixin-jsapi": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmmirror.com/weixin-jsapi/-/weixin-jsapi-1.1.0.tgz",
+      "integrity": "sha512-986drpgKs1yf8gK1A/hrdF2U5cjp/zW/7bhoL37JLoePhNiO14JqZpa+wlNslge0Hlw7gEXMTnEntFvvSXz8Bw=="
+    }
+  }
+}

+ 28 - 0
package.json

@@ -0,0 +1,28 @@
+{
+  "uni-app": {
+    "scripts": {
+      "build:build64": {
+        "title": "build:build64",
+        "env": {
+          "UNI_PLATFORM": "h5",
+          "H_NODE_ENV": "development",
+          "H_BASE_URL": "https://scenicadmin.hw.hongweisoft.com/serviceapi",
+          "H_UP_FILE_URL": "https://scenicadmin.hw.hongweisoft.com/serviceapi"
+        }
+      },
+      "build:buildOnline": {
+        "title": "build:online",
+        "env": {
+          "UNI_PLATFORM": "h5",
+          "H_NODE_ENV": "production",
+          "H_BASE_URL": "https://scenicadmin.hw.hongweisoft.com/serviceapi",
+          "H_UP_FILE_URL": "https://scenicadmin.hw.hongweisoft.com/serviceapi"
+        }
+      }
+    }
+  },
+  "dependencies": {
+    "moment": "^2.29.4",
+    "weixin-jsapi": "^1.1.0"
+  }
+}

+ 174 - 0
pages.json

@@ -0,0 +1,174 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "首页",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/login/login",
+			"style": {
+				"navigationBarTitleText": "登录",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/searchpage",
+			"style": {
+				"navigationBarTitleText": "搜索",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/login/regulation",
+			"style": {
+				"navigationBarTitleText": "使用条款",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/bookticket",
+			"style": {
+				"navigationBarTitleText": "订票",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/ticketlist",
+			"style": {
+				"navigationBarTitleText": "票务购买",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/chosenposition",
+			"style": {
+				"navigationBarTitleText": "选择区域",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/news",
+			"style": {
+				"navigationBarTitleText": "演出资讯",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/newsdetails",
+			"style": {
+				"navigationBarTitleText": "资讯详情",
+				"navigationStyle": "custom"
+			}
+		},
+		{
+			"path": "pages/actors",
+			"style": {
+				"navigationBarTitleText": "演员列表",
+				"navigationStyle": "custom"
+			}
+		}
+	],
+	"subPackages": [
+		{
+			"root": "center",
+			"pages": [
+				{
+					"path": "center",
+					"style": {
+						"navigationBarTitleText": "我的",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "factorauth",
+					"style": {
+						"navigationBarTitleText": "实名认证",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "viewrecord",
+					"style": {
+						"navigationBarTitleText": "观看记录",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "memberinfo",
+					"style": {
+						"navigationBarTitleText": "个人信息",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "people",
+					"style": {
+						"navigationBarTitleText": "信息填写",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "mycoupon",
+					"style": {
+						"navigationBarTitleText": "我的优惠券",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "invoice",
+					"style": {
+						"navigationBarTitleText": "开具发票",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "order",
+					"style": {
+						"navigationBarTitleText": "订单",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "orderdetails",
+					"style": {
+						"navigationBarTitleText": "订单详情",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "refund",
+					"style": {
+						"navigationBarTitleText": "申请退款",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "paysuccess",
+					"style": {
+						"navigationBarTitleText": "支付成功",
+						"navigationStyle": "custom"
+					}
+				},
+				{
+					"path": "viewRefund",
+					"style": {
+						"navigationBarTitleText": "查看退款",
+						"navigationStyle": "custom"
+					}
+				}
+			]
+		}
+	],
+	"preloadRule": {
+	},
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "uni-app",
+		"navigationBarBackgroundColor": "#F8F8F8",
+		"backgroundColor": "#F8F8F8"
+	},
+	"uniIdRouter": {}
+}

+ 112 - 0
pages/actors.vue

@@ -0,0 +1,112 @@
+<template>
+	<view class="pages">
+		<view class="" :style="{height: navHeight+'px' }"></view>
+		<view class="navbar-box">
+			<u-navbar title="演职人员" :safeAreaInsetTop="true" @leftClick="leftClick" :titleStyle="{color:'#000'}" leftIconColor="#000" bgColor="#fff"></u-navbar>
+		</view>
+		<view class="page-wrap">
+			<!-- <view class="title">演员信息</view> -->
+			<view class="list">
+				<view class="item" v-for="(item, index) in actorsArr" :key="index">
+					<image class="img" :src="item.performerHead||staticUrl+'/img/actors.png'"></image>
+					<view class="text">
+						<view class="name">{{item.performerName}}</view>
+						<view class="role u-line-1">{{item.performerRole}}</view>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	export default {
+		mixins:[systemInfo],
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				performId:'',
+				actorsArr:[],
+				params:{
+					
+				}
+				
+			}
+		},
+		onShow() {
+		},
+		onLoad(page) {
+			console.log('page',page);
+			this.performId = page.performId;
+			this.getActors();
+			this.getSystemInfo();
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			getActors(){
+				this.$u.api.actorsList({performId:this.performId}).then(res=>{
+					console.log('actorsList',res.data);
+					this.actorsArr =  res.data.list;
+				}).catch(err=>{
+					console.log('actorsList',err);
+				})
+			},
+
+		}
+	}
+</script>
+<style>
+page{
+	background-color: #F7F8F9;
+}
+</style>
+<style lang="scss" scoped>
+.page-wrap{
+	margin-top: 44rpx;
+}
+.title{
+	font-size: 28rpx;
+	font-weight: 400;
+	color: #7F7F7F;
+	line-height: 42rpx;
+	margin-bottom: 40rpx;
+}
+.list{
+	display: grid;
+	grid-template-columns: repeat(3, 1fr);
+	gap: 24rpx;
+	.item{
+		text-align: center;
+		background: #FFFFFF;
+		border-radius: 20rpx;
+		padding: 12rpx;
+	}
+	.img{
+		display: block;
+		width: 100%;
+		height: 200rpx;
+		margin-bottom: 16rpx;
+	}
+	.name{
+		font-size: 28rpx;
+		font-weight: 500;
+		color: #2D2D2D;
+		line-height: 42rpx;
+		margin-bottom: 4rpx;
+	}
+	.role{
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #7F7F7F;
+		line-height: 36rpx;
+	}
+}
+</style>

+ 924 - 0
pages/bookticket.vue

@@ -0,0 +1,924 @@
+<template>
+	<view class="pages">
+		<view class="" :style="{height: navHeight+'px' }"></view>
+		<view class="navbar-box">
+			<u-navbar title="订单详情" :safeAreaInsetTop="true" @leftClick="leftClick" :titleStyle="{color:'#fff'}"
+				leftIconColor="#fff" bgColor="transparent"></u-navbar>
+		</view>
+		<view class="page-wrap">
+			<view class="base-info">
+				<view class="up u-flex">
+					<image class="img" :src="pageContent.performImg" alt="">
+						<view class="text">
+							<view class="name text-item">{{pageContent.performName}}-{{pageContent.goodsName}}</view>
+							<view class="time text-item">日期 {{pageContent.timeDate}}({{pageContent.timeWeek}})</view>
+							<view class="time text-item">场次 {{pageContent.performTimeStart}} -
+								{{pageContent.performTimeEnd}}</view>
+							<view class="num text-item">{{pageContent.goodsName}} / {{pageContent.seatTypeName}}</view>
+							<view class=" text-item">{{pageContent.auditoriumName}}</view>
+							<!-- <view class="position text-item">{{performInfo.name}}</view> -->
+							<!-- 	<view class="addr u-flex u-row-between">
+							<view class="u-line-1">地址:遵义市《伟大转折》演艺中心</view>
+							<u-icon name="arrow-right" color="#2D2D2D" size="36rpx"></u-icon>
+						</view> -->
+						</view>
+				</view>
+				<view class="down">
+					<view class="num-wrap u-flex u-row-between">
+						<view class="title">选购数量</view>
+						<view class="num">{{totalVisitor}}</view>
+					</view>
+				</view>
+			</view>
+			<view class="block-wrap purchaser">
+				<view class="block-title u-flex">
+					<view class="name">购票人信息</view>
+					<!-- <text>用于入园身份验证</text> -->
+				</view>
+				<view class="" v-if="vuex_member_info.isAuth">
+					<view class="item">姓名:{{pageContent.purchaser.name}}</view>
+					<view class="item">手机号:{{pageContent.purchaser.mobile|hidePhoneNumber}}</view>
+					<view class="item">身份证:{{pageContent.purchaser.idcard|maskID}}</view>
+				</view>
+				<view class="un-auth" v-else @click="$u.route('center/factorauth',{from:'bookticket'})">
+					请先<text style="color: #ED0000;">实名认证</text>
+				</view>
+			</view>
+			<view class="block-wrap visitors">
+				<view class="block-title u-flex">
+					<view class="name">观影人信息</view>
+					<!-- <text>用于入园身份验证</text> -->
+				</view>
+				<view class="people-list u-flex u-flex-wrap">
+					<view class="people btn" v-if="visitors.length>0" v-for="(visitor,index) in visitors" :key="index">
+						{{visitor.name}}
+					</view>
+					<view class="btn u-flex u-row-center" @click="visitorShow = true">
+						<u-icon name="plus-circle" color="#2D2D2D" size="32rpx"></u-icon>
+						<text class="text">新增/更换</text>
+					</view>
+				</view>
+				<!-- <view class="no-people u-flex" v-if="!visitors.length>0">
+					<text>游客</text>
+					<text class="right" @click="visitorShow = true">点击选择游客</text>
+				</view> -->
+				<view class="peoples" v-if="visitors.length>0">
+					<view class="peoples-item u-flex u-row-between" v-for="(visitor,index) in visitors" :key="index">
+						<view class="left u-flex">
+							<u-icon @click="delVisitor(visitor)" name="close-circle" color="#2D2D2D"
+								size="32rpx"></u-icon>
+							<text style="margin-left: 8rpx;">观影人{{ index + 1 }}</text>
+							<text class="name">{{visitor.name}}</text>
+							<!-- <text class="people-id">{{ visitor.idcard | maskID }}</text> -->
+						</view>
+						<view class="right" @click="editVisitor(visitor)">编辑</view>
+					</view>
+				</view>
+			</view>
+			<view class="coupon">
+				<view class="title u-flex u-row-between u-border-bottom">
+					<text>本单可享优惠</text>
+				</view>
+				<view class="single-til u-flex u-row-between">
+					<view class="text">优惠券</view>
+					<view class="more-text u-flex">
+						<text>未使用</text>
+						<u-icon name="arrow-right" color="#E6E6E6" size="24rpx"></u-icon>
+					</view>
+				</view>
+				<view class="no-coupon">
+					您暂无可使用优惠券~
+				</view>
+			</view>
+			<view class="notice">
+				<view class="title u-flex u-row-between u-border-bottom">
+					<text>购票须知</text>
+				</view>
+				<view class="notice-item parse-content">
+					<u-parse :content="pageContent.ticketNotice"></u-parse>
+				</view>
+				<!-- <view class="notice-item">
+					1.由于设备故障等不可抗力因素,存在少量场次取消的情况,会
+					进行退票退款
+				</view>
+				<view class="notice-item">
+					2.由于影院系统不稳定等因素,存在出票失败的情况,会进行退款
+				</view>
+				<view class="notice-item">
+					3.取票码可以在“我的-订单页”中查看
+				</view> -->
+			</view>
+		</view>
+		<u-popup :show="visitorShow">
+			<view class="people-show-content">
+				<view class="title">
+					<view class="cancel" @click="visitorShow=false">取消</view>
+					选择观影人
+				</view>
+				<view class="add-btn u-flex u-row-center" @click="addVisitor">
+					<!-- <image class="img" :src="staticUrl+'/img/car.png'" ></image> -->
+					添加观影人信息
+				</view>
+				<view class="list">
+					<u-checkbox-group v-model="selectedVisitor" iconPlacement="left">
+						<view class="people u-flex u-row-between" v-for="(item,index) in visitorList" :key="index">
+							<u-checkbox activeColor="#ED0303" :label="item.name" :name="item.id"></u-checkbox>
+							<u-icon name="edit-pen-fill" color="#7F7F7F" size="32rpx"
+								@click="editVisitor(item)"></u-icon>
+						</view>
+					</u-checkbox-group>
+				</view>
+				<view class="full-btn" @click="confirmVisitor">确定</view>
+			</view>
+		</u-popup>
+		<view class="page-bottom">
+			<view class="inner u-flex u-row-between">
+				<view class="left u-flex">
+					<text>应付金额:</text>
+					<view class="total-price">
+						{{totalPrice}}
+					</view>
+				</view>
+				<view class="right">
+					<view class="btn active" v-if="totalPrice>0&&cansubmit&&vuex_member_info.isAuth"
+						@click="submitorder">提交报名</view>
+					<view class="btn" v-else>提交报名</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import {
+		systemInfo
+	} from "@/mixin.js";
+	// #ifdef H5
+	import wxH5 from "weixin-jsapi";
+	// #endif
+	export default {
+		mixins: [systemInfo],
+		data() {
+			return {
+				performId: '',
+				performInfo: {},
+				pageData: {}, //上个页面传过来的数据
+				pageContent: {
+					performName: '',
+					goodsName: '',
+					timeDate: '',
+					timeWeek: '',
+					seatTypeName: '',
+					auditoriumName: '',
+					performTimeEnd: '',
+					performTimeStart: '',
+					purchaser: {
+						mobile: '',
+						idcard: ''
+					},
+					ticketNotice: '',
+					viewerList: [],
+				}, //页面信息
+				ticketNotice: '',
+				cansubmit: true,
+				staticUrl: this.$commonConfig.staticUrl,
+				visitors: [], //确认的游客
+				visitorShow: false, //游客弹层
+				visitorList: [], //游客列表
+				selectedVisitor: [], //选中的游客(id)
+				params: {}, //要提交的数据
+				orderId: '', //订单提交获取
+				payResult: {}, //gotoPay结果
+				paysuccess: false, //支付结果
+				templateIdList: [], //微信小程序订阅消息
+
+			}
+		},
+		computed: {
+			totalPrice() {
+				let that = this;
+				return this.visitors.reduce((total, item) => {
+					let price = null;
+					price = Number(that.pageData.salePrice);
+					total += price;
+					return total;
+				}, 0).toFixed(2);
+			},
+			totalVisitor() {
+				let that = this;
+				return this.visitors.reduce((total, item) => {
+					total += 1;
+					return total;
+				}, 0);
+			},
+		},
+		onShow() {
+			this.getMemberAll();
+			this.getSettlement();
+		},
+		onLoad(page) {
+			console.log('page', page);
+			this.pageData = page;
+			this.performId = page.performId;
+			this.pageData.performTimeId = page.performTimeId;
+			this.pageData.seatTypeId = page.seatTypeId
+			this.getSystemInfo();
+			this.getSettlement();
+
+			this.getTemplateIdList(); //获取模板列表
+
+		},
+		methods: {
+			leftClick(e) {
+				let pages = getCurrentPages();
+				if (pages.length == 1) {
+					uni.$u.route('/pages/index/index')
+				} else {
+					uni.navigateBack()
+				};
+			},
+			getSettlement() {
+				this.$u.api.getSettlement(this.pageData).then(res => {
+					// console.log('getSettlement',res.data);
+					this.pageContent = res.data;
+				}).catch(err => {
+					console.log('getSettlement', err);
+				})
+			},
+			getMemberAll() {
+				let that = this;
+				// const userId = this.vuex_member_info.id;
+				const userId = this.pageContent.viewerList[0]?.id;
+				this.$u.api.selectMemberAll({
+					userid: userId
+				}).then(res => {
+					// console.log('getMemberAll',res.data);
+					this.visitorList = res.data.list;
+					// this.visitors = this.visitorList.filter(obj => obj.memberId==userId);
+					this.visitors = this.visitorList.filter(obj => that.selectedVisitor.includes(obj.idcard));
+					// console.log('this.visitorList',this.visitorList);
+					// console.log('this.selectedVisitor',this.selectedVisitor);
+					// console.log('this.visitors',this.visitors);
+				}).catch(err => {
+					console.log('getMemberAll', err);
+				})
+			},
+			confirmVisitor() {
+				let that = this;
+				// console.log('selectedVisitor',this.selectedVisitor);
+				this.visitorShow = false;
+				this.visitors = this.visitorList.filter(obj => that.selectedVisitor.includes(obj.id)).map(item => {
+					return {
+						name: item.name,
+						mobile: item.mobile,
+						idcard: item.idcard
+					}
+				});
+				// console.log('this.visitors',this.visitors);
+			},
+			delVisitor(visitor) {
+				// console.log('delVisitor',visitor);
+				// console.log('this.visitors',this.visitors);
+				this.visitors = this.visitors.filter(obj => obj.idcard != visitor.idcard);
+			},
+			addVisitor() {
+				uni.$u.route('/center/people', {
+					type: 'addVisitor',
+					fromPage: 'bookticket'
+				});
+			},
+			editVisitor(item) {
+				uni.$u.route('/center/people', {
+					type: 'editVisitor',
+					fromPage: 'bookticket',
+					id: item.id,
+					name: item.name,
+					mobile: item.mobile,
+					idcard: item.idcard,
+				});
+			},
+			getTemplateIdList() {
+				this.$u.api.templateIdList({
+					templateLabel: 'order_pay'
+				}).then(res => {
+					console.log('getTemplateIdList', res.data);
+					this.templateIdList = res.data.list.map(item => {
+						return item.templateId
+					});
+					// if(this.templateIdList.length>0){
+					// 	this.templateEven();
+					// }
+				}).catch(err => {
+					console.log('getTemplateIdList', err);
+				})
+			},
+			submitorder() {
+				let params = {
+					performId: this.pageContent.performId,
+					goodsList: [{
+						goodsId: this.pageContent.goodsId,
+						salePeice: this.pageContent.salePrice,
+						saleNum: 1
+					}],
+					auditoriumId: this.pageContent.auditoriumId,
+					performTimeId: this.pageContent.performTimeId,
+					seatTypeId: this.pageContent.seatTypeId,
+					purchaser: {
+						name: this.pageContent.purchaser.name,
+						mobile: this.pageContent.purchaser.mobile,
+						idcard: this.pageContent.purchaser.idcard
+					},
+					viewerList: this.visitors
+				}
+				// #ifdef MP
+				params['source'] = 1
+				// #endif 
+				// #ifdef H5
+				params['source'] = 4
+				// #endif
+				this.cansubmit = false;
+				// console.log('pageData',this.pageData);
+				// console.log('visitors',this.visitors);
+				// console.log('params',params);
+				this.$u.api.submitOrder(params).then(res => {
+					// console.log('submitOrder',res.data);
+					this.orderId = res.data.orderId;
+					// this.templateEven()
+					this.setTemplate();
+
+				}).catch(err => {
+					this.cansubmit = true;
+					uni.$u.toast(err.msg);
+					if (err.msg.includes('限购')) {
+						setTimeout(() => {
+							uni.$u.route('/center/order', {
+								status: 0
+							});
+						}, 2000)
+					}
+					console.log('submitOrder', err);
+				})
+			},
+			// 订阅消息
+			templateEven() {
+				let that = this
+				wx.showModal({
+					title: '温馨提示',
+					content: '为更好的促进与您的交流,服务号需要实时向您发送消息',
+					confirmText: "同意",
+					cancelText: "拒绝",
+					success: function(res) {
+						if (res.confirm) {
+							//调用订阅消息
+							console.log('用户点击确定');
+							//调用订阅
+							that.setTemplate();
+						} else if (res.cancel) {
+							console.log('用户点击取消');
+							///显示第二个弹说明一下
+							wx.showModal({
+								title: '温馨提示',
+								content: '拒绝后您将无法获取实时的消息',
+								confirmText: "知道了",
+								showCancel: false,
+								success: function(res) {
+									that.gotoPay();
+									///点击知道了的后续操作 
+									///如跳转首页面 
+								}
+							});
+						}
+					}
+				});
+			},
+			// 设置小程序订阅消息
+			setTemplate() {
+				let that = this;
+				console.log('templateIdList', this.templateIdList);
+				// #ifdef MP
+				uni.requestSubscribeMessage({
+					tmplIds: that.templateIdList,
+					success(res) {
+						// that.gotoPay();
+						console.log("success:", res);
+					},
+					fail(res) {
+						console.log("fail:", res);
+					},
+					complete(res) {
+						that.gotoPay();
+						console.log("complete:", res);
+					}
+				})
+				// #endif
+				// #ifdef H5
+				that.gotoPay()
+				// #endif
+			},
+			gotoPay() {
+				this.$u.api.gotoPay({
+					orderId: this.orderId,
+					openid: ''
+				}).then(res => {
+					this.payResult = res.data.payInfo;
+					this.payResult.package = res.data.payInfo.packageValue;
+					// #ifdef H5
+					this.initConfig(this.payResult)
+					// #endif 
+					// #ifdef MP
+					this.wxPay()
+					// #endif 
+
+
+					// if(this.params.paymentMode==1||this.params.paymentMode==4){
+					// 	this.wxPay()
+					// }else{
+					// 	uni.$u.route('/shopping/paysuccess');
+					// }
+					console.log('gotoPayres', res.data);
+				}).catch(err => {
+					this.cansubmit = true;
+					// this.paypass = '';
+					// this.checkPassShow = false;
+					console.log('gotoPay', err);
+				})
+			},
+			wxPay() {
+				let that = this;
+				uni.requestPayment({
+					...this.payResult,
+					"provider": "wxpay",
+					"orderInfo": {
+						// "appid": "wx499********7c70e",  // 微信开放平台 - 应用 - AppId,注意和微信小程序、公众号 AppId 可能不一致
+						// "noncestr": "c5sEwbaNPiXAF3iv", // 随机字符串
+						// "package": "Sign=WXPay",        // 固定值
+						// "partnerid": "148*****52",      // 微信支付商户号
+						// "prepayid": "wx202254********************fbe90000", // 统一下单订单号 
+						// "timestamp": 1597935292,        // 时间戳(单位:秒)
+						// "sign": "A842B45937F6EFF60DEC7A2EAA52D5A0" // 签名,这里用的 MD5/RSA 签名
+					},
+					success(res) {
+						that.payQuery();
+					},
+					fail(e) {
+						uni.$u.route('/center/order', {
+							status: 0
+						});
+						console.log('wxPayfail', e);
+					},
+					complete() {
+						this.cansubmit = true;
+					}
+				})
+			},
+			payQuery() {
+				let that = this;
+				let retryCount = 0;
+				let maxRetryCount = 5; // 设置最大重试次数
+				let interval = 2000; // 设置间隔时间为2秒
+				let timeout = 10000; // 设置超时时间为10秒
+				let timer;
+				uni.showLoading({
+					title: '支付结果查询中'
+				})
+				timer = setInterval(() => {
+					retryCount++;
+					if (retryCount > maxRetryCount || retryCount * interval > timeout) {
+						clearInterval(timer);
+						uni.hideLoading();
+						console.log("支付查询超时或达到最大重试次数");
+						// 在这里添加超时或达到最大重试次数的处理逻辑
+						uni.$u.route('/center/order');
+					} else {
+						console.log("第" + retryCount + "次查询");
+						// 调用查询支付状态的方法
+						// 如果支付状态为成功,则清除定时器并处理成功的逻辑
+						// 如果支付状态为失败,则清除定时器并处理失败的逻辑
+						this.$u.api.payQuery({
+							orderId: this.orderId
+						}).then(res => {
+							// 0-未支付 1-已支付 2-支付中 3-支付失败 4-支付退款
+							let payStatus = res.data.payStatus;
+							if (payStatus === 1) {
+								uni.$u.route('/center/order', {
+									status: 3
+								});
+								// uni.$u.route('/center/paysuccess');
+							} else if (payStatus === 0 || payStatus === 2) {
+								this.payQuery()
+							} else if (payStatus === 3) {
+								uni.toast('支付失败')
+							}
+							clearInterval(timer);
+						}).catch(err => {
+							console.log('payQuery', err);
+						}).finally(() => {
+							uni.hideLoading()
+						})
+					}
+				}, interval);
+			},
+
+			/**
+			 * 公众号微信支付
+			 */
+			initConfig() {
+				// #ifdef H5
+				let that = this
+				wxH5.config({
+					debug: false, // 这里一般在测试阶段先用ture,等打包给后台的时候就改回false, 
+					appId: that.payResult.appId, // 必填,公众号的唯一标识 
+					timestamp: that.payResult.timeStamp, // 必填,生成签名的时间戳     
+					nonceStr: that.payResult.nonceStr, // 必填,生成签名的随机串 
+					signature: that.payResult.paySign, // 必填,签名 
+					jsApiList: ['chooseWXPay', 'checkJsApi'] // 必填,需要使用的JS接口列表 
+				})
+				wxH5.ready(() => {
+					wxH5.chooseWXPay({
+						timestamp: that.payResult.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符     
+						nonceStr: that.payResult.nonceStr, // 支付签名随机串,不长于 32 位         
+						package: that.payResult.packageValue, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)     
+						signType: 'SHA1', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'     
+						paySign: that.payResult.paySign, // 支付签名     
+						success: () => {
+							that.payQuery();
+						},
+						fail: (e) => {
+							uni.$u.route('/center/order', {
+								status: 0
+							});
+							console.log('wxPayfail', e);
+						},
+						cancel: () => {
+							uni.$u.route('/center/order', {
+								status: 0
+							});
+							that.cansubmit = true;
+						}
+					})
+				})
+				// #endif
+			}
+
+		}
+	}
+</script>
+<style>
+	page {
+		background: linear-gradient(180deg, #ED0000 0%, #F9FBFD 50%, #fff 100%);
+		background-repeat: no-repeat;
+	}
+</style>
+<style lang="scss" scoped>
+	.base-info {
+		border-radius: 30rpx;
+		overflow: hidden;
+		background: radial-gradient(circle at -26rpx 294rpx, transparent 8%, #fff 4%) left, radial-gradient(circle at calc(100% + 26rpx) 294rpx, transparent 8%, #fff 4%) right;
+		background-size: 50% 100%;
+		background-repeat: no-repeat;
+		padding: 34rpx 36rpx;
+		box-sizing: border-box;
+		margin: 30rpx 0 40rpx;
+
+		.up {
+			margin-bottom: 36rpx;
+
+			.img {
+				display: block;
+				width: 172rpx;
+				height: 230rpx;
+				border-radius: 20rpx;
+				margin-right: 32rpx;
+			}
+
+			.text {
+				font-size: 22rpx;
+				font-weight: 400;
+				color: #606060;
+				line-height: 1;
+			}
+
+			.text-item {
+				margin-bottom: 18rpx;
+			}
+
+			.name {
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #2D2D2D;
+			}
+
+			.addr {
+				font-size: 24rpx;
+				font-weight: 500;
+				color: #2D2D2D;
+			}
+		}
+
+		.down {
+			position: relative;
+
+			&:before {
+				content: '';
+				width: calc(100% + 26rpx);
+				height: 1px;
+				position: absolute;
+				left: -13rpx;
+				top: -2rpx;
+				border-top: 4rpx dashed #F39FA0;
+			}
+
+			.num-wrap {
+				padding-top: 36rpx;
+
+				.title {
+					font-size: 14px;
+					color: #4E4E4E;
+				}
+			}
+		}
+	}
+
+	.block-wrap {
+		background: #FFFFFF;
+		box-shadow: 0rpx 0rpx 20rpx 2rpx rgba(221, 221, 221, 0.5);
+		border-radius: 20rpx;
+		margin-bottom: 28rpx;
+		padding: 32rpx 30rpx 34rpx;
+
+		.block-title {
+			// padding-left: 16rpx;
+			position: relative;
+			margin-bottom: 22rpx;
+			font-size: 24rpx;
+			font-weight: 500;
+			color: #CACACA;
+
+			// &:before{
+			// 	content: '';
+			// 	width: 6rpx;
+			// 	height: 32rpx;
+			// 	background: #1677FF;
+			// 	border-radius: 4rpx;
+			// 	position: absolute;
+			// 	left: 0;
+			// 	top: 50%;
+			// 	margin-top: -16rpx;
+			// }
+			.name {
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #2D2D2D;
+				margin-right: 20rpx;
+			}
+		}
+
+		.no-people {
+			font-size: 28rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+
+			.right {
+				margin-left: 20rpx;
+				color: #1677FF;
+			}
+		}
+
+		.peoples {
+			font-size: 28rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			margin-bottom: 24rpx;
+
+			.peoples-item {
+				margin-bottom: 10rpx;
+			}
+
+			.name {
+				margin-left: 20rpx;
+				margin-right: 28rpx;
+				font-size: 28rpx;
+				font-weight: bold;
+				color: #363636;
+			}
+
+			.people-id {
+				font-size: 20rpx;
+				font-weight: 400;
+				color: #606060;
+			}
+
+			.right {
+				font-size: 28rpx;
+				font-weight: 400;
+				color: #ED0303;
+			}
+		}
+	}
+
+	.people-list {
+		margin-bottom: 20rpx;
+
+		.btn {
+			width: 185rpx;
+			height: 60rpx;
+			line-height: 60rpx;
+			font-size: 24rpx;
+			font-weight: 500;
+			color: #2D2D2D;
+			background-color: #F1F1F1;
+			border: 1px solid #F1F1F1;
+			border-radius: 8rpx;
+			margin-bottom: 12rpx;
+
+			.text {
+				margin-left: 16rpx;
+			}
+
+			&.people {
+				margin-right: 16rpx;
+				text-align: center;
+				background-color: #FFF2F2;
+				border-color: #ED0303;
+			}
+		}
+	}
+
+	.visitors {
+		margin-bottom: 20rpx;
+	}
+
+	.people-show-content {
+		background: #FFFFFF;
+		border-radius: 40rpx 40rpx 0rpx 0rpx;
+		padding: 30rpx 40rpx;
+
+		.title {
+			position: relative;
+			font-size: 32rpx;
+			font-weight: 500;
+			color: #606060;
+			margin-bottom: 46rpx;
+			text-align: center;
+
+			.cancel {
+				position: absolute;
+				left: 0;
+				top: 0;
+			}
+		}
+
+		.add-btn {
+			margin-bottom: 28rpx;
+			background: #ffdee1;
+			border-radius: 44rpx;
+			padding: 26rpx;
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #ff1616;
+
+			.img {
+				width: 28rpx;
+				height: 36rpx;
+				margin-right: 36rpx;
+			}
+		}
+
+		.list {
+			/deep/ .u-radio-group {
+				flex-wrap: wrap;
+			}
+
+			/deep/ .u-checkbox-group {
+				flex-wrap: wrap;
+			}
+
+			overflow-y: auto;
+			height: 40vh;
+
+			.people {
+				width: 100%;
+				margin-bottom: 24rpx;
+			}
+		}
+
+		.btn {}
+	}
+
+	.coupon {
+		background: #FFFFFF;
+		box-shadow: 0rpx 0rpx 16rpx 2rpx rgba(232, 232, 232, 0.5);
+		border-radius: 16rpx;
+		padding: 44rpx 36rpx;
+		margin-bottom: 24rpx;
+
+		.title {
+			font-size: 28rpx;
+			font-weight: bold;
+			color: #2D2D2D;
+			padding-bottom: 20rpx;
+			margin-bottom: 26rpx;
+			border-bottom-style: dashed;
+		}
+
+		.single-til {
+			margin-bottom: 28rpx;
+
+			.text {
+				font-weight: 400;
+			}
+		}
+
+		.no-coupon {
+			height: 116rpx;
+			line-height: 116rpx;
+			text-align: center;
+			background: #F9FAFD;
+			border-radius: 8rpx;
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #C0C0C0;
+		}
+	}
+
+	.notice {
+		background: #FFFFFF;
+		box-shadow: 0rpx 0rpx 16rpx 2rpx rgba(232, 232, 232, 0.5);
+		border-radius: 16rpx;
+		padding: 44rpx 36rpx;
+		margin-bottom: 24rpx;
+
+		.title {
+			font-size: 28rpx;
+			font-weight: bold;
+			color: #2D2D2D;
+			padding-bottom: 20rpx;
+			margin-bottom: 26rpx;
+			border-bottom-style: dashed;
+		}
+
+		.notice-item {
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			line-height: 36rpx;
+			margin-bottom: 8rpx;
+		}
+	}
+
+	.page-bottom {
+		position: relative;
+		z-index: 1001;
+		height: 98rpx;
+		padding: 24rpx 20rpx 50rpx;
+
+		.inner {
+			position: fixed;
+			background-color: #fff;
+			height: 98rpx;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			padding: 24rpx 20rpx 50rpx;
+			box-shadow: 0rpx -4rpx 12rpx 0rpx rgba(215, 215, 215, 0.5);
+
+			.total-price {
+				font-size: 40rpx;
+				font-weight: bold;
+				color: #ED0000;
+			}
+
+			.btn {
+				font-size: 28rpx;
+				height: 80rpx;
+				line-height: 80rpx;
+				border-radius: 50rpx;
+				padding: 0 50rpx;
+				background-color: #eee;
+				color: #333;
+				text-align: center;
+
+				&.active {
+					background: linear-gradient(90deg, #FF7979 0%, #ED0000 100%);
+					color: #fff;
+				}
+			}
+		}
+	}
+
+	.purchaser {
+		.item {
+			margin-bottom: 10rpx;
+			font-size: 26rpx;
+			color: #333;
+		}
+
+		.un-auth {
+			padding: 26rpx;
+			background-color: #eee;
+			color: #333;
+			text-align: center;
+			border-radius: 8rpx;
+		}
+	}
+</style>

+ 178 - 0
pages/chosenposition.vue

@@ -0,0 +1,178 @@
+<template>
+	<view class="pages">
+		<view class="" :style="{height: navHeight+'px' }"></view>
+		<view class="navbar-box">
+			<u-navbar title="选择区域" :safeAreaInsetTop="true" @leftClick="leftClick" :titleStyle="{color:'#fff'}" leftIconColor="#fff" bgColor="#EF1818"></u-navbar>
+		</view>
+		<view class="page-wrap">
+			<view class="base-info">
+				<view class="name">{{pageData.performName}}</view>
+				<view class="time u-flex">
+					<view class="title">{{pageData.day|checkDate}}</view>
+					<text>{{pageData.performTimeStart}} - {{pageData.performTimeEnd}}</text>
+				</view>
+			</view>
+			<view class="position-wrap">
+				<view class="item" :class="{active:positionIndex==index}" :style="{color:positionIndex==index?'#fff':item.seatColor,borderColor:item.seatColor,backgroundColor:positionIndex==index?item.seatColor:'#fff'}" @click="positionClick(index)" v-for="(item,index) in positionArr" :key="index">
+					<!-- <image class="icon" :src="item.icon" ></image> -->
+					<view>{{item.seatTypeName}}</view>
+					<text>¥ {{item.salePrice}}</text>
+				</view>
+			</view>
+			<image class="position-img" :src="positionData.seatImg" mode="widthFix"></image>
+			<view class="btn" @click="book">确定区域</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	export default {
+		mixins: [systemInfo],
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				params:{
+					performId:'',
+				},
+				positionData:{},
+				positionArr:[],
+				positionIndex:0,
+				pageData:{},
+			}
+		},
+		onShow() {
+			
+		},
+		onLoad(page) {
+			// console.log('page',page);
+			this.pageData = page;
+			this.getSystemInfo();
+			this.getPositionData();
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			getPositionData(){
+				let params ={
+					performId:this.pageData.performId,
+					goodsId:this.pageData.goodsId,
+					auditoriumId:this.pageData.auditoriumId,
+					performTimeId:this.pageData.performTimeId,
+				}
+				this.$u.api.selectRegion(params).then(res=>{
+					// console.log('getPositionData',res.data);
+					this.positionData =  res.data;
+					this.positionArr = res.data.regionPriceList;
+				}).catch(err=>{
+					console.log('getPositionData',err);
+				})
+			},
+			book(){
+				if(this.positionArr.length<1){
+					uni.toast('还没有座位信息')
+					return
+				}
+				let seatType = this.positionArr[this.positionIndex];
+				let params ={
+					performId:this.pageData.performId,
+					goodsId:this.pageData.goodsId,
+					auditoriumId:this.pageData.auditoriumId,
+					performTimeId:this.pageData.performTimeId,
+					seatTypeId:seatType.seatTypeId,
+					salePrice:seatType.salePrice
+				}
+				// params.price = this.positionArr[this.positionIndex].price;
+				// params.positionName = this.positionArr[this.positionIndex].positionName;
+				uni.$u.route('pages/bookticket',params)
+			},
+			positionClick(index){
+				this.positionIndex = index;
+			}
+
+		}
+	}
+</script>
+<style>
+page{
+	background-color: #F7F7F9;
+}
+</style>
+<style lang="scss" scoped>
+.page-wrap{
+	padding-top: 36rpx;
+}
+.base-info{
+	background: #FFFFFF;
+	box-shadow: 0rpx 0rpx 20rpx 0rpx rgba(221,221,221,0.4);
+	border-radius: 16rpx;
+	padding: 38rpx 28rpx;
+	margin-bottom: 54rpx;
+	.name{
+		font-size: 28rpx;
+		font-weight: bold;
+		color: #2D2D2D;
+		line-height: 42rpx;
+		margin-bottom: 24rpx;
+	}
+	.time{
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #7F7F7F;
+		.title{
+			font-size: 24rpx;
+			font-weight: 500;
+			color: #F01414;
+			margin-right: 26rpx;
+		}
+	}
+}
+.position-wrap{
+	margin-bottom: 30rpx;
+	display: grid;
+	grid-template-columns: repeat(3, 1fr);
+	gap: 14rpx;
+	.item{
+		// padding: 20rpx 40rpx 14rpx;
+		padding: 20rpx 0;
+		box-sizing: border-box;
+		// flex: 1;
+		border-radius: 8rpx;
+		border: 2rpx solid #D9D9D9;
+		// margin: 0 24rpx 24rpx;
+		font-size: 24rpx;
+		font-weight: 500;
+		color: #EF1D1E;
+		text-align: center;
+		.icon{
+			display: block;
+			width: 36rpx;
+			height: 36rpx;
+			margin-right: 20rpx;
+		}
+		&.active{
+			border-color: #EF1D1E;
+		}
+	}
+}
+.position-img{
+	width: 100%;
+	margin-bottom: 40rpx;
+}
+.btn{
+	height: 92rpx;
+	line-height: 92rpx;
+	background: linear-gradient(90deg, #FF7878 0%, #ED0000 100%);
+	border-radius: 46rpx;
+	text-align: center;
+	font-size: 28rpx;
+	font-weight: 400;
+	color: #FFFFFF;
+}
+</style>

+ 172 - 0
pages/index/index.vue

@@ -0,0 +1,172 @@
+<template>
+	<view class="pages">
+		<view class="page-wrap">
+			<view class="search-wrap">
+				<u-search
+					placeholder="请输入关键词" 
+					:clearabled="true"
+					:showAction="true"
+					height="80rpx"
+					@search="search"
+					@custom="search"
+					@clear="reloadList"
+					bgColor="#EAEAEA"
+					borderColor="#EAEAEA"
+					v-model="params.keyword">
+				</u-search>
+			</view>
+			<view class="star-title u-flex u-row-between">
+				<view class="left u-flex">
+					<image class="icon" :src="staticUrl+'/img/title-star.png'" ></image>
+					<text class="text">演出剧目</text>
+				</view>
+			</view>
+			<swiper class="swiper" :autoplay="true" style="height: 400rpx;margin-bottom: 34rpx" >
+				<swiper-item :item-id="item.id" v-for="(item,index) in theatreList" :key="index">
+					<view class="programme-wrap">
+						<view class="programme">
+							<image class="img" :src="item.showImg" ></image>
+							<!-- <image class="img" :src="item.showImg" ></image> -->
+							<view class="text u-flex u-row-between">
+								<view class="left">
+									<view class="name">{{item.name}}</view>
+									<!-- <view class="addr">演出地点:{{item.address}}</view> -->
+								</view>
+								<view class="btn" @click="bookticket(item)">立即报名</view>
+							</view>
+						<!-- 	<view class="share" @click="getPoster(item)">
+								<image class="icon" :src="staticUrl+'/img/share-ico.png'" ></image>
+							</view> -->
+						</view>
+					</view>
+				</swiper-item>
+			</swiper>
+		</view>
+		<u-toast ref="uToast"></u-toast>
+		<tabbar :tabbarIndexProps='0' />
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	import tabbar from "../../components/tabbar.vue";
+	export default {
+		components:{
+			tabbar,
+			// cartfixed
+		},
+		mixins:[systemInfo],
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				params:{
+					keyword:''
+				},
+				theatreList:[],//剧院信息列表
+			}
+		},
+		computed: {
+		},
+		onShow() {
+			
+		},
+		beforeRouteLeave() {
+			
+		},
+		onLoad(query) {
+			
+			this.getSystemInfo();
+			// console.log('statusBarHeight',this.statusBarHeight);
+			// console.log('navigationBarHeight',this.navigationBarHeight);
+			// console.log('windowHeight',this.windowHeight);
+			// console.log('navHeight',this.navHeight);
+			// console.log('vuex_member_info=======',this.vuex_member_info);
+			this.getClientIndex();
+
+		},
+		onReady() {
+			
+		},
+		onUnload() {
+			
+		},
+		methods: {
+			getMemberInfo(){
+				this.$u.api.memberInfo({id:this.vuex_member_info.id}).then(res=>{
+					this.memberInfo = res.data;
+					this.avatar =  res.data.avatar;
+					this.$u.vuex('vuex_member_info', res.data);
+					// console.log('memberInfo',this.memberInfo);
+					}).catch(err=>{
+					// console.log('memberInfo',err.data);
+				})
+			},
+			getClientIndex(){
+				this.$u.api.clientIndex(this.params).then(res=>{
+					// console.log('res',res);
+					this.theatreList = res.data.performList;
+				}).catch(err=>{
+					console.log('getClientIndex',err.data);
+				})
+			},
+			goLogin(){
+				uni.$u.route('/pages/login/login')
+			},
+			bookticket(item){
+				// console.log('bookticket',item);
+				uni.$u.route('pages/ticketlist',{id:item.id})
+			},
+			search(e){
+				uni.$u.route('pages/searchpage',{keyword:e})
+			},
+			reloadList(){
+				console.log('reloadList');
+			}
+		}
+	}
+</script>
+<style>
+page{
+background-color: #f4f4f4;
+}
+</style>
+<style lang="scss" scoped>
+$pagegap:32rpx;
+.page-wrap{
+	position: relative;
+}
+.search-wrap{
+	background-color: transparent;
+}
+.star-title{
+	margin-bottom: 32rpx;
+	.left{
+		.icon{
+			width: 28rpx;
+			height: 28rpx;
+			margin-right: 8rpx;
+		}
+		.text{
+			font-size: 32rpx;
+			font-weight: bold;
+			color: #FFFFFF;
+			line-height: 48rpx;
+			background: linear-gradient(128deg, #FFEFBC 0%, #FFD767 100%);
+			-webkit-background-clip: text;
+			-webkit-text-fill-color: transparent;
+		}
+	}
+	.right{
+		.text{
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #C79191;
+		}
+	}
+	
+}
+.programme-wrap{
+	// margin-bottom: 64rpx;
+	
+}
+</style>

+ 226 - 0
pages/login/login.vue

@@ -0,0 +1,226 @@
+<template>
+	<view class="body" :style="{height:screenHeight}">
+		<view class="header">
+			<view class="circle circle1"></view>
+			<view class="circle circle2"></view>
+			<view class="circle circle3"></view>
+			<view class="text">
+				<view class="en-text">hello</view>
+				<view class="cn-text">欢迎登录!</view>
+			</view>
+		</view>
+		<view class="login-box">
+			<u--form labelPosition="left" :model="form" :rules="rules" ref="uForm" >
+				<u-form-item label="" prop="account" ref="account" >
+					<u--input
+						v-model="form.account"
+						border="surround"
+						placeholder="输入账号"
+						:customStyle="inputCustomStyle"
+					></u--input>
+				</u-form-item>
+				<u-form-item label="" prop="password" ref="password" >
+					<u--input
+						v-model="form.password"
+						border="surround"
+						:password="true"
+						placeholder="输入密码"
+						:customStyle="inputCustomStyle"
+					></u--input>
+				</u-form-item>
+			</u--form>
+			<u-button 
+				@click="submit"
+				text="登录" 
+				type="primary" 
+				shape="circle" 
+				:customStyle="{'margin-top':'60rpx',height:'98rpx','box-sizing':'border-box'}"
+				color="linear-gradient(90deg, #00D17D 0%, #00A447 100%)">
+			</u-button>
+		</view>
+		<view class="tip">
+			提示:初始账号和密码由管理员设置,请与管理人员联系
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				bname:'旭烁集团',
+				//屏幕高度
+				screenHeight: "",
+				backUrl:null,
+				form:{
+					account:'',
+					password:''
+				},
+				rules: {
+					account: {
+						type: 'string',
+						required: true,
+						message: '请填写账号',
+						trigger: ['blur', 'change']
+					},
+					password: {
+						type: 'string',
+						required: true,
+						message: '请填写密码',
+						trigger: ['blur', 'change']
+					},
+				},
+				inputCustomStyle:{
+					height:'98rpx',
+					'border-color':'#eee',
+					'padding-left':'30rpx',
+					'box-sizing':'border-box',
+				}
+			};
+		},
+		onLoad() {
+			// 测试环境填充用户名密码
+			if(process.env.NODE_ENV=='development'){
+				this.form.account = 'dls1234';
+				this.form.password = '654321';
+			}
+			let that = this;
+			uni.getSystemInfo({
+				success: (res) => {
+					this.screenHeight = res.windowHeight + "px"
+				}
+			});
+			uni.getStorage({
+				key: 'backUrl',
+				success: function (res) {
+					console.log('getStorage',res);
+				},
+				complete(res) {
+					if(res.data){
+						that.backUrl = '/'+res.data;
+					}else{
+						that.backUrl = '/pages/index/index';
+					}
+					console.log('backUrl',that.backUrl);
+				}
+			});
+		},
+		onReady() {
+			//onReady 为uni-app支持的生命周期之一
+			this.$refs.uForm.setRules(this.rules)
+		},
+		onShow() {
+		},
+		methods: {
+			submit(){
+				console.log('form',this.form);
+				this.$refs.uForm.validate().then(res => {
+					// uni.$u.toast('校验通过')
+					this.$u.api.login(this.form).then(res=>{
+						// console.log('res',res.data);
+						this.$u.vuex('vuex_user_info', res.data);
+						uni.reLaunch({url: this.backUrl});
+					}).catch(err=>{
+						console.log('login',err);
+					})
+				}).catch(errors => {
+					uni.$u.toast('请正确填写表单')
+				})
+			}
+			
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+.body {
+	background-color: #fff;
+	box-sizing: border-box;
+}
+.header{
+	position: relative;
+	height: 368rpx;
+	overflow: hidden;
+	background: linear-gradient(325deg, #FCFFFD 0%, #ECFFF4 100%);
+	.circle{
+		position: absolute;
+		border-radius: 50%;
+		&.circle1{
+			width: 201rpx;
+			height: 149rpx;
+			background: linear-gradient(230deg, #E5FFEC 0%, #C0FFD7 100%);
+			left:-20rpx;
+			bottom: -50rpx;
+		}
+		&.circle2{
+			width: 148rpx;
+			height: 148rpx;
+			background: linear-gradient(215deg, #E7FFEE 0%, #BFFFDB 100%);
+			opacity: 0.6;
+			right:158rpx;
+			top: 80rpx;
+		}
+		&.circle3{
+			width: 232rpx;
+			height: 237rpx;
+			background: linear-gradient(215deg, #F1FFF8 0%, #C3FFD3 100%);
+			right:-32rpx;
+			top: -65rpx;
+		}
+	}
+	.text{
+		position: absolute;
+		left: 40rpx;
+		bottom: 80rpx;
+		.en-text{
+			position: relative;
+			text-transform: uppercase;
+			font-size: 58rpx;
+			font-family: AlibabaPuHuiTi_2_115_Black;
+			color: #333333;
+			line-height: 81rpx;
+			font-weight: bold;
+			width: fit-content;
+			z-index: 5;
+			&::before{
+				content: '';
+				display: block;
+				width: 104%;
+				height: 10rpx;
+				background-color: #FFB100;
+				position: absolute;
+				left: -2%;
+				bottom: 10rpx;
+				z-index: -1;
+			}
+		}
+		.cn-text{
+			font-size: 50rpx;
+			font-family: PingFangSC-Regular, PingFang SC;
+			font-weight: 400;
+			color: #333333;
+			line-height: 70rpx;
+		}
+	}
+}
+.login-box{
+	position: relative;
+	top: -24rpx;
+	padding: 60rpx 40rpx;
+	border-top-left-radius: 24rpx;
+	border-top-right-radius: 24rpx;
+	background-color: #fff;
+	// box-shadow: 0rpx 0rpx 16rpx 0rpx rgba(0,28,12,0.06);
+}
+.tip{
+	position: fixed;
+	bottom: 108rpx;
+	width: 100vw;
+	box-sizing: border-box;
+	text-align: center;
+	font-size: 24rpx;
+	font-weight: 400;
+	color: #999999;
+}
+</style>
+

+ 332 - 0
pages/login/loginh5.vue

@@ -0,0 +1,332 @@
+<template>
+	<view class="body" :style="{height:screenHeight}">
+		<image class="login-bg" :src="staticUrl+'/img/login-bg.png'" mode="widthFix"></image>
+		<u-toast ref="uToast"></u-toast>
+		<view style="height: 40%;position: relative;z-index: 10;">
+			<view class="logo-wrap">
+				<img :src="logoSrc" class="logo" alt="">
+			</view>
+			<view class="btn-wrap" style="margin:94rpx">
+				<u-button
+					:hair-line='false' 
+					type="error" 
+					color="#ED0000"
+					@click="disabledClick"
+					shape="circle">登录
+				</u-button>
+			</view>
+			<view class="rule-wrap u-flex u-flex-wrap u-row-center">
+				<u-checkbox-group v-model="checked" @change="checkboxChange" placement="row">
+					<u-checkbox activeColor="#1677FF" name="同意" labelSize="24rpx" shape="circle" label="我已阅读并同意"></u-checkbox>
+				</u-checkbox-group>
+				<text class="link" @click="$u.route('/pages/login/regulation',{regulationName:'用户服务协议',type:1})">《用户服务协议》</text>
+				和<text class="link" @click="$u.route('/pages/login/regulation',{regulationName:'用户隐私政策',type:2})">《用户隐私政策》</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				checked:false,
+				checkboxVal:null,
+				loginBtn:true,
+				bname:'旭烁集团',
+				//屏幕高度
+				screenHeight: "",
+				logoSrc: "/static/logo.png",
+				// sitename:"/static/sitename.png",
+				miniappLoginInfo:null,
+				hasUserInfo:false,
+				userInfo: null,
+				user:{   
+					avatar:'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
+					name:'',
+					mobile:'',
+				},
+				code: '',
+				showAuthorizeUser: false,
+				showAuthorizePhone: false,
+				customStyleUnOk:{},
+				customStyleOk:{'margin-left': '30px',color:'#00A447'},
+				backUrl:null,
+				scene:'',
+			};
+		},
+		onLoad(e) {
+			console.log("回调参数=======",e)
+			if(e&&e.code) { // 微信第三方登录成功
+				this.code = e.code
+				this.login(e)
+			}
+			let that = this;
+			//获取屏幕高度,我的项目再store里已经取到了
+			uni.getSystemInfo({
+				success: (res) => {
+					this.screenHeight = res.windowHeight + "px"
+				}
+			});
+			uni.getStorage({
+				key: 'backUrl',
+				success: function (res) {
+					that.backUrl = '/'+res.data;
+				}
+			});
+		},
+		onShow() {
+			let pages = getCurrentPages(); //当前页面栈
+			console.log("pages=====",pages)
+			let userInfo = uni.getStorageSync('userInfo');
+			this.user.name = userInfo.name;
+		},
+		methods: {
+			openAuth(){
+				this.showAuthorizePhone = true;
+			},
+			//获取昵称输入内容
+			// userNameInput(e){
+			//     this.user.nickName = e.detail.value
+			// },
+			onChooseAvatar(e) {
+				console.log('头像信息》')
+				console.log(e)
+				this.user.avatar = e.detail.avatarUrl;
+			},
+			async login(e){
+				// console.log('e',e);
+				let _this = this;
+				let wxinfo = await this.$u.api.wxinfoH5({code:this.code});
+				let {openid,nickname} = wxinfo.data;
+
+				// console.log('----------登陆中',data)
+				this.$u.api.login({
+					"openId": "",
+					"h5OpenId":"openid",
+					"mobile": ""
+				}).then(res=>{
+					// console.log('微信登录返回结果========',res.data)
+					_this.$u.vuex('vuex_user_info', res.data);
+					// _this.$u.vuex('vuex_member_info',res.data);
+					// uni.setStorageSync('userInfo', res.data)
+					this.showAuthorizePhone = false;
+					uni.removeStorage({
+						key:'scene'
+					});
+					// console.log('res.data.userid',res.data.userid);
+					this.getMemberInfo(res.data.userId);
+					
+				}).catch(err=>{
+					console.log('err',err);
+					this.showAuthorizePhone = false
+				})
+			},
+			/**
+			 * @author ygh
+			 * @date 2023-12-14
+			 * @param {Object} userid
+			 * 根据userId 获取用户信息
+			 */
+			getMemberInfo(userid){
+				// console.log('userid',userid);
+				let _this = this;
+				this.$u.api.memberInfo({id:userid}).then(res=> {
+					if(res.code ===200) {
+						_this.userInfo = res.data;
+						this.$u.vuex('vuex_member_info', res.data);
+						// this.$u.vuex('vuex_member_info.avatar', 'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0');
+						if(!res.data.name){
+							this.$u.vuex('vuex_member_info.name', '微信用户');
+							this.updateMemberInfo();
+						}else{
+							this.goBack();
+						}
+					}
+				})
+			},
+			/**
+			 * @author ygh 
+			 * @date 2023-12-14
+			 * 更新用户信息
+			 */
+			updateMemberInfo(){
+				let params ={
+					id:this.userInfo.id,
+					// avatar:'https://mmbiz.qpic.cn/mmbiz/icTdbqWNOwNRna42FI242Lcia07jQodd2FJGIYQfG0LAJGFxM4FbnQP6yfMxBgJ0F3YRqJCJ1aPAK2dQagdusBZg/0',
+					name:'微信用户'
+				};
+				this.$u.api.updateMemberInfo(params).then(res=>{
+					this.goBack();
+				}).catch(err=>{
+					console.log('err',err);
+				})
+			},
+			goBack(){
+				let url = this.backUrl&&this.backUrl.length>0?this.backUrl:'/pages/index/index';
+				// console.log('url',url);
+				uni.removeStorage({
+					key: 'backUrl',
+					success: function (res) {
+						// console.log('success');
+						uni.reLaunch({url: decodeURIComponent(url)});
+					}
+				});
+			},
+			checkboxChange(e){
+				this.checkboxVal = e[0];
+			},
+			disabledClick(){
+				console.log("this.checked====",this.checked)
+				// console.log('checked',this.checked?.length);
+				// console.log('loginBtn',this.loginBtn);
+				if(!this.checked||this.checked?.length<=0){
+					uni.showToast({
+						title:'请先同意使用条款!',
+						icon:'none'
+					})
+				}else{
+					uni.showToast({
+						title:'登录中!',
+						icon:'none'
+					})
+					this.redirectToAuth()
+				}
+			},
+			/**  微信授权登录  */
+			redirectToAuth() {
+			    const appid = 'wx6490eaa0d20d2be2';
+			    const redirectUri = encodeURIComponent('https://greath5.dev.gztjy.top/pages/login/loginh5');
+			    const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirectUri}&response_type=code&scope=snsapi_base&state=STATE#wechat_redirect`;
+			 
+			    window.location.href = authUrl;
+			},
+			
+			getAccessToken() {
+				const code = (new URLSearchParams(window.location.search)).get('code');
+				if (code) {
+				    const appid = 'wx6490eaa0d20d2be2';
+				    const requestUrl = `https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=YOUR_APP_SECRET&code=${code}&grant_type=authorization_code`;
+				    fetch(requestUrl)
+				    .then(response => response.json())
+				    .then(data => {
+				        if (data && data.openid) {
+				            const openid = data.openid;
+				            // 在这里可以存储或使用用户的openid
+				            // ...
+				        }
+				    })
+				    .catch(error => {
+				        console.error(error);
+				    });
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+.login-bg{
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 0;
+	width: 100%;
+}
+.body {
+	background-color: #fff;
+	box-sizing: border-box;
+	text-align: center;
+	.logo-wrap {
+		padding-top: 248rpx;
+		img{
+			display: block;
+			margin: 0 auto;
+		}
+		.logo{
+			width: 306rpx;
+			height: 258rpx;
+		}
+		.sitename{
+			width: 460rpx;
+			height: 142rpx;
+		}
+	}
+}
+
+// 
+
+.auth-btncard{
+	margin-top: 20px;
+    .btn-unok{
+        width: 45%;
+        float: left;
+    }
+    .btn-ok{
+        width: 45%;
+        float: left;
+        margin: 0;
+        padding: 0;
+        border: 0px solid transparent;  //自定义边框
+        outline: none;    //消除默认点击蓝色边框效果
+        u-button{
+			font-size: 16px;
+            margin: 0;
+            padding: 0;
+            // border: 0px solid transparent;  //自定义边框
+            outline: none;    //消除默认点击蓝色边框效果
+        }
+    }
+}
+.auth-card{
+    text-align: center;
+    .avatar-img{
+        width: 150rpx;
+        height: 150rpx;
+        overflow: hidden;
+        border-radius: 100%;
+        margin-top: 30rpx;
+    }
+    .title{
+        font-size: 30rpx;
+    }
+    .content{
+        margin-top: 10rpx;
+		margin-bottom: 20rpx;
+    }
+}
+.avatar-wrapper{
+	width: 150rpx;
+	height: 100rpx;
+	color: #333 !important;
+	text-align: center !important;
+	border: none !important;
+	border-radius:0 !important;
+	background-color:transparent !important;
+}
+.avatar-wrapper::after {
+	border: none !important;
+}
+.avatar{
+	width: 100rpx;
+	height: 100rpx;
+	overflow: hidden;
+	border-radius: 100%;
+}
+
+.rule-wrap{
+	position: fixed;
+	left: 0;
+	right: 0;
+	bottom: 20px;
+	margin: 40rpx auto;
+	font-size: 24rpx;
+	line-height: 1.5;
+	.link{
+		white-space: nowrap;
+		color: #1677FF;
+	}
+}
+</style>
+

+ 60 - 0
pages/login/regulation.vue

@@ -0,0 +1,60 @@
+<template>
+	<view class="">
+		<u-navbar
+			:title="title"
+			:placeholder="true"
+			:autoBack="true"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+		<!-- <view class="title">积分规则</view> -->
+		<view class="page-wrap parse-content">
+			<u-parse :content="content"></u-parse>
+		</view>
+		
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				title:'',
+				regulationName:'',
+				content:'',
+				type:'',
+			}
+		},
+		onShow() {	
+			
+		},
+		onLoad(page) {
+			this.title = page.regulationName;;
+			this.regulationName = page.regulationName;
+			this.type = page.type;
+			// console.log('page',page);
+			this.getPageData();
+		},
+		methods: {
+			getPageData(){
+				this.$u.api.getAgreement({type:this.type}).then(res=>{
+					this.content = res.data.content
+					console.log('res',res.data);
+				}).catch(err=>{
+					console.log('memberCreditDesc',err);
+				})
+			}
+
+		}
+	}
+</script>
+<style>
+page{
+	background-color: #f5f5f5;
+}
+</style>
+<style lang="scss" scoped>
+.title{
+	text-align: center;
+}
+</style>

+ 230 - 0
pages/news.vue

@@ -0,0 +1,230 @@
+<template>
+	<view class="pages">
+		<u-navbar
+			title="演出资讯"
+			:placeholder="true"
+			:autoBack="false"
+			 @leftClick="leftClick"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+		<view class="search-wrap">
+			<u-search
+				placeholder="请输入关键词" 
+				:clearabled="true"
+				:showAction="true"
+				height="80rpx"
+				@search="search"
+				@custom="search"
+				@clear="reloadList"
+				bgColor="#EAEAEA"
+				borderColor="#EAEAEA"
+				v-model="params.keyword">
+			</u-search>
+		</view>
+		<view class="page-wrap">
+			<mescroll-body class="" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :up="upOption">
+				<!-- :down="downOption" :up="upOption" -->
+				<view class="list">
+					<view v-for="(item,index) in dataList" class="item u-flex" :class="{ show: show }" @click="itemClick(item)" :key="item.id">
+						<view class="text">
+							<view class="title u-line-1">{{ item.title }}</view>
+							<view class="con u-line-2" v-html="item.centent"></view>
+							<view class="time">{{ $u.timeFormat(item.onlineTime, 'yyyy-mm-dd') }}</view>
+						</view>
+						<image class="img" :src="item.mainImg" mode=""></image>
+					</view>
+				</view>
+			</mescroll-body>
+		</view>
+	</view>
+</template>
+
+<script>
+	// 引入mescroll-mixins.js
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		components: {
+			
+		},
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				// // 下拉刷新的配置(可选, 绝大部分情况无需配置)
+				// downOption: {
+				// },
+				// // 上拉加载的配置(可选, 绝大部分情况无需配置)
+				upOption: {
+					page: {
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 5, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					empty: {
+						tip: '暂无相关资讯,请重新搜索'
+					}
+				},
+				params:{
+					keyword:''
+				},
+				dataList:[],
+				observer: null,
+				show:false,
+				
+			}
+		},
+		onShow() {
+		},
+		onLoad() {
+			this.observer = uni.createIntersectionObserver(this,{observeAll: true});
+		},
+		onReady() {
+			this.observer.relativeToViewport().observe('.item', res => {
+			if (res.intersectionRatio > 0) {
+				console.log('resres',res);
+			  // 当元素出现在视口时执行动画
+			  // this.showAnimation(res);
+			  this.show = true;
+			}
+		  })
+		},
+		onUnload() {
+			if (this.observer) {
+				this.observer.disconnect()
+			}
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			/*下拉刷新的回调, 重置列表为第一页 (此处可删,mixins已默认)
+			downCallback(){
+				this.mescroll.resetUpScroll();
+			},
+			/*上拉加载的回调*/
+			upCallback(page) {
+				// 此处可以继续请求其他接口
+				// if(page.num == 1){
+				// 	// 请求其他接口...
+				// }
+			
+				// 如果希望先请求其他接口,再触发upCallback,可参考以下写法
+				// if(!this.hasTypeId){
+				// 	// this.getid();
+				// 	this.mescroll.endErr();//没有接口暂时不调用
+				// 	return // 此处return,先获取xx
+				// }
+			
+				let pageNum = page.num; // 页码, 默认从1开始
+				let pageSize = page.size; // 页长, 默认每页10条
+			
+				let params = {
+					pageNum : page.num,
+					pageSize :  page.size,
+					title:this.params.keyword,
+					status:1
+				}
+				// console.log('this.params',params);
+				this.$u.api.newsList(params).then(data => {
+					this.hasfetch = true;
+					// console.log('data',JSON.parse(JSON.stringify(data)));
+					// 接口返回的当前页数据列表 (数组)
+					let curPageData = data.data.rows;
+					// console.log('curPageData',JSON.parse(JSON.stringify(curPageData)));
+					// 接口返回的当前页数据长度 (如列表有26个数据,当前页返回8个,则curPageLen=8)
+					let curPageLen = curPageData.length; 
+					// 接口返回的总页数 (如列表有26个数据,每页10条,共3页; 则totalPage=3)
+					// let totalPage =  data.data.data.totalPage; 
+					// 接口返回的总数据量(如列表有26个数据,每页10条,共3页; 则totalSize=26)
+					let totalSize = data.data.total; 
+					// 接口返回的是否有下一页 (true/false)
+					// let hasNext = data.xxx; 
+					// console.log('totalPage',totalPage,'curPageLen',curPageLen);
+					//设置列表数据
+					if(page.num == 1) this.dataList = []; //如果是第一页需手动置空列表
+					this.dataList = this.dataList.concat(curPageData); //追加新数据
+					// 请求成功,隐藏加载状态
+					//方法一(推荐): 后台接口有返回列表的总页数 totalPage
+					// this.mescroll.endByPage(curPageLen, totalPage); 
+					//方法二(推荐): 后台接口有返回列表的总数据量 totalSize
+					this.mescroll.endBySize(curPageLen, totalSize); 
+				}).catch(err => {
+					this.mescroll.endErr()
+					console.log(err)
+				});	
+			
+			},
+			/*若希望重新加载列表,只需调用此方法即可(内部会自动page.num=1,再主动触发up.callback)*/
+			reloadList() {
+				this.mescroll.resetUpScroll();
+			},
+			search(e){
+				this.reloadList();
+			},
+			itemClick(item){
+				// console.log('itemClick',item);
+				uni.$u.route('/pages/newsdetails', {
+					id: item.id,
+					type:'news'
+				});
+			}
+		}
+	}
+</script>
+<style>
+	page{
+		background-color: #F9FAFD;
+	}
+</style>
+<style lang="scss" scoped>
+.search-wrap{
+	padding: 20rpx 34rpx;
+}
+.list{
+	.item{
+		&.show{
+			animation: slide-top 0.5s cubic-bezier(0.250, 0.460, 0.450, 0.940) both;
+		}
+		background-color: #fff;
+		border-radius: 24rpx;
+		margin-bottom: 24rpx;
+		overflow: hidden;
+		.img{
+			border-radius: 12rpx;
+			width: 220rpx;
+			height: 200rpx;
+		}
+		.text{
+			height: 200rpx;
+			box-sizing: border-box;
+			padding:24rpx 28rpx;
+			// flex: 1;
+			width: calc( 100% - 220rpx );
+			.title{
+				font-size: 28rpx;
+				font-weight: bold;
+				color: #333333;
+				margin-bottom: 16rpx;
+			}
+			.con{
+				font-size: 24rpx;
+				font-weight: 400;
+				color: #6B6B6B;
+				line-height: 36rpx;
+				margin-bottom: 20rpx;
+			}
+			.time{
+				font-size: 20rpx;
+				font-weight: 400;
+				color: #C0C0C0;
+				line-height: 22rpx;
+			}
+		}
+	}
+}
+</style>

+ 127 - 0
pages/newsdetails.vue

@@ -0,0 +1,127 @@
+<template>
+	<view class="pages">
+		<view class="" :style="{height: navHeight+'px' }"></view>
+		<view class="navbar-box">
+			<u-navbar title="资讯详情" :safeAreaInsetTop="true" @leftClick="leftClick" :titleStyle="{color:'#000'}" leftIconColor="#000" bgColor="transparent"></u-navbar>
+		</view>
+		<view class="banner">
+			<image class="img" :src="details.mainImg||staticUrl+'/img/newsdetails-banner.png'" alt="">
+		</view>
+		<view class="content-wrap">
+			<view class="title">{{details.title}}</view>
+			<view class="time">{{ $u.timeFormat(details.onlineTime, 'yyyy-mm-dd') }}</view>
+			<view class="centent parse-content">
+				<u-parse :content="details.centent"></u-parse>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import { systemInfo } from "@/mixin.js";
+	export default {
+		mixins:[systemInfo],
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				id:'',
+				details:{},
+			}
+		},
+		onShow() {
+		},
+		onLoad(page) {
+			let that = this;
+			this.getSystemInfo();
+			this.id = page.id;
+			this.type = page.type;
+			if(this.type=='swiperDetail'){
+				// console.log('this.id',this.id);
+				this.getSwiperDetails(this.id);
+			}else{
+				this.getDetails(this.id);
+				setTimeout(()=>{
+					that.addReadRecord()
+				},2000);
+			}
+			// this.getDetails();
+			
+
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			getSwiperDetails(id){
+				this.$u.api.swiperDetails({id:id}).then(res=>{
+					this.swiperDetails = res.data;
+					// console.log('res',res);
+					}).catch(err=>{
+					console.log('getSwiperDetails',err.data);
+				})
+			},
+			getDetails(){
+				this.$u.api.newsdetails({id:this.id}).then(res=>{
+					this.details = res.data;
+					// console.log('res',res.data);
+				}).catch(err=>{
+					console.log('getDetails',err);
+				})
+			},
+			addReadRecord(){
+				this.$u.api.addReadRecord({keyId:this.id, accessedPage: "小程序",remark:'资讯浏览'},{custom:{noload:true}}).then(res=>{
+					// console.log('res',res.data);
+				}).catch(err=>{
+					console.log('getDetails',err);
+				})
+			}
+
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+.banner{
+	.img{
+		display: block;
+		width: 100%;
+		height: 400rpx;
+	}
+}
+.content-wrap{
+	padding: 65rpx 32rpx;
+	background: #FFFFFF;
+	border-radius: 20rpx 20rpx 0rpx 0rpx;
+	transform: translateY(-20rpx);
+	.title{
+		font-size: 32rpx;
+		font-weight: bold;
+		color: #333333;
+		// line-height: 22rpx;
+		margin-bottom: 30rpx;
+	}
+	.time{
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #C0C0C0;
+		line-height: 22rpx;
+		margin-bottom: 40rpx;
+	}
+	.centent{
+		font-size: 28rpx;
+		font-weight: 400;
+		color: #6B6B6B;
+		line-height: 44rpx;
+		img,image{
+			width: 100%;
+			display: block;
+			margin: 10rpx auto;
+		}
+	}
+}
+</style>

+ 226 - 0
pages/searchpage.vue

@@ -0,0 +1,226 @@
+<template>
+	<view class="">
+		<u-navbar
+			:title="title"
+			:placeholder="true"
+			:autoBack="false"
+			@leftClick="leftClick"
+			 :safeAreaInsetTop="true"
+		>
+		</u-navbar>
+		<view class="search-wrap">
+			<u-search
+				placeholder="请输入关键词" 
+				:clearabled="true"
+				:showAction="false"
+				height="80rpx"
+				@search="search"
+				@custom="search"
+				@clear="reloadList"
+				bgColor="#fff"
+				borderColor="#ee0808"
+				v-model="params.goodsName">
+			</u-search>
+		</view>
+	<!-- 	<view class="search-history page-wrap" v-if="searchHistory.length>0">
+			<view class="search-title u-flex u-row-between">
+				<text>最近搜索</text>
+				<u-icon @click="clearStorage" name="trash" color="#666" size="35"></u-icon>
+			</view>
+			<view class="history-wrap">
+				<text class="history" v-for="(item,index) in searchHistory" :key="index">
+					{{item.name}}
+				</text>
+			</view>
+		</view> -->
+		<mescroll-body class="" ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback" :down="downOption" :up="upOption">
+			<view class="page-wrap" >
+				<view class="" v-for="item in dataList" :key="item.id" @click="$u.route('/shopping/productdetails',{id:item.id})">
+					<view class="programme">
+						<image class="img" :src="item.showImg" ></image>
+						<view class="text u-flex u-row-between">
+							<view class="left">
+								<view class="name">{{item.name}}</view>
+							</view>
+							<view class="btn" @click="bookticket(item)">立即报名</view>
+						</view>
+					</view>
+				</view>
+			</view>
+		</mescroll-body>
+		<u-toast ref="uToast"></u-toast>
+	</view>
+</template>
+
+<script>
+	// 引入mescroll-mixins.js
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	export default {
+		mixins: [MescrollMixin], // 使用mixin
+		components: {
+			
+		},
+		data() {
+			return {
+				staticUrl:this.$commonConfig.staticUrl,
+				downOption: {},
+				// 上拉加载的配置(可选, 绝大部分情况无需配置)
+				upOption: {
+					page: {
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 3, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					empty: {
+						tip: '暂无相关数据'
+					}
+				},
+				title:'搜索',
+				params:{
+					goodsName:'',
+					// isExplode:0,//是否爆款 0不是,1是
+				},
+				// 列表数据
+				dataList: [],
+				searchHistory:[],
+			}
+		},
+		onLoad(page) {
+			let that = this;
+			this.params.goodsName = page.goodsName;
+			uni.getStorage({
+				key:'searchgoodshistory',
+				 success: function(storagedata) {
+					 that.searchHistory = storagedata.data;
+					 console.log('storagedata',storagedata.data);
+				 }
+			})
+		},
+		onShow() {
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			/*下拉刷新的回调, 重置列表为第一页 (此处可删,mixins已默认)
+			downCallback(){
+				this.mescroll.resetUpScroll();
+			},
+			/*上拉加载的回调*/
+			upCallback(page) {
+				let that = this;
+				// 此处可以继续请求其他接口
+				// if(page.num == 1){
+				// 	// 请求其他接口...
+				// }
+				let pageNum = page.num; // 页码, 默认从1开始
+				let pageSize = page.size; // 页长, 默认每页10条
+				this.params = Object.assign(this.params,{pageNum:pageNum,pageSize:pageSize});
+				console.log('this.params',this.params);
+				this.$u.api.clientIndex(this.params).then(data => {
+					console.log('data',JSON.parse(JSON.stringify(data)));
+					// 接口返回的当前页数据列表 (数组)
+					let curPageData = data.data.performList;
+					console.log('curPageData',JSON.parse(JSON.stringify(curPageData)));
+					// 接口返回的当前页数据长度 (如列表有26个数据,当前页返回8个,则curPageLen=8)
+					let curPageLen = curPageData.length; 
+					// 接口返回的总页数 (如列表有26个数据,每页10条,共3页; 则totalPage=3)
+					// let totalPage =  data.data.data.totalPage; 
+					// 接口返回的总数据量(如列表有26个数据,每页10条,共3页; 则totalSize=26)
+					let totalSize = data.data.total; 
+					// 接口返回的是否有下一页 (true/false)
+					// let hasNext = data.xxx; 
+					// console.log('totalPage',totalPage,'curPageLen',curPageLen);
+					//设置列表数据
+					if(page.num == 1) this.dataList = []; //如果是第一页需手动置空列表
+					this.dataList = this.dataList.concat(curPageData); //追加新数据
+					// 请求成功,隐藏加载状态
+					//方法一(推荐): 后台接口有返回列表的总页数 totalPage
+					// this.mescroll.endByPage(curPageLen, totalPage); 
+					//方法二(推荐): 后台接口有返回列表的总数据量 totalSize
+					this.mescroll.endBySize(curPageLen, totalSize); 
+				}).catch(err => {
+					this.mescroll.endErr()
+					console.log(err)
+				});	
+				
+				// 存储历史数据
+				let index = this.searchHistory.findIndex(item => item.name === that.params.goodsName);
+				// console.log('index',index);
+				const name = that.params.goodsName;
+				if (!name || name.length < 1) {
+				  return;
+				}
+				if (index !== -1) {
+				  // 已经存在,不用重复添加了
+				  return;
+				}
+				this.searchHistory.push({ name: that.params.goodsName });
+				uni.setStorage({
+				  key: 'searchgoodshistory',
+				  data: that.searchHistory,
+				  success: function() {
+				    // console.log('数据存储成功')
+				  },
+				  fail: function() {
+				    // console.log('数据存储失败')
+				  }
+				});
+			},
+			/*若希望重新加载列表,只需调用此方法即可(内部会自动page.num=1,再主动触发up.callback)*/
+			reloadList() {
+				this.mescroll.resetUpScroll();
+			},
+			search(e){
+				this.mescroll.resetUpScroll();
+				// this.reloadList();
+				console.log('search',e)
+			},
+			clearStorage(){
+				let that = this;
+				uni.clearStorage({
+					key:'searchgoodshistory',
+					success:function(){
+						that.searchHistory = []
+					}
+				})
+			}
+		}
+	}
+</script>
+<style>
+page{
+background-color: #f4f4f4;
+}
+</style>
+<style lang="scss" scoped>
+.search-history{
+	color: #333333;
+	margin-bottom: 30rpx;
+	.search-title{
+		font-size: 32rpx;
+		font-weight: 600;
+		line-height: 45rpx;
+		margin-bottom: 20rpx;
+	}
+	.history{
+		display: inline-block;
+		height: 48rpx;
+		line-height: 48rpx;
+		background: #F5F5F5;
+		border-radius: 24rpx;
+		padding: 0 24rpx;
+		min-width: 100rpx;
+		margin-left: 20rpx;
+		margin-bottom: 20rpx;
+		text-align: center;
+	}
+}
+.programme{
+	margin-bottom: 24rpx;
+}
+</style>

+ 888 - 0
pages/ticketlist.vue

@@ -0,0 +1,888 @@
+<template>
+	<view class="pages">
+		<view class="navbar-box">
+			<u-navbar title="票务购买" :safeAreaInsetTop="true" @leftClick="leftClick" :titleStyle="{color:'#fff'}" leftIconColor="#fff" bgColor="transparent"></u-navbar>
+		</view>
+		<view class="banner">
+			<!-- <image class="img" :src="staticUrl+'/img/bookticket-banner.png'" alt=""> -->
+<!-- 			<image class="img" :src="theatre.showImg" alt=""> -->
+			<u-swiper
+				:list="performInfo.photoList"
+				height="554rpx"
+				indicatorMode="dot"
+				:indicatorStyle="{bottom:'100rpx'}"
+				keyName="imageUrl"
+				radius="0"
+				:indicator="false"
+				circular
+			></u-swiper>
+			<view class="content">
+				<view class="share" @click="openShare(item)">
+					<image class="icon" :src="staticUrl+'/img/share-ico.png'" ></image>
+				</view>
+			</view>
+		</view>
+		<view class="base-info">
+			<view class="inner">
+				<view class="name">{{performInfo.name}}</view>
+				<view class="addr u-flex u-row-between">
+					<view class="text">演出地址:{{theatre.address||''}}</view>
+					<view class="menu u-flex">
+						<view class="item" @click="goMap">
+							<image class="icon" :src="staticUrl+'/img/map-ico.png'" ></image>
+							<text>导航</text>
+						</view>
+						<view class="item"  @click="phoneCall">
+							<image class="icon" :src="staticUrl+'/img/phone-call-ico.png'" ></image>
+							<text>电话</text>
+						</view>
+					<!-- 	<u-icon name="arrow-right" color="#ffffff" size="36rpx" @click="goMap"></u-icon>
+						<u-icon name="arrow-right" color="#ffffff" size="36rpx" @click="goMap"></u-icon> -->
+					</view>
+				</view>
+			</view>
+		</view>
+		<view class="tabs-wrap">
+			<view class="inner">
+				<u-tabs
+					:list="tabsArr"  
+					@click="tabsClick"
+					lineColor="#ED0000" 
+					lineWidth="82rpx"
+					:activeStyle="{color: '#2D2D2D',fontWeight: 'bold'}"
+					itemStyle="width:33%; height: 46px;box-sizing:border-box"
+				>
+				</u-tabs>
+			</view>
+		</view>
+		<view class="page-wrap">
+			<view class="ticket" v-if="tabsIndex==0">
+				<view class="date-block">
+					<view class="title">演出日期</view>
+					<view class="date-list u-flex">
+						<view class="date-item" 
+						:class="{active:dateIndex==index,dot:performDateList.find(item => item.performDate === `${date.year}-${date.month}-${date.day}`) !== undefined}" 
+						@click="dateClick(index)" 
+						v-for="(date,index) in dateList" :key="index">
+							<!-- <view class="name">{{ date.name || ' ' }}</view> -->
+						<!-- 	<view class="name">
+								{{ performDateList.find(item => item.performDate === date.fullDay ) !== undefined?'有演出':'无演出' }}
+							</view> -->
+							<view class="name">
+								{{date.fullDay|checkWeekDate}}
+							</view>
+							<view class="date">{{ date.month }} - {{ date.day }}</view>
+							<image class="selected-img" :src="staticUrl+'/img/selected.png'"></image>
+						</view>
+						<view class="date-item more-date u-flex u-row-center" @click="calendarShow = true">
+							<view class="text">
+								<view class="">更多</view>
+								<view class="">日期</view>
+							</view>
+							<u-icon name="arrow-right" color="#7F7F7F" size="24rpx"></u-icon>
+						</view>
+					</view>
+				</view>
+				<view class="date-block auditorium">
+					<view class="title">演出厅</view>
+					<view class="date-list u-flex u-flex-wrap">
+						<view class="date-item" :class="{active:auditoriumIndex==index}" @click="auditoriumClick(index)" v-for="(date,index) in auditoriumList" :key="index">
+							<view class="name">{{ date.name }}</view>
+						</view>
+					</view>
+				</view>
+				<view class="date-block session-wrap">
+					<view class="title">演出场次</view>
+					<view class="session">
+						<view class="session-item" :class="{active:sessionIndex==index}" @click="sessionClick(index)" v-for="(date,index) in sessionList" :key="index">
+							{{ date.performTimeStart}} - {{date.performTimeEnd}}
+						</view>
+					</view>
+					<view class="empty" v-if="auditoriumList.length>=1&&sessionList.length<1">
+						当前日期暂无演出场次,请重新选择
+					</view>
+				</view>
+				<view class="date-block ticket-type">
+					<view class="title">门票</view>
+					<view class="empty" v-if="ticketTypeList.length<1">
+						暂无门票
+					</view>
+					<view class="type-item" :class="{active:sessionIndex==index}" v-for="(item,index) in  ticketTypeList" :key="index">
+						<view class="name-price u-flex u-row-between">
+							<view class="name">{{item.goodsName}}</view>
+							<view class="prices">¥ {{item.salePrice}} 起</view>
+						</view>
+						<!-- <view class="ishave">
+							<text class="text">{{(item.quantity>0&&sessionList.length>=1)?'有票':'无票'}}</text>
+						</view> -->
+						<view class="bottom u-flex u-row-between">
+							<view class="left u-flex" v-html="item.goodsSnapshot||''">
+								<!-- 购票须知
+								<u-icon name="arrow-right" color="#7F7F7F" size="24rpx"></u-icon> -->
+								<!-- {{item.goodsSnapshot||''}} -->
+							</view>
+							<view class="btn" @click="book(item)" v-if="item.quantity!==0&&sessionList.length>=1">报名</view>
+							<view class="btn disabled" v-else>无票</view>
+						</view>
+					</view>
+				</view>
+			</view>
+			<!-- ticket end -->
+			<view class="details" v-if="tabsIndex==1">
+				<view class="details-block">
+					<view class="title">剧情简介</view>
+					<view class="intro parse-content">
+						<u-parse :content="performInfo.performSnapshot"></u-parse>
+					</view>
+				</view>
+				<view class="details-block actors">
+					<view class="title u-flex u-row-between">
+						演职人员
+						<view class="right u-flex" @click="$u.route('pages/actors',{performId:performId})">
+							<text>更多</text>
+							<u-icon name="arrow-right" color="#7F7F7F" size="24rpx"></u-icon>
+						</view>
+					</view>
+					<view class="actor-list">
+						<u-scroll-list :indicator="false">
+							<view class="item" v-for="(item, index) in actorsArr" :key="index">
+								<image class="img" :src="item.performerHead||staticUrl+'/img/actors.png'"></image>
+								<view class="text">
+									<view class="name">{{item.performerName}}</view>
+									<view class="role u-line-1">{{item.performerRole}}</view>
+								</view>
+							</view>
+						</u-scroll-list>
+						<u-empty text="暂无" v-if="actorsArr.length<1"></u-empty>
+					</view>
+				</view>
+			</view>
+			<view class="viewingTips parse-content" v-if="tabsIndex==2">
+				<u-parse :content="formerNotice"></u-parse>
+			</view>
+		</view>
+		<!-- :maxDate="maxDate" -->
+		<u-calendar
+			ref="calendar"
+			:formatter="formatter"
+			:maxDate="maxDate"
+			monthNum="12"
+			:show="calendarShow" 
+			color="#EF1010"
+			:closeOnClickOverlay="true"
+			@close="closeCalendar"
+			@confirm="confirmCalendar">
+		</u-calendar>
+		<!-- 分享选择弹出内容 -->
+		<view class="share-option" :class="{shareShow:shareShow}">
+			<view class="overlay" v-if="shareShow"  @click="shareShow=false"></view>
+			<!-- #ifdef MP-WEIXIN -->
+			<button class="share-option-item wx-share" data-name="shareBtn" open-type="share">
+				发送给朋友
+			</button>
+			<!-- #endif --> 
+			<view class="share-option-item" @click="getPoster">生成海报</view>
+			<view class="share-option-item" @click="shareShow=false">取消</view>
+		</view>
+		<u-popup :show="posterShow" @close="posterShow=false" ref="uni-popup">
+			<view class="poster-wrap u-flex u-col-center">
+				<view class="poster-inner">
+					<view class="close-wrap" @click="posterShow=false">
+						<u-icon name="close-circle" color="#fff" size="56rpx"></u-icon>
+					</view>
+					<view class="poster" id="poster" ref="poster" >
+						<u--image :showLoading="true" :src="posterSrc" width="100%" height="65vh" mode="aspectFit"></u--image>
+					</view>
+					<!-- savePoster -->
+					<view class="poster-btn" @click="saveImage">保存图片</view>
+				</view>
+			</view>
+		</u-popup>
+		<canvas canvas-id="canvas" class="canvas" style="width: 670px; height: 900px;"></canvas>
+	</view>
+</template>
+
+<script>
+	import moment from 'moment';
+	import { systemInfo } from "@/mixin.js";
+	// 日历
+	const d = new Date()
+	const year = d.getFullYear()
+	let month = d.getMonth() + 1
+	month = month < 10 ? `0${month}` : month
+	const date = d.getDate()
+	// 日历
+	// 算最大日期
+	const maxd = new Date()
+	maxd.setMonth(maxd.getMonth() + 12)  // 将月份加3
+	const maxdyear = maxd.getFullYear()
+	let maxdmonth = maxd.getMonth() + 1
+	maxdmonth = maxdmonth < 10 ? `0${maxdmonth}` : maxdmonth
+	const maxddate = maxd.getDate()
+	const theMaxDate = `${maxdyear}-${maxdmonth}-${maxddate}`
+	// console.log('theMaxDate=============================',theMaxDate)  //输出:
+	//算最大日期
+	let parentThis= null;
+	export default {
+		mixins:[systemInfo],
+		data() {
+			return {
+				performId:'',
+				staticUrl:this.$commonConfig.staticUrl,
+				tabsArr:[
+					{name:'票务购买'},
+					{name:'演出详情'},
+					{name:'观影须知'}
+				],
+				theatre:{},
+				tabsIndex:0,
+				dateList:[],
+				dateIndex:0,
+				calendarShow:false,//日历
+				// maxDate:`${year}-${month}-${date + 30}`,
+				maxDate:theMaxDate,
+				auditoriumList:[],//演艺厅
+				auditoriumIndex:0,
+				sessionList:[],//演出场次
+				sessionIndex:0,
+				ticketTypeList:[],//门票
+				actorsArr:[],
+				shareShow:false,
+				posterShow:false,
+				posterSrc:'',
+				posterSrcType:null,
+				performInfo:{},//节目详情
+				formerNotice:{},//节目观影须知
+				performDateList:[],//有票的日期
+				// firstGet:0,
+			}
+		},
+		onShow() {
+		},
+		onLoad(page) {
+			parentThis = this;
+			console.log('page',page);
+			this.performId = page.id;
+			this.getPerformData();
+			// this.getSystemInfo();
+			let today = new Date();
+			this.setDate(today);
+			this.getPerformInfo();
+			this.getPerformerNotice();
+
+		},
+		onReady() {
+			// 如果需要兼容微信小程序的话,需要用此写法
+			this.$refs.calendar.setFormatter(this.formatter);
+		},
+		methods: {
+			leftClick(e){
+				let pages = getCurrentPages();
+				if(pages.length==1){
+					uni.$u.route('/pages/index/index')
+				}else{
+					uni.navigateBack()
+				};
+			},
+			getPerformData(){
+				this.$u.api.performSell({performId:this.performId}).then(res=>{
+					// console.log('getPerformData',res.data);
+					this.theatre =  res.data.theatreList[0];
+					this.auditoriumList  = res.data.theatreList[0].auditoriumList;
+					this.ticketTypeList = res.data.goodsList;
+					this.performDateList = this.auditoriumList[this.auditoriumIndex].performDateList;
+					this.getDate()
+					// console.log('auditoriumList',this.auditoriumList);
+				}).catch(err=>{
+					console.log('getPoster',err);
+				})
+			},
+			getPerformInfo(){
+				this.$u.api.performInfo({id:this.performId}).then(res=>{
+					// console.log('getPerformInfo',res.data);
+					this.performInfo =  res.data;
+					this.actorsArr = res.data.performerList;
+				}).catch(err=>{
+					console.log('getPerformInfo',err);
+				})
+			},
+			getPerformerNotice(){
+				this.$u.api.performerNotice({performId:this.performId}).then(res=>{
+					// console.log('getPerformerNotice',res.data);
+					this.formerNotice =  res.data.performNotice;
+				}).catch(err=>{
+					console.log('getPerformerNotice',err);
+				})
+			},
+			getDate(){
+				let param = {
+					auditoriumId:this.auditoriumList[this.auditoriumIndex]?.id,//演艺厅ID(演艺厅列表)
+					performId:this.performId,
+				};
+				this.$u.api.getAuditoriumDate(param).then(res=>{
+					console.log('getDate',res.data);
+					this.performDateList = res.data.list||[];
+					this.setDate();
+					// this.getTimes();
+				}).catch(err=>{
+					console.log('getDate',err);
+				})
+			},
+			getTimes(){
+				// this.firstGet++;
+				uni.showLoading();
+				let auditoriumId= this.auditoriumList[this.auditoriumIndex]?.id;
+				if(!auditoriumId){return}
+				console.log('this.dateList',this.dateList);
+				console.log('this.dateIndex',this.dateIndex);
+				let dateOBJ = this.dateList[this.dateIndex];
+				let param = {
+					auditoriumId:auditoriumId,//演艺厅ID(演艺厅列表)
+					performId:this.performId,
+					date:`${dateOBJ.year}-${dateOBJ.month}-${dateOBJ.day}`
+				};
+				// console.log('param',param);
+				this.$u.api.getAuditoriumTimes(param).then(res=>{
+					// console.log('getTimes',res.data);
+					this.sessionList = res.data.list.map(item=>{
+						item.performTimeStart = item.performTimeStart;
+						item.performTimeEnd = item.performTimeEnd;
+						return item
+					});
+					uni.hideLoading();
+				}).catch(err=>{
+					console.log('getPoster',err);
+				})
+			},
+			 formatter: (day) => {
+				 day.date = moment(day.date).format("YYYY-MM-DD HH:mm:ss")
+				 // console.log('day',day);
+				 // console.log('thisthisthisthisthisthisthisthisthis',parentThis);
+				 // console.log('==================================',day.date.toISOString().split('T')[0]);
+				let ticket = parentThis.performDateList.find(item => item.performDate === day.date.split(' ')[0]);
+				if(ticket){
+					day.dot = true
+				}
+				// if(ticket&&ticket.ishave){
+				// 	day.bottomInfo = '有票'
+				// 	day.dot = true
+				// }else if(ticket){
+				// 	day.dot = true
+				// }
+			    return day
+			  },
+			setDate(firstDay,isSelect){
+				console.log('this.performDateList',this.performDateList);
+				if(this.performDateList[0]?.performDate&&!isSelect){
+					console.log('111111');
+					// firstDay = new Date(this.performDateList[0].performDate);
+					const uniquePerformDateList = Array.from(new Set(this.performDateList.map(JSON.stringify))).map(JSON.parse);//去重
+					// console.log('uniquePerformDateList',uniquePerformDateList);
+					let arr =  uniquePerformDateList.slice(0, 3);//只要前三项
+					this.dateList = arr.map(item=>{
+						// console.log('uniquePerformDateList item',item);
+						let day = item.performDate.split('-');
+						// console.log('setDate day',day);
+						return { year:day[0],month:String(day[1]),day:String(day[2]), fullDay:`${day[0]}-${String(day[1])}-${String(day[2])}`}
+					});
+					console.log('this.dateList',this.dateList);
+					this.dateIndex = 0;
+					this.getTimes();
+					// 
+					return 
+				}
+				console.log('this.dateListthis.dateListthis.dateListthis.dateList',this.dateList);
+				// 前端写演出日期(今天,明天,后天)
+				let today = firstDay;
+				let tomorrow = new Date();
+				tomorrow.setDate(today.getDate() + 1);
+				let afterTomorrow = new Date();
+				afterTomorrow.setDate(today.getDate() + 2);
+				this.dateList = [
+				  { 
+					  year: today.getFullYear(), 
+					  month: String(today.getMonth() + 1).padStart(2, '0'), 
+					  day: String(today.getDate()).padStart(2, '0'), 
+					  name: '日期',
+					  fullDay:`${today.getFullYear()}-${String(today.getMonth() + 1).padStart(2, '0')}-${ String(today.getDate()).padStart(2, '0')}`
+					},
+				  { 
+					  year: tomorrow.getFullYear(), 
+					  month: String(tomorrow.getMonth() + 1).padStart(2, '0'), 
+					  day: String(tomorrow.getDate()).padStart(2, '0'), 
+					  name: '日期',
+					  fullDay:`${tomorrow.getFullYear()}-${String(tomorrow.getMonth() + 1).padStart(2, '0')}-${ String(tomorrow.getDate()).padStart(2, '0')}`
+					},
+				  { 
+					  year: afterTomorrow.getFullYear(), 
+					  month: String(afterTomorrow.getMonth() + 1).padStart(2, '0'), 
+					  day: String(afterTomorrow.getDate()).padStart(2, '0'), 
+					  name: '日期',
+					  fullDay:`${afterTomorrow.getFullYear()}-${String(afterTomorrow.getMonth() + 1).padStart(2, '0')}-${ String(afterTomorrow.getDate()).padStart(2, '0')}`
+					}
+				];
+				this.getTimes()
+				// console.log('this.dateList ',this.dateList);
+			},
+			dateClick(index){
+				this.dateIndex = index;
+				this.getTimes()
+			},
+			auditoriumClick(index){
+				this.auditoriumIndex = index;
+				this.getDate();
+			},
+			confirmCalendar(e){
+				console.log('confirmCalendar',e);
+				// this.getDate(e)
+				this.dateIndex = 0;
+				this.setDate( new Date(e),true );
+				// console.log('maxDate',this.maxDate);
+				// console.log('dateList',this.dateList);
+				this.calendarShow = false;
+			},
+			closeCalendar(){
+				this.calendarShow = false;
+			},
+			tabsClick(e){
+				// console.log('tabsClick',e);
+				this.tabsIndex = e.index;
+			},
+			sessionClick(index){
+				this.sessionIndex = index;
+			},
+			book(item){
+				// console.log('book',item);
+				// console.log('performInfo',this.performInfo);
+				let session = this.sessionList[this.sessionIndex];
+				// console.log('session',session);
+				let performTimeStart = session.performTimeStart;
+				let performTimeEnd = session.performTimeEnd;
+				let dateOBJ = this.dateList[this.dateIndex];
+				uni.$u.route('pages/chosenposition',{
+					goodsId:item.id,
+					performId:this.performId,
+					performName:this.performInfo.name,
+					day:`${dateOBJ.year}-${dateOBJ.month}-${dateOBJ.day}`,
+					performTimeStart:session.performTimeStart,
+					performTimeEnd:session.performTimeEnd,
+					performTimeId:session.id,
+					auditoriumId:this.auditoriumList[this.auditoriumIndex].id
+				})
+			},
+			openShare(){
+				this.shareShow = true;
+				
+			},
+			// 海报相关开始
+			getPoster(item){
+				this.posterShow = true;
+				this.shareShow = false;
+				// 后端生成海报
+				// this.$u.api.performQrcode({performId:this.performId}).then(res=>{
+				// 	this.posterSrc = res.data.imageUrl;
+				// 	// console.log('getPoster',res.data);
+				// }).catch(err=>{
+				// 	console.log('getPoster',err);
+				// })
+				
+				//前端生成海报
+				uni.showLoading({
+					title: '生成海报中'
+				});
+				// 前端生成海报开始
+				let that = this;
+				 const ctx = uni.createCanvasContext('canvas', this);
+				// 加载海报背景图
+				uni.getImageInfo({
+				  src: this.performInfo.posterImg,
+				  success: res1 => {
+					const img1 = res1.path;
+							
+					// 加载海报二维码
+					uni.getImageInfo({
+					  src: this.performInfo.appletQrcode,
+					  success: res2 => {
+						const img2 = res2.path;
+							
+						// 绘制海报背景图
+						ctx.drawImage(img1, 0, 0, 670, 900);
+							
+						// 绘制海报二维码
+						ctx.drawImage(img2, 468, 724, 188, 166);
+							
+						// 绘制完成后导出图片并显示
+						ctx.draw(false, () => {
+						  uni.canvasToTempFilePath({
+							canvasId: 'canvas',
+							success: res3 => {
+							  this.posterSrc = res3.tempFilePath;
+							  this.posterSrcType = 'local';
+							},
+							fail: err => {
+							  console.error(err);
+							},
+						  }, this);
+						});
+					  },
+					  fail: err2 => {
+						console.error(err2);
+					  },
+					});
+				  },
+				  fail: err1 => {
+					console.error(err1);
+				  },
+				  complete: () => {
+				  	uni.hideLoading()
+				  }
+				});
+				// 前端生成海报结束
+			},
+			saveImage() {
+				let that = this;
+					uni.showLoading({
+						title: '保存中'
+					});
+					if(this.posterSrcType == 'local'){
+						let params = {
+							tempFilePath:that.posterSrc
+						}
+						that.saveImageToPhotosAlbum(params);
+						return
+					}
+			      uni.downloadFile({
+			        url: this.posterSrc,
+			        success(res) {
+			          if (res.statusCode === 200) {
+						  that.saveImageToPhotosAlbum(res)
+			          } else {
+			            uni.showToast({
+			              title: '下载图片失败',
+			              icon: 'none'
+			            });
+			          }
+			        },
+			        fail(e) {
+						console.log('下载图片失败', e);
+						console.log('posterSrc',that.posterSrc);
+			          uni.showToast({
+			            title: '下载图片失败',
+			            icon: 'none'
+			          });
+			        }
+			      });
+			    },
+				saveImageToPhotosAlbum(res){
+					let that = this;
+					uni.saveImageToPhotosAlbum({
+					  filePath: res.tempFilePath,
+					  success() {
+					    uni.showToast({
+					      title: '保存到相册成功',
+					      icon: 'success'
+					    });
+					  },
+					  fail(err) {
+						  console.log('保存图片失败',err);
+					    if (err.errMsg === 'saveImageToPhotosAlbum:fail auth deny') {
+							uni.getSetting({
+							  success(res) {
+								if (!res.authSetting['scope.writePhotosAlbum']) {
+								  uni.showModal({
+									title: '提示',
+									content: '您还没有授权访问相册,请前往设置页面打开权限。',
+									confirmText: '去设置',
+									success(res) {
+									  if (res.confirm) {
+										uni.openSetting();
+									  }
+									}
+								  });
+								} else {
+								  uni.showToast({
+									title: '保存图片失败',
+									icon: 'none'
+								  });
+								}
+							  }
+							});
+						  } else {
+							uni.showToast({
+							  title: '保存图片失败',
+							  icon: 'none'
+							});
+						}
+					  },
+					  complete() {
+					  	uni.hideLoading();
+						that.posterShow = false;
+					  }
+					});					
+				},
+			// 海报相关结束
+			goMap(){
+				uni.openLocation({
+				  latitude:Number(this.theatre.latitude),	//维度
+				  longitude: Number(this.theatre.longitude), //经度
+				  name:  this.theatre.name,	//目的地定位名称
+				  scale: 15,	//缩放比例
+				  address: this.theatre.address	//导航详细地址
+				})
+			},
+			phoneCall(){
+				// console.log('this.theatre',this.theatre);
+				uni.makePhoneCall({
+					phoneNumber: this.theatre.customerServiceMobile,
+					success() {
+						console.log('success');
+					},
+					fail() {
+						console.log('fail');
+					}
+				});
+			}
+
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+.banner{
+	position: relative;
+	.img{
+		width: 750rpx;
+		height: 554rpx;
+		display: block;
+	}
+	.content{
+		position: absolute;
+		width: 100%;
+		box-sizing: border-box;
+		padding: 0 32rpx;
+		left: 0;
+		bottom: 200rpx;
+	}
+	.share{
+		overflow: hidden;
+		margin-bottom: 100rpx;
+		.icon{
+			float: right;
+			display: block;
+			width: 32rpx;
+			height: 32rpx;
+			padding: 10rpx;
+			border-radius: 50%;
+			background-color: rgba(0,0,0,0.4);
+		}
+	}
+}
+.base-info{
+	position: relative;
+	// margin-bottom: 54rpx;
+	.inner{
+		position: relative;
+		padding: 50rpx 38rpx 58rpx 40rpx;
+		background-color: #FFFFFF;
+		border-radius: 32rpx 32rpx 0rpx 0rpx;
+		margin-top: -30px;
+		border-bottom: 12rpx solid #F7F8F9;
+	}
+	.name{
+		font-size: 40rpx;
+		font-weight: 800;
+		color: #2D2D2D;
+		margin-bottom: 26rpx;
+	}
+	.addr{
+		font-size: 26rpx;
+		font-weight: 400;
+		color: #606060;
+	}
+	.menu{
+		.item{
+			margin-left: 18rpx;
+			font-size: 18rpx;
+			font-weight: 400;
+			color: #999999;
+			text-align: center;
+		}
+		.icon{
+			width: 62rpx;
+			height: 64rpx;
+			display: block;
+			margin-bottom: 4rpx;
+		}
+	}
+}
+.tabs-wrap{
+	position: relative;
+	margin-bottom: 54rpx;
+	&::after{
+		content: '';
+		width: 100%;
+		height: 1px;
+		background-color: #eee;
+		position: absolute;
+		left: 0;
+		right: 0;
+		bottom: -46rpx;
+	}
+	.inner{
+		position: relative;
+		padding-top: 48rpx;
+		background-color: #FFFFFF;
+		// border-radius: 32rpx 32rpx 0rpx 0rpx;
+		height: 46rpx;
+		// margin-top: -30px;
+	}
+}
+.date-block{
+	margin-bottom: 64rpx;
+}
+.title{
+	font-size: 32rpx;
+	font-weight: bold;
+	color: #2D2D2D;
+	line-height: 48rpx;
+	margin-bottom: 38rpx;
+}
+.empty{
+		background-color: #636363;
+		color: #979797;
+		border-radius: 8rpx;
+		padding: 24rpx;
+		text-align: center;
+	}
+.session-wrap{
+	
+}
+.session{
+	 display: grid;
+	 grid-template-columns: repeat(3, 1fr);
+	 gap: 20rpx;
+	.session-item{
+		height: 80rpx;
+		line-height: 80rpx;
+		border-radius: 12rpx;
+		border: 2rpx solid #636363;
+		text-align: center;
+		font-size: 24rpx;
+		font-weight: 400;
+		color: #636363;
+		&.active{
+			color: #ED0000;
+			border-color: #ED0000;
+		}
+	}
+}
+.ticket-type{
+	.type-item{
+		background: #FFFFFF;
+		box-shadow: 0rpx 2rpx 12rpx 0rpx rgba(180,180,180,0.5);
+		border-radius: 20rpx;
+		margin-bottom: 20rpx;
+		padding: 38rpx 40rpx;
+		.name-price{
+			margin-bottom: 26rpx;
+		}
+		.name{
+			font-size: 28rpx;
+			font-weight: 500;
+			color: #363636;
+		}
+		.prices{
+			font-size: 36rpx;
+			font-weight: bold;
+			color: #ED0000;
+		}
+		.ishave{
+			margin-bottom: 26rpx;
+			.text{
+				height: 30rpx;
+				line-height: 30rpx;
+				padding: 0 22rpx;
+				background: #FFC8C8;
+				border-radius: 20rpx;
+				font-size: 20rpx;
+				font-weight: 400;
+				color: #ED0000;
+			}
+		}
+		.bottom{
+			font-size: 22rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			.btn{
+				height: 60rpx;
+				line-height: 60rpx;
+				background: linear-gradient(90deg, #FF7979 0%, #ED0000 100%);
+				border-radius: 30rpx;
+				padding: 0 36rpx;
+				font-size: 28rpx;
+				font-weight: 500;
+				color: #FFFFFF;
+				&.disabled{
+					background: #979797;
+				}
+			}
+		}
+	}
+}
+.details{
+	.title{}
+	.details-block{
+		background: #FFFFFF;
+		box-shadow: 0rpx 2rpx 12rpx 2rpx rgba(221,221,221,0.5);
+		border-radius: 20rpx;
+		margin-bottom: 24rpx;
+		padding: 36rpx;
+		&.actors{
+			.title{
+				.right{
+					font-size: 24rpx;
+					font-weight: 400;
+					color: #7F7F7F;
+				}
+			}
+		}
+	}
+	.intro{
+		font-size: 26rpx;
+		font-weight: 400;
+		color: #4E4E4E;
+		line-height: 40rpx;
+	}
+	.actor-list{
+		.item{
+			flex-shrink: 0;
+			width: 160rpx;
+			text-align: center;
+			margin-right: 24rpx;
+		}
+		.img{
+			display: block;
+			width: 100%;
+			height: 200rpx;
+			margin-bottom: 16rpx;
+		}
+		.name{
+			font-size: 28rpx;
+			font-weight: 500;
+			color: #2D2D2D;
+			line-height: 42rpx;
+			margin-bottom: 4rpx;
+		}
+		.role{
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			line-height: 36rpx;
+		}
+	}
+}
+.canvas{
+	position: fixed;
+	left: -99999px;
+}
+</style>

+ 380 - 0
static/css/common.scss

@@ -0,0 +1,380 @@
+$pagegap:32rpx;
+.page-wrap{padding: $pagegap;}
+.top-search{
+	position: absolute;
+	z-index: 31;
+	background-color: rgba(0,0,0,0.04);
+	border-radius: 100rpx;
+	margin-bottom: 20rpx;
+	margin-left: $pagegap;
+}
+
+.search-wrap{
+	background-color: #fff;
+	position: relative;
+	padding: 20rpx;
+	margin-bottom: 24rpx;
+}
+.search-wrap::after {
+  content: "";
+  position: absolute;
+  width: 100%;
+  height: 14rpx;
+  bottom: -20rpx;
+  left: 0;
+  background: linear-gradient(to bottom, #F5F5F5, #fff);
+  z-index: -1;
+}
+.search-wrap{
+	.position{
+		font-size: 28rpx;
+		font-weight: bold;
+		color: #2D2D2D;
+		/deep/ .u-icon{
+			position: relative;
+			top: 5rpx;
+			margin-left: 8rpx;
+		}
+	}
+	.search-out{
+		flex: 1;
+		border: 1px solid #DADADA;
+		border-radius: 50rpx;
+		margin-left: 24rpx;
+	}
+	.conditions{
+		// margin-top: 48rpx;
+		.item{
+			margin: 0 24rpx;
+			padding: 48rpx 0 20rpx;
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #363636;
+			.text{
+				margin-right: 10rpx;
+			}
+		}
+	}
+}
+.gray{
+	color: #999;
+}
+.red{
+	color: #FF3C3F;
+}
+
+
+.view-wrap{
+	padding: 30rpx 20rpx;
+}
+.full-btn{
+	background-color: #F01414;
+	color: #fff;
+	border-radius: 44rpx;
+	padding: 22rpx 0;
+	text-align: center;
+	margin-bottom: 40rpx;
+	margin-top: 20rpx;
+	font-weight: bold;
+	&.gray{
+		background-color: #ddd;
+		color: #999;
+	}
+	&.white{
+		background-color: #fff;
+		color: #606060;
+	}
+	&.red{
+		background-color: #ED0000;
+		color: #fff;
+	}
+}
+
+.single-til{
+	margin-bottom: 20rpx;
+	.text{
+		font-size: 32rpx;
+		color: #333;
+		font-weight: 600;
+		.sub-title{
+			margin-left: 20rpx;
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #999999;
+		}
+	}
+	.more-text{
+		font-size: 24rpx;
+		color: #999;
+	}
+}
+
+.date-list{
+	.date-item{
+		position: relative;
+		overflow: hidden;
+		flex: 1;
+		background: #FFFFFF;
+		border-radius: 16rpx;
+		border: 2rpx solid #EEEEEE;
+		text-align: center;
+		margin: 0 8rpx;
+		box-sizing: border-box;
+		&.active{
+			border-color: #ED0000;
+			background-color: #FFC8C8 ;
+			.name,.date{
+				color: #ED0000;
+			}
+			.selected-img{
+				display: block;
+			}
+		}
+		.selected-img{
+			width: 32rpx;
+			height: 32rpx;
+			position: absolute;
+			right: 0;
+			bottom: 0;
+			display: none;
+		}
+		&.dot::after{
+			content: '';
+			width: 4px;
+			height: 4px;
+			border-radius: 50%;
+			background-color: #ED0000;
+			position: absolute;
+			right: 10rpx;
+			top: 5px;		}
+		.name{
+			padding-top: 26rpx;
+			margin-bottom: 12rpx;
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			line-height: 36rpx;
+		}
+		.date{
+			font-size: 28rpx;
+			font-weight: 500;
+			color: #2D2D2D;
+			line-height: 42rpx;
+			padding-bottom: 28rpx;
+		}
+	}
+	.more-date{
+		height: 148rpx;
+		.text{
+			margin-left: 20rpx;
+			margin-right: 10rpx;
+			font-size: 24rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			line-height: 36rpx;
+		}
+	}
+}
+.date-block.auditorium{
+	.name{
+		margin-bottom: 0;
+		padding: 34rpx 0;
+	}
+}
+// 分享 海报
+.share-option{
+	transform: translateY(100%);
+	.overlay{
+		position: fixed;
+		left: 0;
+		top: 0;
+		width: 100vw;
+		height: 100vh;
+		background-color: rgba(0, 0, 0, 0.35);
+		transform: translateY(-100%);
+	}
+	&.shareShow{
+		transform: translateY(0);
+	}
+	position: fixed;
+	width: 100%;
+	left: 0;
+	bottom: 0;
+	z-index: 50;
+	background-color: #fff;
+	.share-option-item{
+		position: relative;
+		height: 46px;
+		line-height: 46px;
+		border: 0;
+		background-color: #fff;
+		text-align: center;
+		border-radius: 0;
+		border-bottom: 1px solid #eee;
+		font-size: 30rpx;
+	}
+	.wx-share{
+		border-bottom: 0;
+	}
+}
+.poster-wrap{
+	position: fixed;
+	left: 0;
+	top: 0;
+	width: 100%;
+	height: 100vh;
+	.poster-inner{
+		flex: 1;
+		margin: 0 75rpx;
+		.close-wrap{
+			display: flex;
+			justify-content: flex-end;
+			margin-bottom: 16rpx;
+		}
+	}
+	.poster{
+		position: relative;
+		// padding-bottom: 90rpx;
+		background-color: transparent;
+		.posterBg{
+			position: absolute;
+			left: 0;
+			bottom: 0;
+			right: 0;
+			width: 100%;
+			z-index: 1;
+		}
+		.placard{
+			position: relative;
+			z-index: 10;
+		}
+		.bottom{
+			position: relative;
+			z-index: 20;
+			margin: 33rpx 40rpx 0;
+			.left{
+				margin-right: 58rpx;
+				.price{
+					font-size: 22rpx;
+					font-weight: 600;
+					color: #FF3538;
+					line-height: 30rpx;
+					margin-bottom: 10rpx;
+					.num{
+						font-size: 40rpx;
+						font-weight: 600;
+						margin-left: 5rpx;
+					}
+				}
+				.goodsName{
+					font-size: 30rpx;
+					font-weight: 400;
+					color: #333333;
+					line-height: 38rpx;
+					margin-bottom: 16rpx;
+				}
+				.slogan{
+					font-size: 26rpx;
+					font-weight: 600;
+					line-height: 37rpx;
+				}
+			}
+			.right{
+				text-align: center;
+				.imgTip{
+					margin-top: 12rpx;
+					font-size: 20rpx;
+					font-weight: 400;
+					color: #333333;
+					line-height: 28rpx;
+				}
+				.qrcode{
+					width: 108rpx;
+					height:108rpx;
+				}
+			}
+		}
+	}
+	.poster-btn{
+		height: 88rpx;
+		line-height: 88rpx;
+		border-radius: 44rpx;
+		text-align: center;
+		color: #fff;
+		margin-top: 40rpx;
+		font-size: 32rpx;
+		font-weight: 600;
+		background: linear-gradient(90deg, #FF7979 0%, #ED0000 100%);
+	}
+}
+// 分享 海报
+
+// 富文本
+.parse-content{
+	font-size: 26rpx;
+	font-family: SourceHanSansCN, SourceHanSansCN;
+	font-weight: 400;
+	color: #4E4E4E;
+	line-height: 40rpx;
+	rich-text,p{
+		display: block;
+		margin-bottom: 8px;
+	}
+}
+
+// 剧场
+.programme{
+	// background-color: #FFFFFF;
+	position: relative;
+	border-radius: 30rpx;
+	overflow: hidden;
+	// background: radial-gradient(circle at -26rpx 230rpx, transparent 10%, #fff 4%) left, radial-gradient(circle at calc( 100% + 26rpx ) 232rpx, transparent 10%, #fff 4%) right;
+	background-color: #fff;
+	background-size: 50% 100%;
+	background-repeat: no-repeat;
+	.img{
+		width: 100%;
+		height: 242rpx;
+		display: block;
+	}
+	.text{
+		position: relative;
+		padding: 32rpx 30rpx;
+		.name{
+			font-size: 28rpx;
+			font-weight: bold;
+			color: #363636;
+			line-height: 42rpx;
+			// margin-bottom: 18rpx;
+		}
+		.addr{
+			font-size: 22rpx;
+			font-weight: 400;
+			color: #7F7F7F;
+			line-height: 34rpx;
+		}
+		.btn{
+			height: 51rpx;
+			line-height: 51rpx;
+			padding: 0 24rpx;
+			background: linear-gradient(90deg, #FF7979 0%, #ED0000 100%);
+			border-radius: 25rpx;
+			font-size: 20rpx;
+			font-weight: 400;
+			color: #FFFFFF;
+		}
+	}
+	.share{
+		position: absolute;
+		right: 16rpx;
+		top: 16rpx;
+		padding: 10rpx;
+		border-radius: 50%;
+		background-color: rgba(0,0,0,0.4);
+		.icon{
+			display: block;
+			width: 32rpx;
+			height: 32rpx;
+		}
+	}
+}

+ 71 - 0
static/css/flex.scss

@@ -0,0 +1,71 @@
+.u-flex {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+}
+
+.u-flex-wrap {
+	flex-wrap: wrap;
+}
+
+.u-flex-nowrap {
+	flex-wrap: nowrap;
+}
+
+.u-col-center {
+	align-items: center;
+}
+
+.u-col-top {
+	align-items: flex-start;
+}
+
+.u-col-bottom {
+	align-items: flex-end;
+}
+
+.u-row-center {
+	justify-content: center;
+}
+
+.u-row-left {
+	justify-content: flex-start;
+}
+
+.u-row-right {
+	justify-content: flex-end;
+}
+
+.u-row-between {
+	justify-content: space-between;
+}
+
+.u-row-around {
+	justify-content: space-around;
+}
+
+.u-text-left {
+	text-align: left;
+}
+
+.u-text-center {
+	text-align: center;
+}
+
+.u-text-right {
+	text-align: right;
+}
+
+.u-flex-col {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: column;
+}
+
+// 定义flex等分
+@for $i from 0 through 12 {
+	.u-flex-#{$i} {
+		flex: $i;
+	}
+}

+ 419 - 0
static/logo.svg

@@ -0,0 +1,419 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 26.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="图层_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 750 422" style="enable-background:new 0 0 750 422;" xml:space="preserve">
+<style type="text/css">
+	.st0{fill:url(#SVGID_1_);}
+	.st1{fill:url(#SVGID_00000140000486157882826870000007221579918701196449_);}
+	.st2{fill:url(#SVGID_00000036931254733988851740000009953821978700827530_);}
+	.st3{fill:url(#SVGID_00000158731152278408604530000000055075175893880453_);}
+	.st4{fill:url(#SVGID_00000080928632844849270810000017537741479220234144_);}
+	.st5{fill:url(#SVGID_00000091015095995676809280000009079845705063013795_);}
+	.st6{fill:url(#SVGID_00000151545769631247894970000005004757807824389025_);}
+	.st7{fill:url(#SVGID_00000128475466696929793450000010403830378602305940_);}
+	.st8{fill:url(#SVGID_00000036248896964300413530000004462419411256015785_);}
+	.st9{fill:url(#SVGID_00000022548248585355353180000005833023100168402343_);}
+	.st10{fill:url(#SVGID_00000099640502816314516610000015645311528596044965_);}
+	.st11{fill:url(#SVGID_00000143599212459675872200000007240227900190114986_);}
+	.st12{fill:url(#SVGID_00000153699669491735575770000010502572629283511947_);}
+	.st13{fill:url(#SVGID_00000139271213937430465080000012685673228554600854_);}
+	.st14{fill:url(#SVGID_00000160165960824655412760000004520554627207360142_);}
+	.st15{fill:url(#SVGID_00000030488478326947421440000017588227068186671240_);}
+	.st16{fill:url(#SVGID_00000053527995893557589750000003927760824008387238_);}
+	.st17{fill:url(#SVGID_00000100355412462811441330000011505244038520995482_);}
+	.st18{fill:url(#SVGID_00000178199887920956096310000018072506848948699044_);}
+	.st19{fill:url(#SVGID_00000140727996621273254330000008246246362703009698_);}
+	.st20{fill:url(#SVGID_00000053507488083599379620000009634741184751434682_);}
+</style>
+<g>
+	<g>
+		<g>
+			<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="527.5886" y1="139.4055" x2="527.5886" y2="278.2872">
+				<stop  offset="0" style="stop-color:#DEA54E"/>
+				<stop  offset="0.1421" style="stop-color:#FCFADF"/>
+				<stop  offset="0.1594" style="stop-color:#FBF4CF"/>
+				<stop  offset="0.1982" style="stop-color:#F9E9B0"/>
+				<stop  offset="0.2409" style="stop-color:#F7E097"/>
+				<stop  offset="0.2882" style="stop-color:#F6DA83"/>
+				<stop  offset="0.3426" style="stop-color:#F5D575"/>
+				<stop  offset="0.4104" style="stop-color:#F4D26D"/>
+				<stop  offset="0.5334" style="stop-color:#F4D16B"/>
+				<stop  offset="0.8344" style="stop-color:#C26719"/>
+				<stop  offset="1" style="stop-color:#F4CD9C"/>
+			</linearGradient>
+			<path class="st0" d="M538.99,272.42c-0.19-1.2,0.01-2.05,0.62-2.89c1.6-2.19,2.46-4.71,2.34-7.75
+				c-0.24-6.09,0.25-12.15,0.67-18.2c0.01-0.13,0-0.27-0.13-0.54c-1.01,1.35-2.07,2.65-3.02,4.06c-2.26,3.38-3.72,7.21-4.18,11.57
+				c-0.06,0.54-0.07,1.09-0.1,1.63c-0.11,1.78-1.07,2.61-2.38,1.85c-1.19-0.69-2.31-1.71-2.71-3.34c-0.21-0.86-0.13-1.88-0.04-2.81
+				c0.68-6.73,1.4-13.45,2.12-20.17c0.2-1.89,0.42-3.78,0.62-5.67c0.03-0.25,0-0.52,0-0.89c-0.27,0.29-0.49,0.51-0.7,0.74
+				c-3.36,3.83-6.72,7.67-10.09,11.49c-0.3,0.34-0.41,0.65-0.36,1.19c0.49,4.73,0.93,9.46,1.38,14.2c0.02,0.21,0,0.43,0,0.69
+				c-0.92,0.3-1.42,1.17-1.76,2.17c-0.36,1.05-0.63,2.15-0.95,3.22c-0.12,0.38-0.25,1.02-0.44,1.05c-0.56,0.1-1.17,0.26-1.71-0.32
+				c-1.33-1.45-2.7-2.85-4.05-4.28c-0.93-0.99-0.92-0.99-0.78-2.65c0.27-0.04,0.55-0.08,0.83-0.13c0.93-0.18,1.38-0.79,1.38-1.93
+				c0.01-1.71,0-3.42,0-5.36c-1.08,1.28-2.05,2.38-2.98,3.54c-2.2,2.76-4.34,5.59-5.88,8.97c-0.65,1.41-1.05,2.98-1.6,4.47
+				c-0.08,0.21-0.3,0.46-0.47,0.48c-0.51,0.06-1.03,0.04-1.54,0.02c-0.54-0.02-2.55-2.5-2.58-3.16c0-0.03,0-0.06,0-0.08
+				c-0.18-1.56,0.27-2.72,1.17-3.91c4.58-6.01,9.07-12.11,13.57-18.2c0.18-0.25,0.28-0.67,0.29-1.02c0.02-2.43,0.06-4.86,0-7.28
+				c-0.06-2.28,0.18-4.5,0.61-6.71c0.06-0.31,0.09-0.63,0.14-0.95c-0.58-0.32-0.92,0.1-1.28,0.47c-2.77,2.82-5.76,5.22-9.14,6.82
+				c-2.28,1.08-4.64,1.79-7.09,1.77c-1.37-0.01-2.46-0.9-3.34-2.11c-0.49-0.68-0.28-1.47,0.37-1.91c4.7-3.16,9.41-6.31,14.11-9.49
+				c1.32-0.89,2.69-1.73,3.88-2.86c2.7-2.58,3.83-6.17,4.03-10.27c0.03-0.64,0.1-1.31,0.29-1.9c0.85-2.8,1.73-5.6,2.64-8.37
+				c0.26-0.77,0.65-1.49,1.05-2.17c0.35-0.59,1.09-0.61,1.54-0.15c3.17,3.3,4.56,4.13,8.8,5.27c0.23,1.96,0.48,3.95,0.67,5.94
+				c0.03,0.27-0.15,0.65-0.33,0.87c-2.15,2.69-4.39,5.28-6.46,8.05c-1.7,2.28-2.91,4.91-3.05,8.16c-0.09,2.11-0.44,4.21-0.67,6.31
+				c-0.23,2.1-0.45,4.21-0.68,6.31c0.07,0.02,0.12,0.06,0.14,0.05c4.06-3.31,7.9-6.95,10.9-11.73c1.88-3,2.89-6.34,3.33-10.15
+				c0.5-4.4,1.55-8.72,3.28-12.7c0.19-0.43,0.44-0.82,0.69-1.2c0.4-0.62,0.91-0.95,1.57-0.85c0.09,0.01,0.18,0.01,0.27,0
+				c0.98-0.1,1.76,0.16,2.6,0.97c1.95,1.89,4.35,2.7,6.73,3.45c2.22,0.7,4.47,1.23,6.71,1.84c0.21,0.06,0.43,0.13,0.62,0.19
+				c0.19,1.12,0.01,1.97-0.68,2.81c-2,2.46-3.96,4.96-6.3,6.96c-1.25,1.06-2.55,1.99-4.26,2.19c0.82,1,1.53,1.97,2.33,2.82
+				c0.54,0.58,0.65,1.14,0.49,1.98c-0.34,1.82-0.74,3.65-0.6,5.56c0.01,0.16,0.02,0.31,0.03,0.64c1.54-1.25,2.99-2.43,4.49-3.65
+				c-0.42-0.78-0.79-1.55-1.23-2.26c-0.26-0.43-0.23-0.77-0.02-1.18c0.71-1.39,1.84-1.82,3.07-2c0.51-0.07,1.03-0.02,1.54-0.1
+				c0.85-0.13,1.43,0.29,2.05,1c0.69,0.79,0.73,1.62,0.56,2.58c-0.39,2.25-1.46,4.06-2.77,5.58c-2.08,2.42-4.31,4.65-6.46,6.98
+				c-0.28,0.3-0.5,0.74-0.62,1.16c-0.58,2-0.71,4.08-0.65,6.19c0.05,2.02,0.09,4.04-0.01,6.05c-0.15,3.23-0.39,6.46-0.62,9.69
+				c-0.05,0.75-0.04,1.34,0.4,2.01c0.29,0.43,0.3,1.41,0.11,1.98c-0.37,1.08-0.86,2.18-1.51,3.03c-2.04,2.66-4.62,4.43-7.24,6.08
+				C540.15,272.51,539.55,272.37,538.99,272.42z M544.91,220.07c-0.06-0.06-0.12-0.13-0.17-0.19c-0.67,0.4-1.38,0.73-2.02,1.21
+				c-1.72,1.31-2.73,3.33-3.46,5.56c-1.17,3.55-1.57,7.3-1.74,11.09c-0.06,1.37-0.01,2.74-0.01,4.19c0.32-0.26,0.64-0.5,0.94-0.76
+				c3.37-3.01,4.93-7.26,5.48-12.15c0.24-2.1,0.35-4.22,0.56-6.32C544.58,221.81,544.77,220.94,544.91,220.07z M541.17,217.23
+				c0.43-0.09,0.71-0.07,0.94-0.2c2.66-1.61,5.33-3.17,7.65-5.48c0.46-0.46,0.83-1.07,1.32-1.71c-2.63-1.29-5.08-2.5-7.54-3.68
+				c-0.23-0.11-0.52-0.05-0.84-0.08C542.18,209.77,541.69,213.41,541.17,217.23z M524.55,203.52c-0.21,2.04-0.87,3.98-0.68,5.94
+				c0.95-1.15,1.9-2.31,2.77-3.37C525.99,205.29,525.3,204.43,524.55,203.52z"/>
+		</g>
+		<g>
+			
+				<linearGradient id="SVGID_00000155830357604304887680000002910475876380659645_" gradientUnits="userSpaceOnUse" x1="461.3712" y1="139.4055" x2="461.3712" y2="278.2872">
+				<stop  offset="0" style="stop-color:#DEA54E"/>
+				<stop  offset="0.1421" style="stop-color:#FCFADF"/>
+				<stop  offset="0.1594" style="stop-color:#FBF4CF"/>
+				<stop  offset="0.1982" style="stop-color:#F9E9B0"/>
+				<stop  offset="0.2409" style="stop-color:#F7E097"/>
+				<stop  offset="0.2882" style="stop-color:#F6DA83"/>
+				<stop  offset="0.3426" style="stop-color:#F5D575"/>
+				<stop  offset="0.4104" style="stop-color:#F4D26D"/>
+				<stop  offset="0.5334" style="stop-color:#F4D16B"/>
+				<stop  offset="0.8344" style="stop-color:#C26719"/>
+				<stop  offset="1" style="stop-color:#F4CD9C"/>
+			</linearGradient>
+			<path style="fill:url(#SVGID_00000155830357604304887680000002910475876380659645_);" d="M484.35,197.92
+				c-1.26,0.58-0.8,2.06-0.58,3.19c0.43,2.22,1.63,3.84,3,5.28c0.4,0.41,0.6,0.84,0.69,1.49c0.21,1.58,0.97,2.85,1.78,4.06
+				c0.21,0.32,0.44,0.62,0.66,0.92c0.63,0.85,0.66,1.12,0.21,2.17c-1.35,3.13-2.84,6.14-5.14,8.38c-0.86,0.84-1.85,1.47-2.85,2.24
+				c0.4,3.42,0.85,6.97,1.22,10.53c0.38,3.67,1.08,7.29,1.03,11.02c-0.02,1.35-0.44,2.36-1.29,3.15c-1.36,1.27-2.96,1.87-4.6,2.32
+				c-2.94,0.81-5.92,1.31-8.94,1.31c-0.73,0-1.34,0.17-1.91,0.73c-0.4,0.39-0.85,0.7-1.29,1.06c-1.71-1.5-3.72-1.5-5.69-1.71
+				c-0.67-0.07-1.34-0.04-2.01-0.05c-0.94-0.02-1.4-0.59-1.4-1.76c-0.01-3.34,1.53-5.35,4.29-5.56c0.96-0.07,1.92-0.06,2.88-0.04
+				c0.27,0.01,0.56,0.15,0.79,0.33c1.24,0.98,2.45,1.99,3.69,2.97c0.23,0.18,0.54,0.33,0.79,0.29c2.83-0.45,5.66-0.9,8.36-2.1
+				c1.01-0.45,2-1.08,2.9-1.8c0.97-0.78,1.28-1.92,1.05-3.41c-0.74-4.76-1.39-9.54-2.08-14.31c-0.01-0.07-0.05-0.14-0.11-0.28
+				c-0.16,0.11-0.3,0.2-0.44,0.32c-1.7,1.49-3.62,2.18-5.69,2.3c-1.13,0.06-1.7-1.06-1.17-2.28c1.79-4.11,3.57-8.22,5.39-12.31
+				c0.38-0.86,0.53-1.7,0.4-2.74c-0.17,0.18-0.32,0.31-0.45,0.47c-8.49,10.38-16.98,20.75-25.48,31.12
+				c-0.27,0.33-0.4,0.62-0.32,1.13c0.72,4.46,0.78,8.98,0.69,13.51c-0.01,0.31-0.08,0.66-0.22,0.9c-2.43,3.99-5.62,6.19-9.77,6.1
+				c-0.12,0-0.25-0.03-0.47-0.05c0.23-0.3,0.39-0.52,0.56-0.72c2.4-2.93,4.79-5.87,7.2-8.79c0.3-0.37,0.46-0.73,0.45-1.27
+				c-0.02-2.75-0.01-5.51-0.01-8.32c-0.12,0.04-0.21,0.04-0.27,0.09c-3.86,3.59-7.61,7.32-10.78,11.85
+				c-1.15,1.64-2.19,3.39-2.81,5.43c-0.25,0.83-0.38,1.72-0.62,2.86c1.85-0.35,3.5-0.66,5.15-0.97c0.02,0.04,0.04,0.09,0.06,0.13
+				c-1.06,1.11-2.11,2.22-3.17,3.33c-0.47,0.5-0.93,1.03-1.44,1.47c-0.28,0.25-0.65,0.4-0.99,0.49c-0.72,0.19-1.19-0.31-1.74-0.85
+				c-1.21-1.18-1.61-2.58-1.51-4.45c0.11-2.19,0.64-4.16,1.49-6.03c1.48-3.3,3.42-6.21,5.64-8.79c3.07-3.57,6.28-6.96,9.46-10.37
+				c0.55-0.59,0.8-1.08,0.6-1.97c-0.25-1.07-0.51-2.3-0.51-3.32c0-0.28-0.06-1.69-0.06-2.54c0-2.13-0.05-4.26,0.01-6.38
+				c0.05-1.69-0.18-3.32-0.59-4.9c-0.26-0.98-0.17-1.85,0.28-2.63c0.93-1.63,1.88-3.24,2.89-4.8c0.8-1.23,1.68-2.38,2.53-3.56
+				c-0.03-0.07-0.06-0.14-0.09-0.21c-0.32,0.15-0.68,0.24-0.95,0.47c-3.3,2.93-6.6,5.86-9.87,8.85c-0.7,0.64-1.37,1.06-2.25,0.91
+				c-0.3-0.05-0.62-0.01-1.03-0.01c-0.25-1.67-0.5-3.32-0.71-4.98c-0.03-0.27,0.08-0.67,0.25-0.87c2.01-2.38,4.24-4.34,7.06-5.02
+				c0.5-0.12,1.01-0.17,1.6-0.27c-0.16-0.23-0.29-0.41-0.42-0.58c-0.34-0.45-0.36-0.96-0.07-1.41c1.89-2.87,3.82-5.71,6.35-7.8
+				c0.56-0.47,1.23-0.76,1.86-1.08c0.41-0.21,0.75-0.11,1,0.44c0.28,0.62,0.64,1.18,0.96,1.77c0.34,0.64,0.36,1.24-0.13,1.82
+				c-0.58,0.68-0.57,1.16,0.01,1.86c0.87,1.06,1.74,2.11,2.62,3.18c-1.98,3.23-3.92,6.39-5.86,9.55c-0.96,1.57-1.94,3.13-2.88,4.72
+				c-0.16,0.27-0.29,0.63-0.3,0.95c-0.02,5.16-0.01,10.31-0.01,15.47c0,0.13,0.03,0.26,0.05,0.38c0.06,0.06,0.13,0.12,0.19,0.19
+				c0.13-0.25,0.23-0.54,0.4-0.75c4.74-5.86,9.43-11.77,14.24-17.54c2.08-2.5,4.22-5.04,6.64-6.96c2.58-2.05,4.19-5.05,6.21-7.65
+				c0.11-0.14,0.16-0.38,0.19-0.59c0.22-1.53,0.51-3.05,0.61-4.59c0.05-0.74-0.17-1.55-0.4-2.27c-0.25-0.76-0.21-1.41,0.12-2.08
+				c0.3-0.6,0.58-1.22,0.88-1.82c0.46-0.91,1.69-1.14,2.28-0.19c0.45,0.73,1.05,1.56,1.73,1.93
+				C484.46,197.65,484.52,197.85,484.35,197.92z M487.32,213.5c-1.37-0.22-1.35-1.58-1.53-2.78c-0.74,0.07-1.25,0.58-1.57,1.27
+				c-0.56,1.23-1.13,2.48-1.51,3.79c-0.55,1.88-0.94,3.83-1.42,5.85C484.5,220.42,487.28,216.65,487.32,213.5z"/>
+		</g>
+		<g>
+			
+				<linearGradient id="SVGID_00000131327206248398065760000005869725996311911043_" gradientUnits="userSpaceOnUse" x1="352.6551" y1="139.4055" x2="352.6551" y2="278.2872">
+				<stop  offset="0" style="stop-color:#DEA54E"/>
+				<stop  offset="0.1421" style="stop-color:#FCFADF"/>
+				<stop  offset="0.1594" style="stop-color:#FBF4CF"/>
+				<stop  offset="0.1982" style="stop-color:#F9E9B0"/>
+				<stop  offset="0.2409" style="stop-color:#F7E097"/>
+				<stop  offset="0.2882" style="stop-color:#F6DA83"/>
+				<stop  offset="0.3426" style="stop-color:#F5D575"/>
+				<stop  offset="0.4104" style="stop-color:#F4D26D"/>
+				<stop  offset="0.5334" style="stop-color:#F4D16B"/>
+				<stop  offset="0.8344" style="stop-color:#C26719"/>
+				<stop  offset="1" style="stop-color:#F4CD9C"/>
+			</linearGradient>
+			<path style="fill:url(#SVGID_00000131327206248398065760000005869725996311911043_);" d="M351.45,208.97
+				c-0.66-0.13-1.26-0.19-1.84-0.37c-0.51-0.16-0.96-0.49-1.08-1.2c-0.13-0.73,0.21-1.25,0.66-1.56c1.53-1.05,3.06-2.1,4.65-3.01
+				c0.57-0.32,0.74-0.73,0.82-1.38c0.41-3.09,0.82-6.18,1.24-9.27c0.03-0.24,0.1-0.47,0.14-0.7c1.55-0.38,2.77,0.35,3.82,1.61
+				c1.07,1.28,1.4,2.92,1.42,4.85c0.46-0.15,0.86-0.2,1.2-0.41c2.68-1.61,5.33-3.26,8.01-4.86c0.36-0.22,0.76-0.34,1.16-0.37
+				c1.1-0.06,2.2-0.06,3.3-0.01c0.31,0.02,0.77,0.23,1.01,0.48c0.66,0.69,1.26,1.45,1.95,2.26c-2.53,3.06-4.99,6.04-7.55,9.14
+				c1.11,0.13,2.08,0.19,3.02,0.35c0.42,0.07,0.84,0.21,1.24,0.4c1.09,0.53,1.2,0.98,0.49,1.58c-5.4,4.59-10.8,9.18-16.2,13.77
+				c-0.18,0.16-0.36,0.32-0.55,0.47c-0.8,0.65-1.07,1.55-0.78,2.77c0.21-0.14,0.44-0.27,0.66-0.42c3.61-2.48,7.23-4.96,10.83-7.46
+				c0.31-0.22,0.69-0.17,0.97,0.1c0.8,0.78,1.64,1.52,2.52,2.34c0.71,0.63-1.17,1.2-2.29,2.04c-2.38,1.78-4.58,3.81-6.89,5.72
+				c-0.39,0.32-0.63,0.87-0.59,1.45c0.04,0.57,0.01,1.15,0.01,1.78c0,0,1.07,0.03,1.46,0.08c0.56,0.07,0.55,2.63,0,2.74
+				c-0.96,0.18-4.49,0.94-4.49,0.94c0.78,0.94,1.41,1.83,2.16,2.54c0.24,0.23,0.83,0.15,1.15-0.05c1.05-0.67,2.03-1.48,3.06-2.18
+				c0.26-0.18,0.65-0.27,0.92-0.17c1.15,0.41,2.3,0.83,3.41,1.38c0.69,0.34,1.19,0.95,1.31,1.8c0.08,0.58-1.29,0.61-1.95,0.79
+				c-2.69,0.73-5.29,1.75-7.49,3.89c-2.21,2.16-3.31,4.97-3.29,8.62c0.41-0.12,0.79-0.14,1.11-0.32c1.5-0.88,3.01-1.77,4.48-2.74
+				c0.78-0.52,1.53-0.71,2.42-0.49c1.3,0.32,2.63,0.48,4.02,0.72c0,0.81,0.03,1.57-0.02,2.33c-0.01,0.2-0.26,0.5-0.44,0.54
+				c-4.26,1.09-8.11,3.41-11.86,5.99c-0.36,0.25-0.49,0.47-0.38,1.02c0.25,1.24,0.41,2.5,0.62,3.81c0.3,2.19,0.02,4.83-0.07,5.87
+				c-0.33,3.69-1.64,6.81-3.87,9.33c-0.67,0.76-1.23,0.75-1.64-0.29c-0.42-1.06-0.79-2.24-0.85-3.39
+				c-0.19-3.72-0.22-7.46-0.31-11.19c-0.02-0.95,0-1.9,0-3.02c-0.44,0.11-0.81,0.14-1.14,0.3c-0.55,0.28-1.12,0.58-1.61,0.99
+				c-0.75,0.61-1.47,0.61-2.25,0.16c-0.34-0.2-0.71-0.31-1.05-0.51c-1.77-1.06-1.47-1.4-1.46-3.53c0-0.17,0.11-0.35,0.2-0.5
+				c0.57-0.95,1.17-1.88,1.73-2.84c0.18-0.3,0.48-0.43,0.77-0.35c0.52,0.16,1.07,0.19,1.57,0.39c1.18,0.49,2.29,0.32,3.43-0.24
+				c0.47-0.23,0.63-0.52,0.55-1.08c-0.2-1.28-0.36-2.58-0.62-3.84c-0.14-0.65-0.13-1.24,0.28-1.63c0.33-0.32,0.46-0.49,0.9-0.86
+				c-1.9-0.89-1.73-2.9-2.08-4.83c0.47,0,0.87,0.02,1.28-0.01c0.99-0.07,1.77-1.1,1.77-2.31c0-1.3,0-2.61,0-4.09l-2.19,1.74
+				c-1.17,0.97-1.41-1.34-2.1-2.06c-0.23-0.24-0.28-0.66-0.1-0.96c1.17-1.89,2.55-4.05,3.73-5.96c0.88-1.44,1.78-2.86,2.64-4.33
+				c0.2-0.34,0.24-0.8,0.36-1.21c-0.07-0.06-0.13-0.12-0.2-0.18c-0.45,0.33-0.94,0.59-1.33,1c-2.41,2.59-4.86,5.14-7.19,7.84
+				c-2.8,3.25-5.48,6.64-8.23,9.94c-0.26,0.31-0.6,0.53-0.96,0.59c-2.05,0.36-2.98,1.73-3.28,4.68c-0.34,3.35-0.73,6.69-1.17,10.02
+				c-0.23,1.71-0.43,3.38,0.26,5c0.19,0.45,0.04,0.7-0.27,0.92c-0.31,0.22-0.63,0.43-0.89,0.73c-0.77,0.9-1.72,0.86-2.63,0.69
+				c-0.36-0.07-0.79-0.55-0.95-0.97c-0.24-0.65-0.24-1.42-0.4-2.13c-0.06-0.25-0.23-0.56-0.41-0.66c-0.57-0.3-1.17-0.51-1.82-0.78
+				c0.32-2.05,0.63-4.07,0.95-6.09c1.14-7.21,2.27-14.42,3.45-21.63c0.09-0.54,0.38-1.08,0.69-1.5c0.39-0.53,0.88-0.3,1.06,0.4
+				c0.52,2.05,1.03,4.11,1.59,6.14c0.19,0.69,0.5,1.33,0.8,1.97c0.22,0.48,0.48,0.5,0.85,0.1c3.61-3.95,7.23-7.87,10.85-11.8
+				c0.13-0.14,0.25-0.32,0.4-0.44c1.98-1.57,2.55-3.93,2.35-7.34c-0.14-2.42,0.43-4.75,1.04-7.05
+				C351.47,209.35,351.45,209.18,351.45,208.97z M366.64,198.96c-0.06-0.05-0.12-0.1-0.18-0.15c-0.53,0.28-1.06,0.54-1.58,0.84
+				c-3.52,2.04-6.26,5.07-7.86,9.48c-0.16,0.43-0.19,0.93-0.29,1.48c0.2-0.05,0.25-0.05,0.29-0.08c0.19-0.14,0.38-0.29,0.56-0.44
+				c3.37-2.8,6.15-6.36,8.78-10.13C366.53,199.71,366.55,199.3,366.64,198.96z"/>
+			
+				<linearGradient id="SVGID_00000052819573255971274660000006754478074310558641_" gradientUnits="userSpaceOnUse" x1="334.9821" y1="139.4055" x2="334.9821" y2="278.2872">
+				<stop  offset="0" style="stop-color:#DEA54E"/>
+				<stop  offset="0.1421" style="stop-color:#FCFADF"/>
+				<stop  offset="0.1594" style="stop-color:#FBF4CF"/>
+				<stop  offset="0.1982" style="stop-color:#F9E9B0"/>
+				<stop  offset="0.2409" style="stop-color:#F7E097"/>
+				<stop  offset="0.2882" style="stop-color:#F6DA83"/>
+				<stop  offset="0.3426" style="stop-color:#F5D575"/>
+				<stop  offset="0.4104" style="stop-color:#F4D26D"/>
+				<stop  offset="0.5334" style="stop-color:#F4D16B"/>
+				<stop  offset="0.8344" style="stop-color:#C26719"/>
+				<stop  offset="1" style="stop-color:#F4CD9C"/>
+			</linearGradient>
+			<path style="fill:url(#SVGID_00000052819573255971274660000006754478074310558641_);" d="M340.21,199.73
+				c0.82-0.12,1.52-0.14,2.18,0.63c1.05,1.22,2.21,2.29,3.32,3.42c0.71,0.72,0.74,1.11,0.14,1.94c-2.31,3.17-4.63,6.34-6.95,9.51
+				c-3.62,4.96-7.25,9.92-10.87,14.88c-0.88,1.2-1.83,1.25-2.76,0.13c-0.52-0.63-1.06-1.24-1.58-1.86
+				c3.16-3.55,5.72-7.61,8.36-11.57c2.65-3.97,5.21-8.02,7.84-12.01c0.21-0.32,0.57-0.48,1-0.83
+				C340.68,202.72,340.45,201.25,340.21,199.73z"/>
+		</g>
+		<g>
+			
+				<linearGradient id="SVGID_00000104666638876684332600000004035634833883296189_" gradientUnits="userSpaceOnUse" x1="398.4344" y1="139.4055" x2="398.4344" y2="278.2872">
+				<stop  offset="0" style="stop-color:#DEA54E"/>
+				<stop  offset="0.1421" style="stop-color:#FCFADF"/>
+				<stop  offset="0.1594" style="stop-color:#FBF4CF"/>
+				<stop  offset="0.1982" style="stop-color:#F9E9B0"/>
+				<stop  offset="0.2409" style="stop-color:#F7E097"/>
+				<stop  offset="0.2882" style="stop-color:#F6DA83"/>
+				<stop  offset="0.3426" style="stop-color:#F5D575"/>
+				<stop  offset="0.4104" style="stop-color:#F4D26D"/>
+				<stop  offset="0.5334" style="stop-color:#F4D16B"/>
+				<stop  offset="0.8344" style="stop-color:#C26719"/>
+				<stop  offset="1" style="stop-color:#F4CD9C"/>
+			</linearGradient>
+			<path style="fill:url(#SVGID_00000104666638876684332600000004035634833883296189_);" d="M384.52,233.27
+				c-0.17-1.82-0.17-1.8,1.11-2.13c4.23-1.08,8.46-2.18,12.45-4.26c1.46-0.76,2.85-1.78,4.19-2.84c0.97-0.77,1.64-1.91,1.78-3.41
+				c0.02-0.24,0.19-0.48,0.33-0.68c1.94-2.67,2.64-5.87,2.67-9.34c0-0.32-0.22-0.69-0.4-0.97c-1.97-3.09-3.98-6.15-5.92-9.28
+				c-0.53-0.84-0.86-1.87-1.29-2.84c1.65-0.62,7.15,3.08,9.34,6.07c2.52,3.44,3.55,7.53,3.56,12.11c0.01,0.71,0.57,0.88,0.72,0
+				c0.14-1.39,0.89-2.26,1.81-2.94c1.72-1.27,3.63-1.9,5.56-2.48c0.13-0.04,0.31,0.01,0.44,0.08c0.9,0.54,1.79,1.09,2.83,1.73
+				c-0.41,0.73-0.76,1.44-1.19,2.07c-2.02,2.95-4.46,5.35-6.98,7.61c-3.76,3.36-7.61,6.57-11.39,9.89
+				c-0.52,0.46-1.02,1.06-1.35,1.73c-3.91,7.69-7.96,15.26-12.6,22.32c-1.41,2.14-2.93,4.18-4.4,6.26c-1.85,2.63-4.29,4.23-6.8,5.65
+				c-1.47,0.83-3.02,1.44-4.52,2.23c-0.65,0.34-0.96-0.15-1.19-0.62c-0.23-0.47-0.17-0.97,0.29-1.4c4.3-3.96,8.42-8.17,12.19-12.89
+				c3.5-4.39,6.71-9.07,8.89-14.64c0.71-1.8,1.22-3.73,1.75-5.63c0.35-1.24,0.2-1.31-0.75-1.83c-0.97-0.53-1.83-0.49-2.67,0.26
+				c-1.16,1.03-2.38,0.99-3.73,0.59c-1.02-0.31-2.11-0.48-3.17-0.43C385.57,233.31,384.6,233.81,384.52,233.27z"/>
+			
+				<linearGradient id="SVGID_00000006698553838715441150000006468526626636131720_" gradientUnits="userSpaceOnUse" x1="419.8334" y1="139.4055" x2="419.8334" y2="278.2872">
+				<stop  offset="0" style="stop-color:#DEA54E"/>
+				<stop  offset="0.1421" style="stop-color:#FCFADF"/>
+				<stop  offset="0.1594" style="stop-color:#FBF4CF"/>
+				<stop  offset="0.1982" style="stop-color:#F9E9B0"/>
+				<stop  offset="0.2409" style="stop-color:#F7E097"/>
+				<stop  offset="0.2882" style="stop-color:#F6DA83"/>
+				<stop  offset="0.3426" style="stop-color:#F5D575"/>
+				<stop  offset="0.4104" style="stop-color:#F4D26D"/>
+				<stop  offset="0.5334" style="stop-color:#F4D16B"/>
+				<stop  offset="0.8344" style="stop-color:#C26719"/>
+				<stop  offset="1" style="stop-color:#F4CD9C"/>
+			</linearGradient>
+			<path style="fill:url(#SVGID_00000006698553838715441150000006468526626636131720_);" d="M412.17,263.54
+				c0.24-0.55,0.37-1,0.59-1.35c1.6-2.44,3.24-4.85,4.83-7.3c0.86-1.33,0.97-2.83,0.48-4.4c-0.29-0.91-0.53-1.86-0.94-2.7
+				c-0.92-1.9-1.92-3.73-2.88-5.59c-0.09-0.18-0.19-0.35-0.4-0.74c0.53,0.11,0.92,0.14,1.28,0.28c3.34,1.23,6.05,3.69,8.37,6.77
+				c1.31,1.74,2.47,3.65,3.67,5.51c0.54,0.84,0.39,2.43-0.3,3.1c-0.59,0.57-1.21,1.16-1.9,1.51c-3.35,1.68-6.71,3.34-10.11,4.87
+				C414.14,263.81,413.24,263.54,412.17,263.54z"/>
+		</g>
+	</g>
+	<g>
+		
+			<linearGradient id="SVGID_00000047035691048151441870000014968092272306573230_" gradientUnits="userSpaceOnUse" x1="545.4971" y1="329.9934" x2="419.568" y2="310.3531">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<path style="fill:url(#SVGID_00000047035691048151441870000014968092272306573230_);" d="M499.16,318.17l-35.81,4.77
+			c-47.75,3.13-37.25-4.22-83.57-23.6c1.65,0.6,3.3,1.17,4.96,1.69c0.02,0,0.02,0,0.03,0.02c1.48,0.46,22.88,4.9,32.26,4.87
+			l31.65-0.1c16.17-0.05,32.15,3.56,46.73,10.55L499.16,318.17z"/>
+		
+			<linearGradient id="SVGID_00000063609354156613872810000017220577945889751464_" gradientUnits="userSpaceOnUse" x1="353.0473" y1="318.1414" x2="401.3137" y2="298.116">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.7601" style="stop-color:#E62C2E"/>
+		</linearGradient>
+		<path style="fill:url(#SVGID_00000063609354156613872810000017220577945889751464_);" d="M463.35,322.93l-52.5,7.41
+			c-4.24,0.59-8.47,0.94-12.71,1.02c-48.63-3.91-38.06-44.87-62.99-56.35c6.05,2.34,11.89,5.5,17.33,9.47l1.17,0.86
+			c8.07,5.88,16.87,10.58,26.13,13.99C426.1,318.71,415.59,326.06,463.35,322.93z"/>
+		
+			<linearGradient id="SVGID_00000044859097233475420150000017294217224159958176_" gradientUnits="userSpaceOnUse" x1="343.198" y1="344.8954" x2="328.8208" y2="282.5085">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<path style="fill:url(#SVGID_00000044859097233475420150000017294217224159958176_);" d="M398.14,331.36
+			c-16.02,0.34-31.98-2.93-46.66-9.64c-2.85-1.31-5.76-2.4-8.72-3.31c-42.53-17.41-34.23-57.3-72.63-38.62
+			c11.57-6.59,24.54-9.92,37.56-9.92c9.29,0,18.59,1.69,27.42,5.13c0.02,0,0.03,0.02,0.05,0.02
+			C360.08,286.49,349.51,327.45,398.14,331.36z"/>
+	</g>
+	<g>
+		
+			<linearGradient id="SVGID_00000136394704996483294990000018380544818187287462_" gradientUnits="userSpaceOnUse" x1="340.717" y1="141.6266" x2="491.3503" y2="156.9726">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000136394704996483294990000018380544818187287462_);" points="418.96,169.82 388.86,158.99 
+			393.13,126.74 		"/>
+		
+			<linearGradient id="SVGID_00000056406797688230079960000009773664603587219640_" gradientUnits="userSpaceOnUse" x1="451.2126" y1="177.6628" x2="347.0212" y2="33.8948">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000056406797688230079960000009773664603587219640_);" points="425.11,112.42 409.85,137.06 
+			393.13,126.74 		"/>
+		
+			<linearGradient id="SVGID_00000007428621917166539450000015583986995171762837_" gradientUnits="userSpaceOnUse" x1="443.1714" y1="183.4904" x2="338.98" y2="39.7224">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000007428621917166539450000015583986995171762837_);" points="396.23,107.67 393.13,126.74 
+			377.09,80 		"/>
+		
+			<linearGradient id="SVGID_00000095330560046905565680000012653014443888594062_" gradientUnits="userSpaceOnUse" x1="432.4759" y1="201.5028" x2="349.4862" y2="106.3978">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000095330560046905565680000012653014443888594062_);" points="393.13,126.74 333.06,117.24 
+			366.09,109.03 		"/>
+		
+			<linearGradient id="SVGID_00000007418647100218523120000011985711963450850992_" gradientUnits="userSpaceOnUse" x1="405.4063" y1="87.2944" x2="357.3491" y2="166.0437">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000007418647100218523120000011985711963450850992_);" points="393.13,126.74 360.97,178.47 
+			360.34,141.81 		"/>
+		
+			<linearGradient id="SVGID_00000052086401443861530350000009892287849444241566_" gradientUnits="userSpaceOnUse" x1="415.0266" y1="281.0163" x2="372.0174" y2="82.9315">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000052086401443861530350000009892287849444241566_);" points="393.13,126.74 366.09,109.03 
+			377.09,80 		"/>
+		
+			<linearGradient id="SVGID_00000054237655125153033930000000885783561814504864_" gradientUnits="userSpaceOnUse" x1="419.8904" y1="200.3626" x2="315.699" y2="56.5946">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000054237655125153033930000000885783561814504864_);" points="393.13,126.74 360.34,141.81 
+			333.06,117.24 		"/>
+		
+			<linearGradient id="SVGID_00000049906736961950545670000002369480488441995690_" gradientUnits="userSpaceOnUse" x1="414.494" y1="204.2735" x2="310.3026" y2="60.5055">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000049906736961950545670000002369480488441995690_);" points="393.13,126.74 388.86,158.99 
+			360.97,178.47 		"/>
+		
+			<linearGradient id="SVGID_00000032621984761622746590000016794674069364360327_" gradientUnits="userSpaceOnUse" x1="286.2739" y1="148.2623" x2="435.6957" y2="105.4549">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000032621984761622746590000016794674069364360327_);" points="425.11,112.42 393.13,126.74 
+			396.23,107.67 		"/>
+		
+			<linearGradient id="SVGID_00000108311198905665412900000004465620395145953712_" gradientUnits="userSpaceOnUse" x1="489.9962" y1="255.3554" x2="367.3289" y2="89.9818">
+			<stop  offset="0" style="stop-color:#9A0000"/>
+			<stop  offset="0.4774" style="stop-color:#E50029"/>
+			<stop  offset="0.5423" style="stop-color:#E81B22"/>
+			<stop  offset="0.7009" style="stop-color:#ED5814"/>
+			<stop  offset="0.8356" style="stop-color:#F18509"/>
+			<stop  offset="0.9395" style="stop-color:#F4A102"/>
+			<stop  offset="1" style="stop-color:#F5AB00"/>
+		</linearGradient>
+		<polygon style="fill:url(#SVGID_00000108311198905665412900000004465620395145953712_);" points="418.96,169.82 393.13,126.74 
+			409.85,137.06 		"/>
+	</g>
+	
+		<linearGradient id="SVGID_00000132778855163012709780000008730862617147263632_" gradientUnits="userSpaceOnUse" x1="359.893" y1="149.3366" x2="187.978" y2="282.8451">
+		<stop  offset="0" style="stop-color:#9A0000"/>
+		<stop  offset="0.4774" style="stop-color:#E50029"/>
+		<stop  offset="0.5423" style="stop-color:#E81B22"/>
+		<stop  offset="0.7009" style="stop-color:#ED5814"/>
+		<stop  offset="0.8356" style="stop-color:#F18509"/>
+		<stop  offset="0.9395" style="stop-color:#F4A102"/>
+		<stop  offset="1" style="stop-color:#F5AB00"/>
+	</linearGradient>
+	<path style="fill:url(#SVGID_00000132778855163012709780000008730862617147263632_);" d="M255.95,290.19
+		c0.99-0.92,1.96-1.79,3-2.64c-0.05,0.07-0.19,0.27-0.44,0.51c-0.41,0.51-1.16,1.36-2.18,2.42c-0.39,0.41-0.82,0.87-1.31,1.38
+		c-6.57,6.61-20.64,18.49-35.98,17.32l-0.24-0.02l-0.1-0.02c0,0-0.02,0-0.05,0c-0.07,0-0.24,0-0.46-0.02
+		c-3.34-0.15-22.1-2.3-23.77-30.46c0.46-10.59-0.62-39.6,36.32-83.25c32.04-37.86,68.28-60.56,99.08-75.7l25.59,23.87l1.07,35.79
+		c-55.44,12.28-124.59,33.92-135.28,92.85C216.48,298.24,242.58,302.62,255.95,290.19z"/>
+	
+		<linearGradient id="SVGID_00000124861145368272951150000002031596967736413349_" gradientUnits="userSpaceOnUse" x1="305.2405" y1="270.0725" x2="219.861" y2="336.3778">
+		<stop  offset="0" style="stop-color:#9A0000"/>
+		<stop  offset="0.7601" style="stop-color:#E62C2E"/>
+	</linearGradient>
+	<path style="fill:url(#SVGID_00000124861145368272951150000002031596967736413349_);" d="M342.77,318.4
+		c-6.71-2.04-13.64-3.05-20.55-3.05c-9.96,0-19.87,2.11-29.08,6.3c-2.74,1.24-5.43,2.67-8.02,4.26l-1.04,0.65l-7.75,4.8
+		c-26.73,16.57-58.06,6.2-72.81-16.31v-0.02c-0.51-0.75-0.99-1.53-1.45-2.33c-0.12-0.17-0.22-0.36-0.32-0.53
+		c-0.44-0.75-0.85-1.53-1.24-2.28c-4.85-9.4-6.54-20.21-6.08-31.23c1.67,28.16,20.43,30.31,23.77,30.46
+		c0.22,0.02,0.39,0.02,0.46,0.02c0.05,0.02,0.1,0.02,0.15,0.02l0.24,0.02c15.34,1.16,29.42-10.71,35.98-17.32
+		c0.48-0.51,0.92-0.97,1.31-1.38c1.02-1.07,1.77-1.91,2.18-2.42c0.24-0.24,0.39-0.44,0.44-0.51c0.02-0.02,0.02-0.02,0.02-0.02
+		c3.54-2.96,7.24-5.55,11.15-7.73C308.53,261.09,300.22,301,342.77,318.4z"/>
+</g>
+</svg>

+ 29 - 0
store/$u.mixin.js

@@ -0,0 +1,29 @@
+// $u.mixin.js
+
+import { mapState } from 'vuex'
+import store from "@/store"
+
+// 尝试将用户在根目录中的store/index.js的vuex的state变量,全部加载到全局变量中
+let $uStoreKey = [];
+try{
+	$uStoreKey = store.state ? Object.keys(store.state) : [];
+}catch(e){
+	
+}
+
+module.exports = {
+	created() {
+		// 将vuex方法挂在到$u中
+		// 使用方法为:如果要修改vuex的state中的user.name变量为"史诗" => this.$u.vuex('user.name', '史诗')
+		// 如果要修改vuex的state的version变量为1.0.1 => this.$u.vuex('version', '1.0.1')
+		this.$u.vuex = (name, value) => {
+			this.$store.commit('$uStore', {
+				name,value
+			})
+		}
+	},
+	computed: {
+		// 将vuex的state中的所有变量,解构到全局混入的mixin中
+		...mapState($uStoreKey)
+	}
+}

+ 87 - 0
store/index.js

@@ -0,0 +1,87 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+Vue.use(Vuex)
+
+let lifeData = {};
+
+try{
+	// 尝试获取本地是否存在lifeData变量,第一次启动APP时是不存在的
+	lifeData = uni.getStorageSync('lifeData');
+}catch(e){
+	
+}
+
+// 需要永久存储,且下次APP启动需要取出的,在state中的变量名
+
+let saveStateKeys = ['vuex_member_info', 'vuex_user_info','vuex_wechatOpenid'];
+
+// 保存变量到本地存储中
+const saveLifeData = function(key, value){
+	// 判断变量名是否在需要存储的数组中
+	if(saveStateKeys.indexOf(key) != -1) {
+		// 获取本地存储的lifeData对象,将变量添加到对象中
+		let tmp = uni.getStorageSync('lifeData');
+		// 第一次打开APP,不存在lifeData变量,故放一个{}空对象
+		tmp = tmp ? tmp : {};
+		tmp[key] = value;
+		// 执行这一步后,所有需要存储的变量,都挂载在本地的lifeData对象中
+		uni.setStorageSync('lifeData', tmp);
+	}
+}
+const store = new Vuex.Store({
+	state: {
+		// 如果上面从本地获取的lifeData对象下有对应的属性,就赋值给state中对应的变量
+		// 加上vuex_前缀,是防止变量名冲突,也让人一目了然
+		vuex_member_info: lifeData.vuex_member_info ? lifeData.vuex_member_info : {mobile:''},
+		vuex_user_info: lifeData.vuex_user_info ? lifeData.vuex_user_info : {},
+		vuex_wechatOpenid:lifeData.vuex_wechatOpenid ? lifeData.vuex_wechatOpenid : '',
+		cartGoods:[],//购物车商品
+		creditGoods:[],//积分商品
+		buyNowGoods:[],//立即购买商品
+		// vuex_version: '1.0.1',
+	},
+	mutations: {
+		$uStore(state, payload) {
+			// 判断是否多层级调用,state中为对象存在的情况,诸如user.info.score = 1
+			let nameArr = payload.name.split('.');
+			let saveKey = '';
+			let len = nameArr.length;
+			if(len >= 2) {
+				let obj = state[nameArr[0]];
+				for(let i = 1; i < len - 1; i ++) {
+					obj = obj[nameArr[i]];
+				}
+				obj[nameArr[len - 1]] = payload.value;
+				saveKey = nameArr[0];
+			} else {
+				// 单层级变量,在state就是一个普通变量的情况
+				state[payload.name] = payload.value;
+				saveKey = payload.name;
+			}
+			// 保存变量到本地,见顶部函数定义
+			saveLifeData(saveKey, state[saveKey])
+		}
+	},
+	actions: {
+		// 获取用户信息
+		getUserInfo({ commit },backFu) {
+			if(this.state.user_info && this.state.user_info.userId) {
+				Vue.prototype.$u.api.checkToken().then((data) => {
+					if(data && data.code ===200) {
+						backFu(this.state.user_info)
+					}
+				})
+			} else {
+			    Vue.prototype.$u.api.userInfo().then((data) => {
+					console.log(data)
+			    	if(data && data.code ===200) {
+						Vue.prototype.$u.vuex('user_info', data.data)
+			    		backFu(data.data)
+			    	}
+			    })	
+			}
+		},
+	}
+})
+
+export default store

+ 212 - 0
uni.scss

@@ -0,0 +1,212 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+
+/* 颜色变量 */
+@import '@/uni_modules/uview-ui/theme.scss';
+
+/* 行为相关颜色 */
+$uni-color-primary: #00A447;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+$uni-text-color-red:#FF3C3F;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+$uni-background-color:#F5F5F5;
+
+/* 边框颜色 */
+$uni-border-color:#eee;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;
+
+page{
+	padding-top: 0;
+	// &:has(.u-navbar){
+	// 	padding-top: 104px;
+	// }
+}
+// @import url("@/static/css/flex.scss");
+.u-flex {
+	display: flex;
+	flex-direction: row;
+	align-items: center;
+}
+
+.u-flex-wrap {
+	flex-wrap: wrap;
+}
+
+.u-flex-nowrap {
+	flex-wrap: nowrap;
+}
+
+.u-col-center {
+	align-items: center;
+}
+
+.u-col-top {
+	align-items: flex-start;
+}
+
+.u-col-bottom {
+	align-items: flex-end;
+}
+
+.u-row-center {
+	justify-content: center;
+}
+
+.u-row-left {
+	justify-content: flex-start;
+}
+
+.u-row-right {
+	justify-content: flex-end;
+}
+
+.u-row-between {
+	justify-content: space-between;
+}
+
+.u-row-around {
+	justify-content: space-around;
+}
+
+.u-text-left {
+	text-align: left;
+}
+
+.u-text-center {
+	text-align: center;
+}
+
+.u-text-right {
+	text-align: right;
+}
+
+.u-flex-col {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: column;
+}
+
+// 定义flex等分
+@for $i from 0 through 12 {
+	.u-flex-#{$i} {
+		flex: $i;
+	}
+}
+
+@mixin multi-ellipsis($line: 1) {
+  @if $line <= 0 {
+      $line: 1;
+  }
+
+  overflow: hidden;
+  text-overflow: ellipsis;
+  display: -webkit-box;
+  -webkit-line-clamp: $line;
+  -webkit-box-orient: vertical;
+}
+
+.ellipsis-1 {
+  @include multi-ellipsis(1);
+}
+
+.ellipsis-2 {
+  @include multi-ellipsis(2);
+}
+
+.tabs-wrap{
+	margin-bottom: 30rpx;
+	.more{
+		font-size: 24rpx;
+		color: #999;
+	}
+}
+
+
+.page-bg{
+	position: absolute;
+	z-index: -1;
+	left: 0;
+	top: 0;
+	right: 0;
+	.img{
+		width: 100%;
+	}
+}
+
+.pickup-info{
+	padding: 0 30rpx;
+	.item{
+		padding: 30rpx 0;
+		font-size: 30rpx;
+		font-weight: 400;
+		color: #333333;
+		line-height: 42rpx;
+		&:not(:last-of-type){
+			border-bottom: 1px solid #eee;
+		}
+	}
+}

+ 6 - 0
uni_modules/mescroll-uni/changelog.md

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

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

@@ -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);
+	}
+}

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

@@ -0,0 +1,400 @@
+<template>
+	<view 
+	class="mescroll-body mescroll-render-touch" 
+	:class="{'mescorll-sticky': sticky}"
+	:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+	@touchstart="wxsBiz.touchstartEvent" 
+	@touchmove="wxsBiz.touchmoveEvent" 
+	@touchend="wxsBiz.touchendEvent" 
+	@touchcancel="wxsBiz.touchendEvent"
+	:change:prop="wxsBiz.propObserver"
+	:prop="wxsProp"
+	>
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+					<view class="downwarp-tip">{{downText}}</view>
+				</view>
+			</view>
+	
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from "../mescroll-uni/wxs/renderjs.js";
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from "../mescroll-uni/mescroll-uni.js";
+	// 引入全局配置
+	import GlobalOption from "../mescroll-uni/mescroll-uni-option.js";
+	// 引入国际化工具类
+	import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
+	// 引入回到顶部组件
+	import MescrollTop from "../mescroll-uni/components/mescroll-top.vue";
+	// 引入兼容wxs(含renderjs)写法的mixins
+	import WxsMixin from "../mescroll-uni/wxs/mixins.js";
+	
+	/**
+	 * mescroll-body 基于page滚动的下拉刷新和上拉加载组件, 支持嵌套原生组件, 性能好
+	 * @property {Object} down 下拉刷新的参数配置
+	 * @property {Object} up 上拉加载的参数配置
+	 * @property {Object} i18n 国际化的参数配置
+	 * @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+	 * @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+	 * @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
+	 * @property {String, Number} height 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+	 * @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
+	 * @property {Boolean} sticky 是否支持sticky,默认false; 当值配置true时,需避免在mescroll-body标签前面加非定位的元素,否则下拉区域无法隐藏
+	 * @event {Function} init 初始化完成的回调 
+	 * @event {Function} down 下拉刷新的回调
+	 * @event {Function} up 上拉加载的回调 
+	 * @event {Function} emptyclick 点击empty配置的btnText按钮回调
+	 * @event {Function} topclick 点击回到顶部的按钮回调
+	 * @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
+	 * @example <mescroll-body ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-body>
+	 */
+	export default {
+		name: 'mescroll-body',
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		props: {
+			down: Object,
+			up: Object,
+			i18n: Object,
+			top: [String, Number],
+			topbar: [Boolean, String],
+			bottom: [String, Number],
+			safearea: Boolean,
+			height: [String, Number],
+			bottombar:{
+				type: Boolean,
+				default: true
+			},
+			sticky: Boolean
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		computed: {
+			// mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			minHeight(){
+				return this.toPx(this.height || '100%') + 'px'
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			padTop() {
+				return this.numTop + 'px';
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom);
+			},
+			padBottom() {
+				return this.numBottom + 'px';
+			},
+			// 是否为重置下拉的状态
+			isDownReset() {
+				return this.downLoadType === 3 || this.downLoadType === 4;
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			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%"则等于windowHeight的10%
+						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); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		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的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+			
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			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;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					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,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../mescroll-body/mescroll-body.css";
+	@import "../mescroll-uni/components/mescroll-down.css";
+	@import "../mescroll-uni/components/mescroll-up.css";
+</style>

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

@@ -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)}
+}

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

@@ -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 // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 是否在加载中
+		isDownLoading(){
+			return this.type === 3
+		},
+		// 旋转的角度
+		downRotate(){
+			return this.type === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
+		}
+	}
+};
+</script>
+
+<style>
+@import "../../../mescroll-uni/components/mescroll-down.css";
+@import "./mescroll-down.css";
+</style>

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

@@ -0,0 +1,360 @@
+<template>
+	<view 
+		class="mescroll-body mescroll-render-touch" 
+		:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+		:class="{'mescorll-sticky': sticky}"
+		@touchstart="wxsBiz.touchstartEvent" 
+		@touchmove="wxsBiz.touchmoveEvent" 
+		@touchend="wxsBiz.touchendEvent" 
+		@touchcancel="wxsBiz.touchendEvent"
+		:change:prop="wxsBiz.propObserver"
+		:prop="wxsProp"
+		>
+		
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
+					<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
+					<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
+					<view class="downwarp-mascot"></view>
+				</view>
+			</view>
+						
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				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失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			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%"则等于windowHeight的10%
+						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); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		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的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			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;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					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,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../../mescroll-body/mescroll-body.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+</style>

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

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

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

@@ -0,0 +1,437 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+						
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+							
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<image class="downwarp-slogan" src="https://www.mescroll.com/img/beibei/mescroll-slogan.jpg?v=1" mode="widthFix"/>
+							<view v-if="isDownLoading" class="downwarp-loading mescroll-rotate"></view>
+							<view v-else class="downwarp-progress" :style="{'transform':downRotate}"></view>
+							<view class="downwarp-mascot"></view>
+						</view>
+					</view>
+
+					<!-- 列表内容 -->
+					<slot></slot>
+
+					<!-- 空布局 -->
+					<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+					<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+					<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+				
+				<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			fixed: { // 是否通过fixed固定mescroll的高度, 默认true
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean // 是否禁止滚动
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : ''
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return this.downLoadType === 2 ? 'rotate(180deg)' : 'rotate(0deg)'
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						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();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			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;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-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,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+</style>

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

@@ -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);
+	}
+}

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

@@ -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 // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 是否在加载中
+		isDownLoading() {
+			return this.type === 3;
+		},
+		// 旋转的角度
+		downRotate() {
+			return this.type === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
+		},
+		// 文本提示
+		downText() {
+			switch (this.type) {
+				case 1:
+					return this.mOption.textInOffset;
+				case 2:
+					return this.mOption.textOutOffset;
+				case 3:
+					return this.mOption.textLoading;
+				case 4:
+					return this.mOption.textLoading;
+				default:
+					return this.mOption.textInOffset;
+			}
+		}
+	}
+};
+</script>
+
+<style>
+@import '../../../mescroll-uni/components/mescroll-down.css';
+@import './mescroll-down.css';
+</style>

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

@@ -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);
+	}
+}

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

@@ -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 // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 加载中
+		isUpLoading() {
+			return this.type === 1;
+		},
+		// 没有更多了
+		isUpNoMore() {
+			return this.type === 2;
+		}
+	}
+};
+</script>
+
+<style>
+@import '../../../mescroll-uni/components/mescroll-up.css';
+@import './mescroll-up.css';
+</style>

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

@@ -0,0 +1,380 @@
+<template>
+	<view 
+		class="mescroll-body mescroll-render-touch" 
+		:style="{'minHeight':minHeight, 'padding-top': padTop, 'padding-bottom': padBottom}" 
+		:class="{'mescorll-sticky': sticky}"
+		@touchstart="wxsBiz.touchstartEvent" 
+		@touchmove="wxsBiz.touchmoveEvent" 
+		@touchend="wxsBiz.touchendEvent" 
+		@touchcancel="wxsBiz.touchendEvent"
+		:change:prop="wxsBiz.propObserver"
+		:prop="wxsProp"
+		>
+		
+		<!-- 状态栏 -->
+		<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+		<view class="mescroll-body-content mescroll-wxs-content" :style="{ transform: translateY, transition: transition }" :change:prop="wxsBiz.callObserver" :prop="callProp">
+			<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+			<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+			<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+				<view class="downwarp-content">
+					<view v-if="isDownLoading" class="downwarp-progress"></view>
+					<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
+					<view class="downwarp-tip">{{ downText }}</view>
+				</view>
+			</view>
+			
+			<!-- 列表内容 -->
+			<slot></slot>
+
+			<!-- 空布局 -->
+			<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+			<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+			<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+			<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+				<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+				<view v-show="upLoadType===1">
+					<view class="upwarp-progress mescroll-rotate"></view>
+					<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+				</view>
+				<!-- 无数据 -->
+				<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+			</view>
+		</view>
+		
+		<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+		<!-- #ifdef H5 -->
+		<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+		<!-- #endif -->
+		
+		<!-- 适配iPhoneX -->
+		<view v-if="safearea" class="mescroll-safearea"></view>
+		
+		<!-- 回到顶部按钮 (fixed元素需写在transform外面,防止降级为absolute)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态:0(loading前),1(loading中),2(没有更多了,显示END文本提示),3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				windowHeight: 0, // 可使用窗口的高度
+				windowBottom: 0, // 可使用窗口的底部位置
+				statusBarHeight: 0 // 状态栏高度
+			};
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			height: [String, Number], // 指定mescroll最小高度,默认windowHeight,使列表不满屏仍可下拉
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				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失效,需注意把fixed元素写在mescroll之外
+			},
+			// 是否在加载中
+			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%"则等于windowHeight的10%
+						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); // 派发点击回到顶部按钮的回调
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		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的状态,避免下次inOffset不及时显示textInOffset
+							if(vm.downLoadType === 4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll);
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) {
+							// 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+					}
+				}
+			};
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({down: vm.down,up: vm.up})); // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption, true); // 传入true,标记body为滚动区域
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			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;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+			
+			// 因为使用的是page的scroll,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				if(typeof y === 'string'){
+					// 滚动到指定view (y为css选择器)
+					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,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	};
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+	@import "./components/mescroll-up.css";
+</style>

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

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

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

@@ -0,0 +1,462 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll"  :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+			
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+				
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<view v-if="isDownLoading" class="downwarp-progress"></view>
+							<view v-else class="downwarp-arrow" :style="{ transform: downRotate }"></view>
+							<view class="downwarp-tip">{{ downText }}</view>
+						</view>
+					</view>
+
+					<!-- 列表内容 -->
+					<slot></slot>
+
+					<!-- 空布局 -->
+					<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+					<!-- 上拉加载区域 (下拉刷新时不显示,支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+					<!-- <mescroll-up v-if="mescroll.optUp.use && downLoadType !== 3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+				
+				<!-- 底部是否偏移TabBar的高度(仅H5端生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="../../mescroll-uni/wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from '../../mescroll-uni/wxs/renderjs.js';
+	export default {
+		mixins: [renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	import MeScroll from '../../mescroll-uni/mescroll-uni.js';
+	import MescrollTop from '../../mescroll-uni/components/mescroll-top.vue';
+	import WxsMixin from '../../mescroll-uni/wxs/mixins.js';
+	import mescrollI18n from '../../mescroll-uni/mescroll-i18n.js';
+	import GlobalOption from './mescroll-uni-option.js';
+	
+	export default {
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		data() {
+			return {
+				mescroll: null, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		props: {
+			down: Object, // 下拉刷新的参数配置
+			up: Object, // 上拉加载的参数配置
+			i18n: Object, // 国际化的参数配置
+			top: [String, Number], // 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			topbar: [Boolean, String], // top的偏移量是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+			bottom: [String, Number], // 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			safearea: Boolean, // bottom的偏移量是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+			fixed: { // 是否通过fixed固定mescroll的高度, 默认true
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number], // 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+			bottombar:{ // 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效)
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean // 是否禁止滚动
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : ''
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : '' // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading() {
+				return this.downLoadType === 3;
+			},
+			// 旋转的角度
+			downRotate() {
+				return this.downLoadType === 2 ? 'rotate(-180deg)' : 'rotate(0deg)';
+			},
+			// 文本提示
+			downText() {
+				if(!this.mescroll) return "";
+				switch (this.downLoadType) {
+					case 1:
+						return this.mescroll.optDown.textInOffset;
+					case 2:
+						return this.mescroll.optDown.textOutOffset;
+					case 3:
+						return this.mescroll.optDown.textLoading;
+					case 4:
+						return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default:
+						return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						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();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({
+				'down': vm.down,
+				'up': vm.up
+			})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			// 挂载语言包
+			vm.mescroll.i18n = i18nOption;
+			// init回调mescroll对象
+			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;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-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,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "../../mescroll-uni/mescroll-uni.css";
+	@import "../../mescroll-uni/components/mescroll-down.css";
+	@import "../../mescroll-uni/components/mescroll-up.css";
+	@import "./components/mescroll-down.css";
+	@import "./components/mescroll-up.css";
+</style>

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

@@ -0,0 +1,116 @@
+<!--空布局:
+遵循easycom规范, 可作为独立的组件, 不使用mescroll的页面也能使用:
+<mescroll-empty v-if="isShowEmpty" :option="optEmpty" @emptyclick="emptyClick"></mescroll-empty>
+-->
+<template>
+	<view class="mescroll-empty" :class="{ 'empty-fixed': option.fixed }" :style="{ 'z-index': option.zIndex, top: option.top }">
+		<view> <image v-if="icon" class="empty-icon" :src="icon" mode="widthFix" /> </view>
+		<view v-if="tip" class="empty-tip">{{ tip }}</view>
+		<view v-if="btnText" class="empty-btn" @click="emptyClick">{{ btnText }}</view>
+	</view>
+</template>
+
+<script>
+// 引入全局配置
+import GlobalOption from '../mescroll-uni/mescroll-uni-option.js';
+// 引入国际化工具类
+import mescrollI18n from '../mescroll-uni/mescroll-i18n.js';
+export default {
+	props: {
+		// empty的配置项: 默认为GlobalOption.up.empty
+		option: {
+			type: Object,
+			default() {
+				return {};
+			}
+		}
+	},
+	// 使用computed获取配置,用于支持option的动态配置
+	computed: {
+		// 图标
+		icon() {
+			if (this.option.icon != null) { // 此处不使用短路求值, 用于支持传空串不显示图标
+				return this.option.icon
+			} else{
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				if (this.option.i18n) {
+					return this.option.i18n[i18nType].icon
+				} else{
+					return GlobalOption.i18n[i18nType].up.empty.icon || GlobalOption.up.empty.icon
+				}
+			}
+		},
+		// 文本提示
+		tip() {
+			if (this.option.tip != null) { // 支持传空串不显示文本提示
+				return this.option.tip
+			} else{
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				if (this.option.i18n) {
+					return this.option.i18n[i18nType].tip
+				} else{
+					return GlobalOption.i18n[i18nType].up.empty.tip || GlobalOption.up.empty.tip
+				}
+			}
+		},
+		// 按钮文本
+		btnText() {
+			if (this.option.i18n) {
+				let i18nType = mescrollI18n.getType() // 国际化配置
+				return this.option.i18n[i18nType].btnText
+			} else{
+				return this.option.btnText
+			}
+		}
+	},
+	methods: {
+		// 点击按钮
+		emptyClick() {
+			this.$emit('emptyclick');
+		}
+	}
+};
+</script>
+
+<style>
+/* 无任何数据的空布局 */
+.mescroll-empty {
+	box-sizing: border-box;
+	width: 100%;
+	padding: 100rpx 50rpx;
+	text-align: center;
+}
+
+.mescroll-empty.empty-fixed {
+	z-index: 99;
+	position: absolute; /*transform会使fixed失效,最终会降级为absolute */
+	top: 100rpx;
+	left: 0;
+}
+
+.mescroll-empty .empty-icon {
+	width: 280rpx;
+	height: 280rpx;
+}
+
+.mescroll-empty .empty-tip {
+	margin-top: 20rpx;
+	font-size: 24rpx;
+	color: gray;
+}
+
+.mescroll-empty .empty-btn {
+	display: inline-block;
+	margin-top: 40rpx;
+	min-width: 200rpx;
+	padding: 18rpx;
+	font-size: 28rpx;
+	border: 1rpx solid #e04b28;
+	border-radius: 60rpx;
+	color: #e04b28;
+}
+
+.mescroll-empty .empty-btn:active {
+	opacity: 0.75;
+}
+</style>

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

@@ -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);
+	}
+}

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

@@ -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, // 下拉状态(inOffset:1, outOffset:2, showLoading:3, endDownScroll:4)
+		rate: Number // 下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 是否在加载中
+		isDownLoading(){
+			return this.type === 3
+		},
+		// 旋转的角度
+		downRotate(){
+			return 'rotate(' + 360 * this.rate + 'deg)'
+		},
+		// 文本提示
+		downText(){
+			switch (this.type){
+				case 1: return this.mOption.textInOffset;
+				case 2: return this.mOption.textOutOffset;
+				case 3: return this.mOption.textLoading;
+				case 4: return this.mOption.textLoading;
+				default: return this.mOption.textInOffset;
+			}
+		}
+	}
+};
+</script>
+
+<style>
+@import "./mescroll-down.css";
+</style>

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

@@ -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: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption(){
+			return this.option || {}
+		},
+		// 优先显示左边
+		left(){
+			return this.mOption.left ? this.addUnit(this.mOption.left) : 'auto';
+		},
+		// 右边距离 (优先显示左边)
+		right() {
+			return this.mOption.left ? 'auto' : this.addUnit(this.mOption.right);
+		}
+	},
+	methods: {
+		addUnit(num){
+			if(!num) return 0;
+			if(typeof num === 'number') return num + 'rpx';
+			return num
+		},
+		toTopClick() {
+			this.$emit('input', false); // 使v-model生效
+			this.$emit('click'); // 派发点击事件
+		}
+	}
+};
+</script>
+
+<style>
+/* 回到顶部的按钮 */
+.mescroll-totop {
+	z-index: 9990;
+	position: fixed !important; /* 加上important避免编译到H5,在多mescroll中定位失效 */
+	right: 20rpx;
+	bottom: 120rpx;
+	width: 72rpx;
+	height: auto;
+	border-radius: 50%;
+	opacity: 0;
+	transition: opacity 0.5s; /* 过渡 */
+	margin-bottom: var(--window-bottom); /* css变量 */
+}
+
+/* 适配 iPhoneX */
+@supports (bottom: constant(safe-area-inset-bottom)) or (bottom: env(safe-area-inset-bottom)) {
+	.mescroll-totop-safearea {
+		margin-bottom: calc(var(--window-bottom) + constant(safe-area-inset-bottom)); /* window-bottom + 适配 iPhoneX */
+		margin-bottom: calc(var(--window-bottom) + env(safe-area-inset-bottom));
+	}
+}
+
+/* 显示 -- 淡入 */
+.mescroll-totop-in {
+	opacity: 1;
+}
+
+/* 隐藏 -- 淡出且不接收事件*/
+.mescroll-totop-out {
+	opacity: 0;
+	pointer-events: none;
+}
+</style>

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

@@ -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);
+	}
+}

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

@@ -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 // 上拉加载的状态:0(loading前),1(loading中),2(没有更多了)
+	},
+	computed: {
+		// 支付宝小程序需写成计算属性,prop定义default仍报错
+		mOption() {
+			return this.option || {};
+		},
+		// 加载中
+		isUpLoading() {
+			return this.type === 1;
+		},
+		// 没有更多了
+		isUpNoMore() {
+			return this.type === 2;
+		}
+	}
+};
+</script>
+
+<style>
+@import './mescroll-up.css';
+</style>

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

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

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

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

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

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

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

@@ -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);
+	}
+}

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

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

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

@@ -0,0 +1,477 @@
+<template>
+	<view class="mescroll-uni-warp">
+		<scroll-view :id="viewId" class="mescroll-uni" :class="{'mescroll-uni-fixed':isFixed}" :style="{'height':scrollHeight,'padding-top':padTop,'padding-bottom':padBottom,'top':fixedTop,'bottom':fixedBottom}" :scroll-top="scrollTop" :scroll-with-animation="scrollAnim" @scroll="scroll" :scroll-y='scrollable' :enable-back-to-top="true" :throttle="false">
+			<view class="mescroll-uni-content mescroll-render-touch"
+			@touchstart="wxsBiz.touchstartEvent" 
+			@touchmove="wxsBiz.touchmoveEvent" 
+			@touchend="wxsBiz.touchendEvent" 
+			@touchcancel="wxsBiz.touchendEvent"
+			:change:prop="wxsBiz.propObserver"
+			:prop="wxsProp">
+				<!-- 状态栏 -->
+				<view v-if="topbar&&statusBarHeight" class="mescroll-topbar" :style="{height: statusBarHeight+'px', background: topbar}"></view>
+		
+				<view class="mescroll-wxs-content" :style="{'transform': translateY, 'transition': transition}" :change:prop="wxsBiz.callObserver" :prop="callProp">
+					<!-- 下拉加载区域 (支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-down组件实现)-->
+					<!-- <mescroll-down :option="mescroll.optDown" :type="downLoadType" :rate="downRate"></mescroll-down> -->
+					<view v-if="mescroll.optDown.use" class="mescroll-downwarp" :style="{'background':mescroll.optDown.bgColor,'color':mescroll.optDown.textColor}">
+						<view class="downwarp-content">
+							<view class="downwarp-progress mescroll-wxs-progress" :class="{'mescroll-rotate': isDownLoading}" :style="{'border-color':mescroll.optDown.textColor, 'transform': downRotate}"></view>
+							<view class="downwarp-tip">{{downText}}</view>
+						</view>
+					</view>
+
+					<!-- 列表内容 -->
+					<slot></slot>
+
+					<!-- 空布局 -->
+					<mescroll-empty v-if="isShowEmpty" :option="mescroll.optUp.empty" @emptyclick="emptyClick"></mescroll-empty>
+
+					<!-- 上拉加载区域 (下拉刷新时不显示, 支付宝小程序子组件传参给子子组件仍报单项数据流的异常,暂时不通过mescroll-up组件实现)-->
+					<!-- <mescroll-up v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" :option="mescroll.optUp" :type="upLoadType"></mescroll-up> -->
+					<view v-if="mescroll.optUp.use && !isDownLoading && upLoadType!==3" class="mescroll-upwarp" :style="{'background':mescroll.optUp.bgColor,'color':mescroll.optUp.textColor}">
+						<!-- 加载中 (此处不能用v-if,否则android小程序快速上拉可能会不断触发上拉回调) -->
+						<view v-show="upLoadType===1">
+							<view class="upwarp-progress mescroll-rotate" :style="{'border-color':mescroll.optUp.textColor}"></view>
+							<view class="upwarp-tip">{{ mescroll.optUp.textLoading }}</view>
+						</view>
+						<!-- 无数据 -->
+						<view v-if="upLoadType===2" class="upwarp-nodata">{{ mescroll.optUp.textNoMore }}</view>
+					</view>
+				</view>
+			
+				<!-- 底部是否偏移TabBar的高度(默认仅在H5端的tab页生效) -->
+				<!-- #ifdef H5 -->
+				<view v-if="bottombar && windowBottom>0" class="mescroll-bottombar" :style="{height: windowBottom+'px'}"></view>
+				<!-- #endif -->
+				
+				<!-- 适配iPhoneX -->
+				<view v-if="safearea" class="mescroll-safearea"></view>
+			</view>
+		</scroll-view>
+
+		<!-- 回到顶部按钮 (fixed元素,需写在scroll-view外面,防止滚动的时候抖动)-->
+		<mescroll-top v-model="isShowToTop" :option="mescroll.optUp.toTop" @click="toTopClick"></mescroll-top>
+		
+		<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+		<!-- renderjs的数据载体,不可写在mescroll-downwarp内部,避免use为false时,载体丢失,无法更新数据 -->
+		<view :change:prop="renderBiz.propObserver" :prop="wxsProp"></view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<!-- 微信小程序, QQ小程序, app, h5使用wxs -->
+<!-- #ifdef MP-WEIXIN || MP-QQ || APP-PLUS || H5 -->
+<script src="./wxs/wxs.wxs" module="wxsBiz" lang="wxs"></script>
+<!-- #endif -->
+
+<!-- app, h5使用renderjs -->
+<!-- #ifdef APP-PLUS || H5 -->
+<script module="renderBiz" lang="renderjs">
+	import renderBiz from './wxs/renderjs.js';
+	export default {
+		mixins:[renderBiz]
+	}
+</script>
+<!-- #endif -->
+
+<script>
+	// 引入mescroll-uni.js,处理核心逻辑
+	import MeScroll from './mescroll-uni.js';
+	// 引入全局配置
+	import GlobalOption from './mescroll-uni-option.js';
+	// 引入国际化工具类
+	import mescrollI18n from './mescroll-i18n.js';
+	// 引入回到顶部组件
+	import MescrollTop from './components/mescroll-top.vue';
+	// 引入兼容wxs(含renderjs)写法的mixins
+	import WxsMixin from './wxs/mixins.js';
+	
+	/**
+	 * mescroll-uni 嵌在页面某个区域的下拉刷新和上拉加载组件, 如嵌在弹窗,浮层,swiper中...
+	 * @property {Object} down 下拉刷新的参数配置
+	 * @property {Object} up 上拉加载的参数配置
+	 * @property {Object} i18n 国际化的参数配置
+	 * @property {String, Number} top 下拉布局往下的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean, String} topbar 偏移量top是否加上状态栏高度, 默认false (使用场景:取消原生导航栏时,配置此项可留出状态栏的占位, 支持传入字符串背景,如色值,背景图,渐变)
+	 * @property {String, Number} bottom 上拉布局往上的偏移量 (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} safearea 偏移量bottom是否加上底部安全区的距离, 默认false (需要适配iPhoneX时使用)
+	 * @property {Boolean} fixed 是否通过fixed固定mescroll的高度, 默认true
+	 * @property {String, Number} height 指定mescroll的高度, 此项有值,则不使用fixed. (支持20, "20rpx", "20px", "20%"格式的值, 其中纯数字则默认单位rpx, 百分比则相对于windowHeight)
+	 * @property {Boolean} bottombar 底部是否偏移TabBar的高度 (仅在H5端的tab页生效)
+	 * @property {Boolean} disableScroll 是否禁止滚动, 默认false
+	 * @event {Function} init 初始化完成的回调 
+	 * @event {Function} down 下拉刷新的回调
+	 * @event {Function} up 上拉加载的回调 
+	 * @event {Function} emptyclick 点击empty配置的btnText按钮回调
+	 * @event {Function} topclick 点击回到顶部的按钮回调
+	 * @event {Function} scroll 滚动监听 (需在 up 配置 onScroll:true 才生效)
+	 * @example <mescroll-uni ref="mescrollRef" @init="mescrollInit" @down="downCallback" @up="upCallback"> ... </mescroll-uni>
+	 */
+	export default {
+		name: 'mescroll-uni',
+		mixins: [WxsMixin],
+		components: {
+			MescrollTop
+		},
+		props: {
+			down: Object,
+			up: Object,
+			i18n: Object,
+			top: [String, Number],
+			topbar: [Boolean, String],
+			bottom: [String, Number],
+			safearea: Boolean,
+			fixed: {
+				type: Boolean,
+				default: true
+			},
+			height: [String, Number],
+			bottombar:{
+				type: Boolean,
+				default: true
+			},
+			disableScroll: Boolean
+		},
+		data() {
+			return {
+				mescroll: {optDown:{},optUp:{}}, // mescroll实例
+				viewId: 'id_' + Math.random().toString(36).substr(2,16), // 随机生成mescroll的id(不能数字开头,否则找不到元素)
+				downHight: 0, //下拉刷新: 容器高度
+				downRate: 0, // 下拉比率(inOffset: rate<1; outOffset: rate>=1)
+				downLoadType: 0, // 下拉刷新状态: 0(loading前), 1(inOffset), 2(outOffset), 3(showLoading), 4(endDownScroll)
+				upLoadType: 0, // 上拉加载状态: 0(loading前), 1loading中, 2没有更多了,显示END文本提示, 3(没有更多了,不显示END文本提示)
+				isShowEmpty: false, // 是否显示空布局
+				isShowToTop: false, // 是否显示回到顶部按钮
+				scrollTop: 0, // 滚动条的位置
+				scrollAnim: false, // 是否开启滚动动画
+				windowTop: 0, // 可使用窗口的顶部位置
+				windowBottom: 0, // 可使用窗口的底部位置
+				windowHeight: 0, // 可使用窗口的高度
+				statusBarHeight: 0 // 状态栏高度
+			}
+		},
+		computed: {
+			// 是否使用fixed定位 (当height有值,则不使用)
+			isFixed(){
+				return !this.height && this.fixed
+			},
+			// mescroll的高度
+			scrollHeight(){
+				if (this.isFixed) {
+					return "auto"
+				} else if(this.height){
+					return this.toPx(this.height) + 'px'
+				}else{
+					return "100%"
+				}
+			},
+			// 下拉布局往下偏移的距离 (px)
+			numTop() {
+				return this.toPx(this.top)
+			},
+			fixedTop() {
+				return this.isFixed ? (this.numTop + this.windowTop) + 'px' : 0
+			},
+			padTop() {
+				return !this.isFixed ? this.numTop + 'px' : 0
+			},
+			// 上拉布局往上偏移 (px)
+			numBottom() {
+				return this.toPx(this.bottom)
+			},
+			fixedBottom() {
+				return this.isFixed ? (this.numBottom + this.windowBottom) + 'px' : 0
+			},
+			padBottom() {
+				return !this.isFixed ? this.numBottom + 'px' : 0
+			},
+			// 是否为重置下拉的状态
+			isDownReset(){
+				return this.downLoadType===3 || this.downLoadType===4
+			},
+			// 过渡
+			transition() {
+				return this.isDownReset ? 'transform 300ms' : '';
+			},
+			translateY() {
+				return this.downHight > 0 ? 'translateY(' + this.downHight + 'px)' : ''; // transform会使fixed失效,需注意把fixed元素写在mescroll之外
+			},
+			// 列表是否可滑动
+			scrollable(){
+				if(this.disableScroll) return false
+				return this.downLoadType===0 || this.isDownReset
+			},
+			// 是否在加载中
+			isDownLoading(){
+				return this.downLoadType === 3
+			},
+			// 旋转的角度
+			downRotate(){
+				return 'rotate(' + 360 * this.downRate + 'deg)'
+			},
+			// 文本提示
+			downText(){
+				if(!this.mescroll) return ""; // 避免头条小程序初始化时报错
+				switch (this.downLoadType){
+					case 1: return this.mescroll.optDown.textInOffset;
+					case 2: return this.mescroll.optDown.textOutOffset;
+					case 3: return this.mescroll.optDown.textLoading;
+					case 4: return this.mescroll.isDownEndSuccess ? this.mescroll.optDown.textSuccess : this.mescroll.isDownEndSuccess==false ? this.mescroll.optDown.textErr : this.mescroll.optDown.textInOffset;
+					default: return this.mescroll.optDown.textInOffset;
+				}
+			}
+		},
+		methods: {
+			//number,rpx,upx,px,% --> px的数值
+			toPx(num){
+				if(typeof num === "string"){
+					if (num.indexOf('px') !== -1) {
+						if(num.indexOf('rpx') !== -1) { // "10rpx"
+							num = num.replace('rpx', '');
+						} else if(num.indexOf('upx') !== -1) { // "10upx"
+							num = num.replace('upx', '');
+						} else { // "10px"
+							return Number(num.replace('px', ''))
+						}
+					}else if (num.indexOf('%') !== -1){
+						// 传百分比,则相对于windowHeight,传"10%"则等于windowHeight的10%
+						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();
+			}
+		},
+		// 使用created初始化mescroll对象; 如果用mounted部分css样式编译到H5会失效
+		created() {
+			let vm = this;
+
+			let diyOption = {
+				// 下拉刷新的配置
+				down: {
+					inOffset() {
+						vm.downLoadType = 1; // 下拉的距离进入offset范围内那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					outOffset() {
+						vm.downLoadType = 2; // 下拉的距离大于offset那一刻的回调 (自定义mescroll组件时,此行不可删)
+					},
+					onMoving(mescroll, rate, downHight) {
+						// 下拉过程中的回调,滑动过程一直在执行;
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downRate = rate; //下拉比率 (inOffset: rate<1; outOffset: rate>=1)
+					},
+					showLoading(mescroll, downHight) {
+						vm.downLoadType = 3; // 显示下拉刷新进度的回调 (自定义mescroll组件时,此行不可删)
+						vm.downHight = downHight; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+					},
+					beforeEndDownScroll(mescroll){
+						vm.downLoadType = 4; 
+						return mescroll.optDown.beforeEndDelay // 延时结束的时长
+					},
+					endDownScroll() {
+						vm.downLoadType = 4; // 结束下拉 (自定义mescroll组件时,此行不可删)
+						vm.downHight = 0; // 设置下拉区域的高度 (自定义mescroll组件时,此行不可删)
+						vm.downResetTimer && clearTimeout(vm.downResetTimer)
+						vm.downResetTimer = setTimeout(()=>{ // 过渡动画执行完毕后,需重置为0的状态,以便置空this.transition,避免iOS小程序列表渲染不完整
+							if(vm.downLoadType===4) vm.downLoadType = 0
+						},300)
+					},
+					// 派发下拉刷新的回调
+					callback: function(mescroll) {
+						vm.$emit('down', mescroll)
+					}
+				},
+				// 上拉加载的配置
+				up: {
+					// 显示加载中的回调
+					showLoading() {
+						vm.upLoadType = 1;
+					},
+					// 显示无更多数据的回调
+					showNoMore() {
+						vm.upLoadType = 2;
+					},
+					// 隐藏上拉加载的回调
+					hideUpScroll(mescroll) {
+						vm.upLoadType = mescroll.optUp.hasNext ? 0 : 3;
+					},
+					// 空布局
+					empty: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowEmpty = isShow;
+						}
+					},
+					// 回到顶部
+					toTop: {
+						onShow(isShow) { // 显示隐藏的回调
+							vm.isShowToTop = isShow;
+						}
+					},
+					// 派发上拉加载的回调
+					callback: function(mescroll) {
+						vm.$emit('up', mescroll);
+						// 更新容器的高度 (多mescroll的情况)
+						vm.setClientHeight()
+					}
+				}
+			}
+
+			let i18nType = mescrollI18n.getType() // 当前语言类型
+			let i18nOption = {type: i18nType} // 国际化配置
+			MeScroll.extend(i18nOption, vm.i18n) // 具体页面的国际化配置
+			MeScroll.extend(i18nOption, GlobalOption.i18n) // 全局的国际化配置
+			MeScroll.extend(diyOption, i18nOption[i18nType]); // 混入国际化配置
+			MeScroll.extend(diyOption, {down:GlobalOption.down, up:GlobalOption.up}); // 混入全局的配置
+			let myOption = JSON.parse(JSON.stringify({'down': vm.down,'up': vm.up})) // 深拷贝,避免对props的影响
+			MeScroll.extend(myOption, diyOption); // 混入具体界面的配置
+
+			// 初始化MeScroll对象
+			vm.mescroll = new MeScroll(myOption);
+			vm.mescroll.viewId = vm.viewId; // 附带id
+			vm.mescroll.i18n = i18nOption; // 挂载语言包
+			// init回调mescroll对象
+			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;
+			// 使down的bottomOffset生效
+			vm.mescroll.setBodyHeight(sys.windowHeight);
+
+			// 因为使用的是scrollview,这里需自定义scrollTo
+			vm.mescroll.resetScrollTo((y, t) => {
+				vm.scrollAnim = (t !== 0); // t为0,则不使用动画过渡
+				if(typeof y === 'string'){
+					// 小程序不支持slot里面的scroll-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,则取本vue的safearea值
+			if (vm.up && vm.up.toTop && vm.up.toTop.safearea != null) {} else {
+				vm.mescroll.optUp.toTop.safearea = vm.safearea;
+			}
+			
+			// 全局配置监听
+			uni.$on("setMescrollGlobalOption", options=>{
+				if(!options) return;
+				let i18nType = options.i18n ? options.i18n.type : null
+				if(i18nType && vm.mescroll.i18n.type != i18nType){
+					vm.mescroll.i18n.type = i18nType
+					mescrollI18n.setType(i18nType)
+					MeScroll.extend(options, vm.mescroll.i18n[i18nType])
+				}
+				if(options.down){
+					let down = MeScroll.extend({}, options.down)
+					vm.mescroll.optDown = MeScroll.extend(down, vm.mescroll.optDown)
+				}
+				if(options.up){
+					let up = MeScroll.extend({}, options.up)
+					vm.mescroll.optUp = MeScroll.extend(up, vm.mescroll.optUp)
+				}
+			})
+		},
+		mounted() {
+			// 设置容器的高度
+			this.setClientHeight()
+		},
+		destroyed() {
+			// 注销全局配置监听
+			uni.$off("setMescrollGlobalOption")
+		}
+	}
+</script>
+
+<style>
+	@import "./mescroll-uni.css";
+	@import "./components/mescroll-down.css";
+	@import './components/mescroll-up.css';
+</style>

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

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

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

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

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

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

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

@@ -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;

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

@@ -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;

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

@@ -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
+}

+ 80 - 0
uni_modules/mescroll-uni/package.json

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

+ 45 - 0
uni_modules/mescroll-uni/readme.md

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

+ 21 - 0
uni_modules/uview-ui/LICENSE

@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2020 www.uviewui.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.

+ 66 - 0
uni_modules/uview-ui/README.md

@@ -0,0 +1,66 @@
+<p align="center">
+    <img alt="logo" src="https://uviewui.com/common/logo.png" width="120" height="120" style="margin-bottom: 10px;">
+</p>
+<h3 align="center" style="margin: 30px 0 30px;font-weight: bold;font-size:40px;">uView 2.0</h3>
+<h3 align="center">多平台快速开发的UI框架</h3>
+
+[![stars](https://img.shields.io/github/stars/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![forks](https://img.shields.io/github/forks/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0)
+[![issues](https://img.shields.io/github/issues/umicro/uView2.0?style=flat-square&logo=GitHub)](https://github.com/umicro/uView2.0/issues)
+[![Website](https://img.shields.io/badge/uView-up-blue?style=flat-square)](https://uviewui.com)
+[![release](https://img.shields.io/github/v/release/umicro/uView2.0?style=flat-square)](https://gitee.com/umicro/uView2.0/releases)
+[![license](https://img.shields.io/github/license/umicro/uView2.0?style=flat-square)](https://en.wikipedia.org/wiki/MIT_License)
+
+## 说明
+
+uView UI,是[uni-app](https://uniapp.dcloud.io/)全面兼容nvue的uni-app生态框架,全面的组件和便捷的工具会让您信手拈来,如鱼得水
+
+## [官方文档:https://uviewui.com](https://uviewui.com)
+
+
+## 预览
+
+您可以通过**微信**扫码,查看最佳的演示效果。
+<br>
+<br>
+<img src="https://uviewui.com/common/weixin_mini_qrcode.png" width="220" height="220" >
+
+
+## 链接
+
+- [官方文档](https://www.uviewui.com/)
+- [更新日志](https://www.uviewui.com/components/changelog.html)
+- [升级指南](https://www.uviewui.com/components/changeGuide.html)
+- [关于我们](https://www.uviewui.com/cooperation/about.html)
+
+## 交流反馈
+
+欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uviewui.com/components/addQQGroup.html)
+
+## 关于PR
+
+> 我们非常乐意接受各位的优质PR,但在此之前我希望您了解uView2.0是一个需要兼容多个平台的(小程序、h5、ios app、android app)包括nvue页面、vue页面。
+> 所以希望在您修复bug并提交之前尽可能的去这些平台测试一下兼容性。最好能携带测试截图以方便审核。非常感谢!
+
+## 安装
+
+#### **uni-app插件市场链接** —— [https://ext.dcloud.net.cn/plugin?id=1593](https://ext.dcloud.net.cn/plugin?id=1593)
+
+请通过[官网安装文档](https://www.uviewui.com/components/install.html)了解更详细的内容
+
+## 快速上手
+
+请通过[快速上手](https://uviewui.com/components/quickstart.html)了解更详细的内容
+
+## 使用方法
+配置easycom规则后,自动按需引入,无需`import`组件,直接引用即可。
+
+```html
+<template>
+	<u-button text="按钮"></u-button>
+</template>
+```
+
+## 版权信息
+uView遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uView应用到您的产品中。
+

+ 357 - 0
uni_modules/uview-ui/changelog.md

@@ -0,0 +1,357 @@
+## 2.0.34(2022-09-25)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. `u-input`、`u-textarea`增加`ignoreCompositionEvent`属性
+2. 修复`route`方法调用可能报错的问题
+3. 修复`u-no-network`组件`z-index`无效的问题
+4. 修复`textarea`组件在h5上confirmType=""报错的问题
+5. `u-rate`适配`nvue`
+6. 优化验证手机号码的正则表达式(根据工信部发布的《电信网编号计划(2017年版)》进行修改。)
+7. `form-item`添加`labelPosition`属性
+8. `u-calendar`修复`maxDate`设置为当前日期,并且当前时间大于08:00时无法显示日期列表的问题 (#724)
+9. `u-radio`增加一个默认插槽用于自定义修改label内容 (#680)
+10. 修复`timeFormat`函数在safari重的兼容性问题 (#664)
+## 2.0.33(2022-06-17)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`loadmore`组件`lineColor`类型错误问题
+2. 修复`u-parse`组件`imgtap`、`linktap`不生效问题
+## 2.0.32(2022-06-16)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+1. `u-loadmore`新增自定义颜色、虚/实线
+2. 修复`u-swiper-action`组件部分平台不能上下滑动的问题
+3. 修复`u-list`回弹问题
+4. 修复`notice-bar`组件动画在低端安卓机可能会抖动的问题
+5. `u-loading-page`添加控制图标大小的属性`iconSize`
+6. 修复`u-tooltip`组件`color`参数不生效的问题
+7. 修复`u--input`组件使用`blur`事件输出为`undefined`的bug
+8. `u-code-input`组件新增键盘弹起时,是否自动上推页面参数`adjustPosition`
+9. 修复`image`组件`load`事件无回调对象问题
+10. 修复`button`组件`loadingSize`设置无效问题
+10. 其他修复
+## 2.0.31(2022-04-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`upload`在`vue`页面上传成功后没有成功标志的问题
+2. 解决演示项目中微信小程序模拟上传图片一直出于上传中问题
+3. 修复`u-code-input`组件在`nvue`页面编译到`app`平台上光标异常问题(`app`去除此功能)
+4. 修复`actionSheet`组件标题关闭按钮点击事件名称错误的问题
+5. 其他修复
+## 2.0.30(2022-04-04)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. `u-rate`增加`readonly`属性
+2. `tabs`滑块支持设置背景图片
+3. 修复`u-subsection` `mode`为`subsection`时,滑块样式不正确的问题
+4. `u-code-input`添加光标效果动画
+5. 修复`popup`的`open`事件不触发
+6. 修复`u-flex-column`无效的问题
+7. 修复`u-datetime-picker`索引在特定场合异常问题
+8. 修复`u-datetime-picker`最小时间字符串模板错误问题
+9. `u-swiper`添加`m3u8`验证
+10. `u-swiper`修改判断image和video逻辑
+11. 修复`swiper`无法使用本地图片问题,增加`type`参数
+12. 修复`u-row-notice`格式错误问题
+13. 修复`u-switch`组件当`unit`为`rpx`时,`nodeStyle`消失的问题
+14. 修复`datetime-picker`组件`showToolbar`与`visibleItemCount`属性无效的问题
+15. 修复`upload`组件条件编译位置判断错误,导致`previewImage`属性设置为`false`时,整个组件都会被隐藏的问题
+16. 修复`u-checkbox-group`设置`shape`属性无效的问题
+17. 修复`u-upload`的`capture`传入字符串的时候不生效的问题
+18. 修复`u-action-sheet`组件,关闭事件逻辑错误的问题
+19. 修复`u-list`触顶事件的触发错误的问题
+20. 修复`u-text`只有手机号可拨打的问题
+21. 修复`u-textarea`不能换行的问题
+22. 其他修复
+## 2.0.29(2022-03-13)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复`u--text`组件设置`decoration`属性未生效的问题
+2. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+3. 修复`u-datetime-picker` `intercept` 可能为undefined
+4. 修复已设置单位 uni..config.unit = 'rpx'时,线型指示器 `transform` 的位置翻倍,导致指示器超出宽度
+5. 修复mixin中bem方法生成的类名在支付宝和字节小程序中失效
+6. 修复默认值传值为空的时候,打开`u-datetime-picker`报错,不能选中第一列时间的bug
+7. 修复`u-datetime-picker`使用`formatter`后返回值不正确
+8. 修复`u-image`组件`loading`无效果的问题
+9. 修复`config.unit`属性设为`rpx`时,导航栏占用高度不足导致塌陷的问题
+10. 修复`u-datetime-picker`组件`itemHeight`无效问题
+11. 其他修复
+## 2.0.28(2022-02-22)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. search组件新增searchIconSize属性
+2. 兼容Safari/Webkit中传入时间格式如2022-02-17 12:00:56
+3. 修复text value.js 判断日期出format错误问题
+4. priceFormat格式化金额出现精度错误
+5. priceFormat在部分情况下出现精度损失问题
+6. 优化表单rules提示
+7. 修复avatar组件src为空时,展示状态不对
+8. 其他修复
+## 2.0.27(2022-01-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1.样式修复
+## 2.0.26(2022-01-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1.样式修复
+## 2.0.25(2022-01-27)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复text组件mode=price时,可能会导致精度错误的问题
+2. 添加$u.setConfig()方法,可设置uView内置的config, props, zIndex, color属性,详见:[修改uView内置配置方案](https://uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+3. 优化form组件在errorType=toast时,如果输入错误页面会有抖动的问题
+4. 修复$u.addUnit()对配置默认单位可能无效的问题
+## 2.0.24(2022-01-25)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复swiper在current指定非0时缩放有误
+2. 修复u-icon添加stop属性的时候报错
+3. 优化遗留的通过正则判断rpx单位的问题
+4. 优化Layout布局 vue使用gutter时,会超出固定区域
+5. 优化search组件高度单位问题(rpx -> px)
+6. 修复u-image slot 加载和错误的图片失去了高度
+7. 修复u-index-list中footer插槽与header插槽存在性判断错误
+8. 修复部分机型下u-popup关闭时会闪烁
+9. 修复u-image在nvue-app下失去宽高
+10. 修复u-popup运行报错
+11. 修复u-tooltip报错
+12. 修复box-sizing在app下的警告
+13. 修复u-navbar在小程序中报运行时错误
+14. 其他修复
+## 2.0.23(2022-01-24)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复image组件在hx3.3.9的nvue下可能会显示异常的问题
+2. 修复col组件gutter参数带rpx单位处理不正确的问题
+3. 修复text组件单行时无法显示省略号的问题
+4. navbar添加titleStyle参数
+5. 升级到hx3.3.9可消除nvue下控制台样式警告的问题
+## 2.0.22(2022-01-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. $u.page()方法优化,避免在特殊场景可能报错的问题
+2. picker组件添加immediateChange参数
+3. 新增$u.pages()方法
+## 2.0.21(2022-01-19)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化:form组件在用户设置rules的时候提示用户model必传
+2. 优化遗留的通过正则判断rpx单位的问题
+3. 修复微信小程序环境中tabbar组件开启safeAreaInsetBottom属性后,placeholder高度填充不正确
+4. 修复swiper在current指定非0时缩放有误
+5. 修复u-icon添加stop属性的时候报错
+6. 修复upload组件在accept=all的时候没有作用
+7. 修复在text组件mode为phone时call属性无效的问题
+8. 处理u-form clearValidate方法
+9. 其他修复
+## 2.0.20(2022-01-14)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复calendar默认会选择一个日期,如果直接点确定的话,无法取到值的问题
+2. 修复Slider缺少disabled props 还有注释
+3. 修复u-notice-bar点击事件无法拿到index索引值的问题
+4. 修复u-collapse-item在vue文件下,app端自定义插槽不生效的问题
+5. 优化头像为空时显示默认头像 
+6. 修复图片地址赋值后判断加载状态为完成问题
+7. 修复日历滚动到默认日期月份区域
+8. search组件暴露点击左边icon事件
+9. 修复u-form clearValidate方法不生效
+10. upload h5端增加返回文件参数(文件的name参数)
+11. 处理upload选择文件后url为blob类型无法预览的问题
+12. u-code-input 修复输入框没有往左移出一半屏幕
+13. 修复Upload上传 disabled为true时,控制台报hoverClass类型错误
+14. 临时处理ios app下grid点击坍塌问题
+15. 其他修复
+## 2.0.19(2021-12-29)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化微信小程序包体积可在微信中预览,请升级HbuilderX3.3.4,同时在“运行->运行到小程序模拟器”中勾选“运行时是否压缩代码”
+2. 优化微信小程序setData性能,处理某些方法如$u.route()无法在模板中使用的问题
+3. navbar添加autoBack参数
+4. 允许avatar组件的事件冒泡
+5. 修复cell组件报错问题
+6. 其他修复
+## 2.0.18(2021-12-28)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复app端编译报错问题
+2. 重新处理微信小程序端setData过大的性能问题
+3. 修复边框问题
+4. 修复最大最小月份不大于0则没有数据出现的问题
+5. 修复SwipeAction微信小程序端无法上下滑动问题
+6. 修复input的placeholder在小程序端默认显示为true问题
+7. 修复divider组件click事件无效问题
+8. 修复u-code-input maxlength 属性值为 String 类型时显示异常
+9. 修复当 grid只有 1到2时 在小程序端algin设置无效的问题
+10. 处理form-item的label为top时,取消错误提示的左边距
+11. 其他修复
+## 2.0.17(2021-12-26)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 解决HBuilderX3.3.3.20211225版本导致的样式问题
+2. calendar日历添加monthNum参数
+3. navbar添加center slot
+## 2.0.16(2021-12-25)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 解决微信小程序setData性能问题
+2. 修复count-down组件change事件不触发问题
+## 2.0.15(2021-12-21)
+## uView正在参与开源中国的“年度最佳项目”评选,之前投过票的现在也可以投票,恳请同学们投一票,[点此帮助uView](https://www.oschina.net/project/top_cn_2021/?id=583)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复Cell单元格titleWidth无效
+2. 修复cheakbox组件ischecked不更新
+3. 修复keyboard是否显示"."按键默认值问题
+4. 修复number-keyboard是否显示键盘的"."符号问题
+5. 修复Input输入框 readonly无效
+6. 修复u-avatar 导致打包app、H5时候报错问题
+7. 修复Upload上传deletable无效
+8. 修复upload当设置maxSize时无效的问题
+9. 修复tabs lineWidth传入带单位的字符串的时候偏移量计算错误问题
+10. 修复rate组件在有padding的view内,显示的星星位置和可触摸区域不匹配,无法正常选中星星
+## 2.0.13(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复配置默认单位为rpx可能会导致自定义导航栏高度异常的问题
+## 2.0.12(2021-12-14)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复tabs组件在vue环境下划线消失的问题
+2. 修复upload组件在安卓小程序无法选择视频的问题
+3. 添加uni.$u.config.unit配置,用于配置参数默认单位,详见:[默认单位配置](https://www.uviewui.com/components/setting.html#%E9%BB%98%E8%AE%A4%E5%8D%95%E4%BD%8D%E9%85%8D%E7%BD%AE)
+4. 修复textarea组件在没绑定v-model时,字符统计不生效问题
+5. 修复nvue下控制是否出现滚动条失效问题
+## 2.0.11(2021-12-13)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. text组件align参数无效的问题
+2. subsection组件添加keyName参数
+3. upload组件无法判断[Object file]类型的问题
+4. 处理notify层级过低问题
+5. codeInput组件添加disabledDot参数
+6. 处理actionSheet组件round参数无效的问题
+7. calendar组件添加round参数用于控制圆角值
+8. 处理swipeAction组件在vue环境下默认被打开的问题
+9. button组件的throttleTime节流参数无效的问题
+10. 解决u-notify手动关闭方法close()无效的问题
+11. input组件readonly不生效问题
+12. tag组件type参数为info不生效问题
+## 2.0.10(2021-12-08)
+## [点击加群交流反馈:364463526](https://jq.qq.com/?_chanwv=1027&k=mCxS3TGY)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复button sendMessagePath属性不生效
+2. 修复DatetimePicker选择器title无效
+3. 修复u-toast设置loading=true不生效
+4. 修复u-text金额模式传0报错
+5. 修复u-toast组件的icon属性配置不生效
+6. button的icon在特殊场景下的颜色优化
+7. IndexList优化,增加#
+## 2.0.9(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 优化swiper的height支持100%值(仅vue有效),修复嵌入视频时click事件无法触发的问题
+2. 优化tabs组件对list值为空的判断,或者动态变化list时重新计算相关尺寸的问题
+3. 优化datetime-picker组件逻辑,让其后续打开的默认值为上一次的选中值,需要通过v-model绑定值才有效
+4. 修复upload内嵌在其他组件中,选择图片可能不会换行的问题
+## 2.0.8(2021-12-01)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复toast的position参数无效问题
+2. 处理input在ios nvue上无法获得焦点的问题
+3. avatar-group组件添加extraValue参数,让剩余展示数量可手动控制
+4. tabs组件添加keyName参数用于配置从对象中读取的键名
+5. 处理text组件名字脱敏默认配置无效的问题
+6. 处理picker组件item文本太长换行问题
+## 2.0.7(2021-11-30)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复radio和checkbox动态改变v-model无效的问题。
+2. 优化form规则validator在微信小程序用法
+3. 修复backtop组件mode参数在微信小程序无效的问题
+4. 处理Album的previewFullImage属性无效的问题
+5. 处理u-datetime-picker组件mode='time'在选择改变时间时,控制台报错的问题
+## 2.0.6(2021-11-27)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 处理tag组件在vue下边框无效的问题。
+2. 处理popup组件圆角参数可能无效的问题。
+3. 处理tabs组件lineColor参数可能无效的问题。
+4. propgress组件在值很小时,显示异常的问题。
+## 2.0.5(2021-11-25)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. calendar在vue下显示异常问题。 
+2. form组件labelPosition和errorType参数无效的问题
+3. input组件inputAlign无效的问题
+4. 其他一些修复
+## 2.0.4(2021-11-23)
+## [点击加群交流反馈:232041042](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+0. input组件缺失@confirm事件,以及subfix和prefix无效问题
+1. component.scss文件样式在vue下干扰全局布局问题
+2. 修复subsection在vue环境下表现异常的问题
+3. tag组件的bgColor等参数无效的问题
+4. upload组件不换行的问题
+5. 其他的一些修复处理
+## 2.0.3(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 处理modal的confirm回调事件拼写错误问题
+6. 处理input组件@input事件参数错误问题
+7. 其他一些修复
+## 2.0.2(2021-11-16)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+6. 优化loading-icon组件的scss写法问题,防止不兼容新版本scss
+## 2.0.0(2020-11-15)
+## [点击加群交流反馈:1129077272](https://jq.qq.com/?_wv=1027&k=KnbeceDU)
+
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. uView2.0已实现全面兼容nvue
+2. uView2.0对1.x进行了架构重构,细节和性能都有极大提升
+3. 目前uView2.0为公测阶段,相关细节可能会有变动
+4. 我们写了一份与1.x的对比指南,详见[对比1.x](https://www.uviewui.com/components/diff1.x.html)
+5. 修复input组件formatter参数缺失问题
+
+

+ 78 - 0
uni_modules/uview-ui/components/u--form/u--form.vue

@@ -0,0 +1,78 @@
+<template>
+	<uvForm
+		ref="uForm"
+		:model="model"
+		:rules="rules"
+		:errorType="errorType"
+		:borderBottom="borderBottom"
+		:labelPosition="labelPosition"
+		:labelWidth="labelWidth"
+		:labelAlign="labelAlign"
+		:labelStyle="labelStyle"
+		:customStyle="customStyle"
+	>
+		<slot />
+	</uvForm>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-form被uni-app官方占用了,u-form在nvue中相当于form组件
+	 * 所以在nvue下,取名为u--form,内部其实还是u-form.vue,只不过做一层中转
+	 */
+	import uvForm from '../u-form/u-form.vue';
+	import props from '../u-form/props.js'
+	export default {
+		// #ifdef MP-WEIXIN
+		name: 'u-form',
+		// #endif
+		// #ifndef MP-WEIXIN
+		name: 'u--form',
+		// #endif
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvForm
+		},
+		created() {
+			this.children = []
+		},
+		methods: {
+			// 手动设置校验的规则,如果规则中有函数的话,微信小程序中会过滤掉,所以只能手动调用设置规则
+			setRules(rules) {
+				this.$refs.uForm.setRules(rules)
+			},
+			validate() {
+				/**
+				 * 在微信小程序中,通过this.$parent拿到的父组件是u--form,而不是其内嵌的u-form
+				 * 导致在u-form组件中,拿不到对应的children数组,从而校验无效,所以这里每次调用u-form组件中的
+				 * 对应方法的时候,在小程序中都先将u--form的children赋值给u-form中的children
+				 */
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validate()
+			},
+			validateField(value, callback) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.validateField(value, callback)
+			},
+			resetFields() {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.resetFields()
+			},
+			clearValidate(props) {
+				// #ifdef MP-WEIXIN
+				this.setMpData()
+				// #endif
+				return this.$refs.uForm.clearValidate(props)
+			},
+			setMpData() {
+				this.$refs.uForm.children = this.children
+			}
+		},
+	}
+</script>

+ 47 - 0
uni_modules/uview-ui/components/u--image/u--image.vue

@@ -0,0 +1,47 @@
+<template>
+	<uvImage 
+		:src="src"
+		:mode="mode"
+		:width="width"
+		:height="height"
+		:shape="shape"
+		:radius="radius"
+		:lazyLoad="lazyLoad"
+		:showMenuByLongpress="showMenuByLongpress"
+		:loadingIcon="loadingIcon"
+		:errorIcon="errorIcon"
+		:showLoading="showLoading"
+		:showError="showError"
+		:fade="fade"
+		:webp="webp"
+		:duration="duration"
+		:bgColor="bgColor"
+		:customStyle="customStyle"
+		@click="$emit('click')"
+		@error="$emit('error')"
+		@load="$emit('load')"
+	>
+		<template v-slot:loading>
+			<slot name="loading"></slot>
+		</template>
+		<template v-slot:error>
+			<slot name="error"></slot>
+		</template>
+	</uvImage>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-image被uni-app官方占用了,u-image在nvue中相当于image组件
+	 * 所以在nvue下,取名为u--image,内部其实还是u-iamge.vue,只不过做一层中转
+	 */
+	import uvImage from '../u-image/u-image.vue';
+	import props from '../u-image/props.js';
+	export default {
+		name: 'u--image',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvImage
+		},
+	}
+</script>

+ 73 - 0
uni_modules/uview-ui/components/u--input/u--input.vue

@@ -0,0 +1,73 @@
+<template>
+	<uvInput 
+		:value="value"
+		:type="type"
+		:fixed="fixed"
+		:disabled="disabled"
+		:disabledColor="disabledColor"
+		:clearable="clearable"
+		:password="password"
+		:maxlength="maxlength"
+		:placeholder="placeholder"
+		:placeholderClass="placeholderClass"
+		:placeholderStyle="placeholderStyle"
+		:showWordLimit="showWordLimit"
+		:confirmType="confirmType"
+		:confirmHold="confirmHold"
+		:holdKeyboard="holdKeyboard"
+		:focus="focus"
+		:autoBlur="autoBlur"
+		:disableDefaultPadding="disableDefaultPadding"
+		:cursor="cursor"
+		:cursorSpacing="cursorSpacing"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:inputAlign="inputAlign"
+		:fontSize="fontSize"
+		:color="color"
+		:prefixIcon="prefixIcon"
+		:suffixIcon="suffixIcon"
+		:suffixIconStyle="suffixIconStyle"
+		:prefixIconStyle="prefixIconStyle"
+		:border="border"
+		:readonly="readonly"
+		:shape="shape"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		:ignoreCompositionEvent="ignoreCompositionEvent"
+		@focus="$emit('focus')"
+		@blur="e => $emit('blur', e)"
+		@keyboardheightchange="$emit('keyboardheightchange')"
+		@change="e => $emit('change', e)"
+		@input="e => $emit('input', e)"
+		@confirm="e => $emit('confirm', e)"
+		@clear="$emit('clear')"
+		@click="$emit('click')"
+	>
+		<!-- #ifdef MP -->
+		<slot name="prefix"></slot>
+		<slot name="suffix"></slot>
+		<!-- #endif -->
+		<!-- #ifndef MP -->
+		<slot name="prefix" slot="prefix"></slot>
+		<slot name="suffix" slot="suffix"></slot>
+		<!-- #endif -->
+	</uvInput>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u-input被uni-app官方占用了,u-input在nvue中相当于input组件
+	 * 所以在nvue下,取名为u--input,内部其实还是u-input.vue,只不过做一层中转
+	 */
+	import uvInput from '../u-input/u-input.vue';
+	import props from '../u-input/props.js'
+	export default {
+		name: 'u--input',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvInput
+		},
+	}
+</script>

+ 44 - 0
uni_modules/uview-ui/components/u--text/u--text.vue

@@ -0,0 +1,44 @@
+<template>
+    <uvText
+        :type="type"
+        :show="show"
+        :text="text"
+        :prefixIcon="prefixIcon"
+        :suffixIcon="suffixIcon"
+        :mode="mode"
+        :href="href"
+        :format="format"
+        :call="call"
+        :openType="openType"
+        :bold="bold"
+        :block="block"
+        :lines="lines"
+        :color="color"
+		:decoration="decoration"
+        :size="size"
+        :iconStyle="iconStyle"
+        :margin="margin"
+        :lineHeight="lineHeight"
+        :align="align"
+        :wordWrap="wordWrap"
+        :customStyle="customStyle"
+        @click="$emit('click')"
+    ></uvText>
+</template>
+
+<script>
+/**
+ * 此组件存在的理由是,在nvue下,u-text被uni-app官方占用了,u-text在nvue中相当于input组件
+ * 所以在nvue下,取名为u--input,内部其实还是u-text.vue,只不过做一层中转
+ * 不使用v-bind="$attrs",而是分开独立写传参,是因为微信小程序不支持此写法
+ */
+import uvText from "../u-text/u-text.vue";
+import props from "../u-text/props.js";
+export default {
+    name: "u--text",
+    mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+    components: {
+        uvText,
+    },
+};
+</script>

+ 48 - 0
uni_modules/uview-ui/components/u--textarea/u--textarea.vue

@@ -0,0 +1,48 @@
+<template>
+	<uvTextarea
+		:value="value"
+		:placeholder="placeholder"
+		:height="height"
+		:confirmType="confirmType"
+		:disabled="disabled"
+		:count="count"
+		:focus="focus"
+		:autoHeight="autoHeight"
+		:fixed="fixed"
+		:cursorSpacing="cursorSpacing"
+		:cursor="cursor"
+		:showConfirmBar="showConfirmBar"
+		:selectionStart="selectionStart"
+		:selectionEnd="selectionEnd"
+		:adjustPosition="adjustPosition"
+		:disableDefaultPadding="disableDefaultPadding"
+		:holdKeyboard="holdKeyboard"
+		:maxlength="maxlength"
+		:border="border"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		:ignoreCompositionEvent="ignoreCompositionEvent"
+		@focus="e => $emit('focus')"
+		@blur="e => $emit('blur')"
+		@linechange="e => $emit('linechange', e)"
+		@confirm="e => $emit('confirm')"
+		@input="e => $emit('input', e)"
+		@keyboardheightchange="e => $emit('keyboardheightchange')"
+	></uvTextarea>
+</template>
+
+<script>
+	/**
+	 * 此组件存在的理由是,在nvue下,u--textarea被uni-app官方占用了,u-textarea在nvue中相当于textarea组件
+	 * 所以在nvue下,取名为u--textarea,内部其实还是u-textarea.vue,只不过做一层中转
+	 */
+	import uvTextarea from '../u-textarea/u-textarea.vue';
+	import props from '../u-textarea/props.js'
+	export default {
+		name: 'u--textarea',
+		mixins: [uni.$u.mpMixin, props, uni.$u.mixin],
+		components: {
+			uvTextarea
+		},
+	}
+</script>

+ 54 - 0
uni_modules/uview-ui/components/u-action-sheet/props.js

@@ -0,0 +1,54 @@
+export default {
+    props: {
+        // 操作菜单是否展示 (默认false)
+        show: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.show
+        },
+        // 标题
+        title: {
+            type: String,
+            default: uni.$u.props.actionSheet.title
+        },
+        // 选项上方的描述信息
+        description: {
+            type: String,
+            default: uni.$u.props.actionSheet.description
+        },
+        // 数据
+        actions: {
+            type: Array,
+            default: uni.$u.props.actionSheet.actions
+        },
+        // 取消按钮的文字,不为空时显示按钮
+        cancelText: {
+            type: String,
+            default: uni.$u.props.actionSheet.cancelText
+        },
+        // 点击某个菜单项时是否关闭弹窗
+        closeOnClickAction: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.closeOnClickAction
+        },
+        // 处理底部安全区(默认true)
+        safeAreaInsetBottom: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.safeAreaInsetBottom
+        },
+        // 小程序的打开方式
+        openType: {
+            type: String,
+            default: uni.$u.props.actionSheet.openType
+        },
+        // 点击遮罩是否允许关闭 (默认true)
+        closeOnClickOverlay: {
+            type: Boolean,
+            default: uni.$u.props.actionSheet.closeOnClickOverlay
+        },
+        // 圆角值
+        round: {
+            type: [Boolean, String, Number],
+            default: uni.$u.props.actionSheet.round
+        }
+    }
+}

+ 278 - 0
uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue

@@ -0,0 +1,278 @@
+
+<template>
+	<u-popup
+	    :show="show"
+	    mode="bottom"
+	    @close="closeHandler"
+	    :safeAreaInsetBottom="safeAreaInsetBottom"
+	    :round="round"
+	>
+		<view class="u-action-sheet">
+			<view
+			    class="u-action-sheet__header"
+			    v-if="title"
+			>
+				<text class="u-action-sheet__header__title u-line-1">{{title}}</text>
+				<view
+				    class="u-action-sheet__header__icon-wrap"
+				    @tap.stop="cancel"
+				>
+					<u-icon
+					    name="close"
+					    size="17"
+					    color="#c8c9cc"
+					    bold
+					></u-icon>
+				</view>
+			</view>
+			<text
+			    class="u-action-sheet__description"
+				:style="[{
+					marginTop: `${title && description ? 0 : '18px'}`
+				}]"
+			    v-if="description"
+			>{{description}}</text>
+			<slot>
+				<u-line v-if="description"></u-line>
+				<view class="u-action-sheet__item-wrap">
+					<template v-for="(item, index) in actions">
+						<!-- #ifdef MP -->
+						<button
+						    :key="index"
+						    class="u-reset-button"
+						    :openType="item.openType"
+						    @getuserinfo="onGetUserInfo"
+						    @contact="onContact"
+						    @getphonenumber="onGetPhoneNumber"
+						    @error="onError"
+						    @launchapp="onLaunchApp"
+						    @opensetting="onOpenSetting"
+						    :lang="lang"
+						    :session-from="sessionFrom"
+						    :send-message-title="sendMessageTitle"
+						    :send-message-path="sendMessagePath"
+						    :send-message-img="sendMessageImg"
+						    :show-message-card="showMessageCard"
+						    :app-parameter="appParameter"
+						    @tap="selectHandler(index)"
+						    :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+						>
+							<!-- #endif -->
+							<view
+							    class="u-action-sheet__item-wrap__item"
+							    @tap.stop="selectHandler(index)"
+							    :hover-class="!item.disabled && !item.loading ? 'u-action-sheet--hover' : ''"
+							    :hover-stay-time="150"
+							>
+								<template v-if="!item.loading">
+									<text
+									    class="u-action-sheet__item-wrap__item__name"
+									    :style="[itemStyle(index)]"
+									>{{ item.name }}</text>
+									<text
+									    v-if="item.subname"
+									    class="u-action-sheet__item-wrap__item__subname"
+									>{{ item.subname }}</text>
+								</template>
+								<u-loading-icon
+								    v-else
+								    custom-class="van-action-sheet__loading"
+								    size="18"
+								    mode="circle"
+								/>
+							</view>
+							<!-- #ifdef MP -->
+						</button>
+						<!-- #endif -->
+						<u-line v-if="index !== actions.length - 1"></u-line>
+					</template>
+				</view>
+			</slot>
+			<u-gap
+			    bgColor="#eaeaec"
+			    height="6"
+			    v-if="cancelText"
+			></u-gap>
+			<view hover-class="u-action-sheet--hover">
+				<text
+				    @touchmove.stop.prevent
+				    :hover-stay-time="150"
+				    v-if="cancelText"
+				    class="u-action-sheet__cancel-text"
+				    @tap="cancel"
+				>{{cancelText}}</text>
+			</view>
+		</view>
+	</u-popup>
+</template>
+
+<script>
+	import openType from '../../libs/mixin/openType'
+	import button from '../../libs/mixin/button'
+	import props from './props.js';
+	/**
+	 * ActionSheet 操作菜单
+	 * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
+	 * @tutorial https://www.uviewui.com/components/actionSheet.html
+	 * 
+	 * @property {Boolean}			show				操作菜单是否展示 (默认 false )
+	 * @property {String}			title				操作菜单标题
+	 * @property {String}			description			选项上方的描述信息
+	 * @property {Array<Object>}	actions				按钮的文字数组,见官方文档示例
+	 * @property {String}			cancelText			取消按钮的提示文字,不为空时显示按钮
+	 * @property {Boolean}			closeOnClickAction	点击某个菜单项时是否关闭弹窗 (默认 true )
+	 * @property {Boolean}			safeAreaInsetBottom	处理底部安全区 (默认 true )
+	 * @property {String}			openType			小程序的打开方式 (contact | launchApp | getUserInfo | openSetting |getPhoneNumber |error )
+	 * @property {Boolean}			closeOnClickOverlay	点击遮罩是否允许关闭  (默认 true )
+	 * @property {Number|String}	round				圆角值,默认无圆角  (默认 0 )
+	 * @property {String}			lang				指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文
+	 * @property {String}			sessionFrom			会话来源,openType="contact"时有效
+	 * @property {String}			sendMessageTitle	会话内消息卡片标题,openType="contact"时有效
+	 * @property {String}			sendMessagePath		会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+	 * @property {String}			sendMessageImg		会话内消息卡片图片,openType="contact"时有效
+	 * @property {Boolean}			showMessageCard		是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效 (默认 false )
+	 * @property {String}			appParameter		打开 APP 时,向 APP 传递的参数,openType=launchApp 时有效
+	 * 
+	 * @event {Function} select			点击ActionSheet列表项时触发 
+	 * @event {Function} close			点击取消按钮时触发
+	 * @event {Function} getuserinfo	用户点击该按钮时,会返回获取到的用户信息,回调的 detail 数据与 wx.getUserInfo 返回的一致,openType="getUserInfo"时有效
+	 * @event {Function} contact		客服消息回调,openType="contact"时有效
+	 * @event {Function} getphonenumber	获取用户手机号回调,openType="getPhoneNumber"时有效
+	 * @event {Function} error			当使用开放能力时,发生错误的回调,openType="error"时有效
+	 * @event {Function} launchapp		打开 APP 成功的回调,openType="launchApp"时有效
+	 * @event {Function} opensetting	在打开授权设置页后回调,openType="openSetting"时有效
+	 * @example <u-action-sheet :actions="list" :title="title" :show="show"></u-action-sheet>
+	 */
+	export default {
+		name: "u-action-sheet",
+		// 一些props参数和methods方法,通过mixin混入,因为其他文件也会用到
+		mixins: [openType, button, uni.$u.mixin, props],
+		data() {
+			return {
+
+			}
+		},
+		computed: {
+			// 操作项目的样式
+			itemStyle() {
+				return (index) => {
+					let style = {};
+					if (this.actions[index].color) style.color = this.actions[index].color
+					if (this.actions[index].fontSize) style.fontSize = uni.$u.addUnit(this.actions[index].fontSize)
+					// 选项被禁用的样式
+					if (this.actions[index].disabled) style.color = '#c0c4cc'
+					return style;
+				}
+			},
+		},
+		methods: {
+			closeHandler() {
+				// 允许点击遮罩关闭时,才发出close事件
+				if(this.closeOnClickOverlay) {
+					this.$emit('close')
+				}
+			},
+			// 点击取消按钮
+			cancel() {
+				this.$emit('close')
+			},
+			selectHandler(index) {
+				const item = this.actions[index]
+				if (item && !item.disabled && !item.loading) {
+					this.$emit('select', item)
+					if (this.closeOnClickAction) {
+						this.$emit('close')
+					}
+				}
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+	$u-action-sheet-reset-button-width:100% !default;
+	$u-action-sheet-title-font-size: 16px !default;
+	$u-action-sheet-title-padding: 12px 30px !default;
+	$u-action-sheet-title-color: $u-main-color !default;
+	$u-action-sheet-header-icon-wrap-right:15px !default;
+	$u-action-sheet-header-icon-wrap-top:15px !default;
+	$u-action-sheet-description-font-size:13px !default;
+	$u-action-sheet-description-color:14px !default;
+	$u-action-sheet-description-margin: 18px 15px !default;
+	$u-action-sheet-item-wrap-item-padding:15px !default;
+	$u-action-sheet-item-wrap-name-font-size:16px !default;
+	$u-action-sheet-item-wrap-subname-font-size:13px !default;
+	$u-action-sheet-item-wrap-subname-color: #c0c4cc !default;
+	$u-action-sheet-item-wrap-subname-margin-top:10px !default;
+	$u-action-sheet-cancel-text-font-size:16px !default;
+	$u-action-sheet-cancel-text-color:$u-content-color !default;
+	$u-action-sheet-cancel-text-font-size:15px !default;
+	$u-action-sheet-cancel-text-hover-background-color:rgb(242, 243, 245) !default;
+
+	.u-reset-button {
+		width: $u-action-sheet-reset-button-width;
+	}
+
+	.u-action-sheet {
+		text-align: center;
+		&__header {
+			position: relative;
+			padding: $u-action-sheet-title-padding;
+			&__title {
+				font-size: $u-action-sheet-title-font-size;
+				color: $u-action-sheet-title-color;
+				font-weight: bold;
+				text-align: center;
+			}
+
+			&__icon-wrap {
+				position: absolute;
+				right: $u-action-sheet-header-icon-wrap-right;
+				top: $u-action-sheet-header-icon-wrap-top;
+			}
+		}
+
+		&__description {
+			font-size: $u-action-sheet-description-font-size;
+			color: $u-tips-color;
+			margin: $u-action-sheet-description-margin;
+			text-align: center;
+		}
+
+		&__item-wrap {
+
+			&__item {
+				padding: $u-action-sheet-item-wrap-item-padding;
+				@include flex;
+				align-items: center;
+				justify-content: center;
+				flex-direction: column;
+
+				&__name {
+					font-size: $u-action-sheet-item-wrap-name-font-size;
+					color: $u-main-color;
+					text-align: center;
+				}
+
+				&__subname {
+					font-size: $u-action-sheet-item-wrap-subname-font-size;
+					color: $u-action-sheet-item-wrap-subname-color;
+					margin-top: $u-action-sheet-item-wrap-subname-margin-top;
+					text-align: center;
+				}
+			}
+		}
+
+		&__cancel-text {
+			font-size: $u-action-sheet-cancel-text-font-size;
+			color: $u-action-sheet-cancel-text-color;
+			text-align: center;
+			padding: $u-action-sheet-cancel-text-font-size;
+		}
+
+		&--hover {
+			background-color: $u-action-sheet-cancel-text-hover-background-color;
+		}
+	}
+</style>

+ 59 - 0
uni_modules/uview-ui/components/u-album/props.js

@@ -0,0 +1,59 @@
+export default {
+    props: {
+        // 图片地址,Array<String>|Array<Object>形式
+        urls: {
+            type: Array,
+            default: uni.$u.props.album.urls
+        },
+        // 指定从数组的对象元素中读取哪个属性作为图片地址
+        keyName: {
+            type: String,
+            default: uni.$u.props.album.keyName
+        },
+        // 单图时,图片长边的长度
+        singleSize: {
+            type: [String, Number],
+            default: uni.$u.props.album.singleSize
+        },
+        // 多图时,图片边长
+        multipleSize: {
+            type: [String, Number],
+            default: uni.$u.props.album.multipleSize
+        },
+        // 多图时,图片水平和垂直之间的间隔
+        space: {
+            type: [String, Number],
+            default: uni.$u.props.album.space
+        },
+        // 单图时,图片缩放裁剪的模式
+        singleMode: {
+            type: String,
+            default: uni.$u.props.album.singleMode
+        },
+        // 多图时,图片缩放裁剪的模式
+        multipleMode: {
+            type: String,
+            default: uni.$u.props.album.multipleMode
+        },
+        // 最多展示的图片数量,超出时最后一个位置将会显示剩余图片数量
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.album.maxCount
+        },
+        // 是否可以预览图片
+        previewFullImage: {
+            type: Boolean,
+            default: uni.$u.props.album.previewFullImage
+        },
+        // 每行展示图片数量,如设置,singleSize和multipleSize将会无效
+        rowCount: {
+            type: [String, Number],
+            default: uni.$u.props.album.rowCount
+        },
+        // 超出maxCount时是否显示查看更多的提示
+        showMore: {
+            type: Boolean,
+            default: uni.$u.props.album.showMore
+        }
+    }
+}

+ 259 - 0
uni_modules/uview-ui/components/u-album/u-album.vue

@@ -0,0 +1,259 @@
+<template>
+    <view class="u-album">
+        <view
+            class="u-album__row"
+            ref="u-album__row"
+            v-for="(arr, index) in showUrls"
+            :forComputedUse="albumWidth"
+            :key="index"
+        >
+            <view
+                class="u-album__row__wrapper"
+                v-for="(item, index1) in arr"
+                :key="index1"
+                :style="[imageStyle(index + 1, index1 + 1)]"
+                @tap="previewFullImage ? onPreviewTap(getSrc(item)) : ''"
+            >
+                <image
+                    :src="getSrc(item)"
+                    :mode="
+                        urls.length === 1
+                            ? imageHeight > 0
+                                ? singleMode
+                                : 'widthFix'
+                            : multipleMode
+                    "
+                    :style="[
+                        {
+                            width: imageWidth,
+                            height: imageHeight
+                        }
+                    ]"
+                ></image>
+                <view
+                    v-if="
+                        showMore &&
+                        urls.length > rowCount * showUrls.length &&
+                        index === showUrls.length - 1 &&
+                        index1 === showUrls[showUrls.length - 1].length - 1
+                    "
+                    class="u-album__row__wrapper__text"
+                >
+                    <u--text
+                        :text="`+${urls.length - maxCount}`"
+                        color="#fff"
+                        :size="multipleSize * 0.3"
+                        align="center"
+                        customStyle="justify-content: center"
+                    ></u--text>
+                </view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+import props from './props.js'
+// #ifdef APP-NVUE
+// 由于weex为阿里的KPI业绩考核的产物,所以不支持百分比单位,这里需要通过dom查询组件的宽度
+const dom = uni.requireNativePlugin('dom')
+// #endif
+
+/**
+ * Album 相册
+ * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
+ * @tutorial https://www.uviewui.com/components/album.html
+ *
+ * @property {Array}           urls             图片地址列表 Array<String>|Array<Object>形式
+ * @property {String}          keyName          指定从数组的对象元素中读取哪个属性作为图片地址
+ * @property {String | Number} singleSize       单图时,图片长边的长度  (默认 180 )
+ * @property {String | Number} multipleSize     多图时,图片边长 (默认 70 )
+ * @property {String | Number} space            多图时,图片水平和垂直之间的间隔 (默认 6 )
+ * @property {String}          singleMode       单图时,图片缩放裁剪的模式 (默认 'scaleToFill' )
+ * @property {String}          multipleMode     多图时,图片缩放裁剪的模式 (默认 'aspectFill' )
+ * @property {String | Number} maxCount         取消按钮的提示文字 (默认 9 )
+ * @property {Boolean}         previewFullImage 是否可以预览图片 (默认 true )
+ * @property {String | Number} rowCount         每行展示图片数量,如设置,singleSize和multipleSize将会无效	(默认 3 )
+ * @property {Boolean}         showMore         超出maxCount时是否显示查看更多的提示 (默认 true )
+ *
+ * @event    {Function}        albumWidth       某些特殊的情况下,需要让文字与相册的宽度相等,这里事件的形式对外发送  (回调参数 width )
+ * @example <u-album :urls="urls2" @albumWidth="width => albumWidth = width" multipleSize="68" ></u-album>
+ */
+export default {
+    name: 'u-album',
+    mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+    data() {
+        return {
+            // 单图的宽度
+            singleWidth: 0,
+            // 单图的高度
+            singleHeight: 0,
+            // 单图时,如果无法获取图片的尺寸信息,让图片宽度默认为容器的一定百分比
+            singlePercent: 0.6
+        }
+    },
+    watch: {
+        urls: {
+            immediate: true,
+            handler(newVal) {
+                if (newVal.length === 1) {
+                    this.getImageRect()
+                }
+            }
+        }
+    },
+    computed: {
+        imageStyle() {
+            return (index1, index2) => {
+                const { space, rowCount, multipleSize, urls } = this,
+                    { addUnit, addStyle } = uni.$u,
+                    rowLen = this.showUrls.length,
+                    allLen = this.urls.length
+                const style = {
+                    marginRight: addUnit(space),
+                    marginBottom: addUnit(space)
+                }
+                // 如果为最后一行,则每个图片都无需下边框
+                if (index1 === rowLen) style.marginBottom = 0
+                // 每行的最右边一张和总长度的最后一张无需右边框
+                if (
+                    index2 === rowCount ||
+                    (index1 === rowLen &&
+                        index2 === this.showUrls[index1 - 1].length)
+                )
+                    style.marginRight = 0
+                return style
+            }
+        },
+        // 将数组划分为二维数组
+        showUrls() {
+            const arr = []
+            this.urls.map((item, index) => {
+                // 限制最大展示数量
+                if (index + 1 <= this.maxCount) {
+                    // 计算该元素为第几个素组内
+                    const itemIndex = Math.floor(index / this.rowCount)
+                    // 判断对应的索引是否存在
+                    if (!arr[itemIndex]) {
+                        arr[itemIndex] = []
+                    }
+                    arr[itemIndex].push(item)
+                }
+            })
+            return arr
+        },
+        imageWidth() {
+            return uni.$u.addUnit(
+                this.urls.length === 1 ? this.singleWidth : this.multipleSize
+            )
+        },
+        imageHeight() {
+            return uni.$u.addUnit(
+                this.urls.length === 1 ? this.singleHeight : this.multipleSize
+            )
+        },
+        // 此变量无实际用途,仅仅是为了利用computed特性,让其在urls长度等变化时,重新计算图片的宽度
+        // 因为用户在某些特殊的情况下,需要让文字与相册的宽度相等,所以这里事件的形式对外发送
+        albumWidth() {
+            let width = 0
+            if (this.urls.length === 1) {
+                width = this.singleWidth
+            } else {
+                width =
+                    this.showUrls[0].length * this.multipleSize +
+                    this.space * (this.showUrls[0].length - 1)
+            }
+            this.$emit('albumWidth', width)
+            return width
+        }
+    },
+    methods: {
+        // 预览图片
+        onPreviewTap(url) {
+            const urls = this.urls.map((item) => {
+                return this.getSrc(item)
+            })
+            uni.previewImage({
+                current: url,
+                urls
+            })
+        },
+        // 获取图片的路径
+        getSrc(item) {
+            return uni.$u.test.object(item)
+                ? (this.keyName && item[this.keyName]) || item.src
+                : item
+        },
+        // 单图时,获取图片的尺寸
+        // 在小程序中,需要将网络图片的的域名添加到小程序的download域名才可能获取尺寸
+        // 在没有添加的情况下,让单图宽度默认为盒子的一定宽度(singlePercent)
+        getImageRect() {
+            const src = this.getSrc(this.urls[0])
+            uni.getImageInfo({
+                src,
+                success: (res) => {
+                    // 判断图片横向还是竖向展示方式
+                    const isHorizotal = res.width >= res.height
+                    this.singleWidth = isHorizotal
+                        ? this.singleSize
+                        : (res.width / res.height) * this.singleSize
+                    this.singleHeight = !isHorizotal
+                        ? this.singleSize
+                        : (res.height / res.width) * this.singleWidth
+                },
+                fail: () => {
+                    this.getComponentWidth()
+                }
+            })
+        },
+        // 获取组件的宽度
+        async getComponentWidth() {
+            // 延时一定时间,以获取dom尺寸
+            await uni.$u.sleep(30)
+            // #ifndef APP-NVUE
+            this.$uGetRect('.u-album__row').then((size) => {
+                this.singleWidth = size.width * this.singlePercent
+            })
+            // #endif
+
+            // #ifdef APP-NVUE
+            // 这里ref="u-album__row"所在的标签为通过for循环出来,导致this.$refs['u-album__row']是一个数组
+            const ref = this.$refs['u-album__row'][0]
+            ref &&
+                dom.getComponentRect(ref, (res) => {
+                    this.singleWidth = res.size.width * this.singlePercent
+                })
+            // #endif
+        }
+    }
+}
+</script>
+
+<style lang="scss" scoped>
+@import '../../libs/css/components.scss';
+
+.u-album {
+    @include flex(column);
+
+    &__row {
+        @include flex(row);
+        flex-wrap: wrap;
+
+        &__wrapper {
+            position: relative;
+
+            &__text {
+                position: absolute;
+                top: 0;
+                left: 0;
+                right: 0;
+                bottom: 0;
+                background-color: rgba(0, 0, 0, 0.3);
+                @include flex(row);
+                justify-content: center;
+                align-items: center;
+            }
+        }
+    }
+}
+</style>

+ 44 - 0
uni_modules/uview-ui/components/u-alert/props.js

@@ -0,0 +1,44 @@
+export default {
+    props: {
+        // 显示文字
+        title: {
+            type: String,
+            default: uni.$u.props.alert.title
+        },
+        // 主题,success/warning/info/error
+        type: {
+            type: String,
+            default: uni.$u.props.alert.type
+        },
+        // 辅助性文字
+        description: {
+            type: String,
+            default: uni.$u.props.alert.description
+        },
+        // 是否可关闭
+        closable: {
+            type: Boolean,
+            default: uni.$u.props.alert.closable
+        },
+        // 是否显示图标
+        showIcon: {
+            type: Boolean,
+            default: uni.$u.props.alert.showIcon
+        },
+        // 浅或深色调,light-浅色,dark-深色
+        effect: {
+            type: String,
+            default: uni.$u.props.alert.effect
+        },
+        // 文字是否居中
+        center: {
+            type: Boolean,
+            default: uni.$u.props.alert.center
+        },
+        // 字体大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.alert.fontSize
+        }
+    }
+}

+ 243 - 0
uni_modules/uview-ui/components/u-alert/u-alert.vue

@@ -0,0 +1,243 @@
+<template>
+	<u-transition
+	    mode="fade"
+	    :show="show"
+	>
+		<view
+		    class="u-alert"
+		    :class="[`u-alert--${type}--${effect}`]"
+		    @tap.stop="clickHandler"
+		    :style="[$u.addStyle(customStyle)]"
+		>
+			<view
+			    class="u-alert__icon"
+			    v-if="showIcon"
+			>
+				<u-icon
+				    :name="iconName"
+				    size="18"
+				    :color="iconColor"
+				></u-icon>
+			</view>
+			<view
+			    class="u-alert__content"
+			    :style="[{
+					paddingRight: closable ? '20px' : 0
+				}]"
+			>
+				<text
+				    class="u-alert__content__title"
+				    v-if="title"
+					:style="[{
+						fontSize: $u.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+				>{{ title }}</text>
+				<text
+				    class="u-alert__content__desc"
+					v-if="description"
+					:style="[{
+						fontSize: $u.addUnit(fontSize),
+						textAlign: center ? 'center' : 'left'
+					}]"
+				    :class="[effect === 'dark' ? 'u-alert__text--dark' : `u-alert__text--${type}--light`]"
+				>{{ description }}</text>
+			</view>
+			<view
+			    class="u-alert__close"
+			    v-if="closable"
+			    @tap.stop="closeHandler"
+			>
+				<u-icon
+				    name="close"
+				    :color="iconColor"
+				    size="15"
+				></u-icon>
+			</view>
+		</view>
+	</u-transition>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * Alert  警告提示
+	 * @description 警告提示,展现需要关注的信息。
+	 * @tutorial https://www.uviewui.com/components/alertTips.html
+	 * 
+	 * @property {String}			title       显示的文字 
+	 * @property {String}			type        使用预设的颜色  (默认 'warning' )
+	 * @property {String}			description 辅助性文字,颜色比title浅一点,字号也小一点,可选  
+	 * @property {Boolean}			closable    关闭按钮(默认为叉号icon图标)  (默认 false )
+	 * @property {Boolean}			showIcon    是否显示左边的辅助图标   ( 默认 false )
+	 * @property {String}			effect      多图时,图片缩放裁剪的模式  (默认 'light' )
+	 * @property {Boolean}			center		文字是否居中  (默认 false )
+	 * @property {String | Number}	fontSize    字体大小  (默认 14 )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @event    {Function}        click       点击组件时触发
+	 * @example  <u-alert :title="title"  type = "warning" :closable="closable" :description = "description"></u-alert>
+	 */
+	export default {
+		name: 'u-alert',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+				show: true
+			}
+		},
+		computed: {
+			iconColor() {
+				return this.effect === 'light' ? this.type : '#fff'
+			},
+			// 不同主题对应不同的图标
+			iconName() {
+				switch (this.type) {
+					case 'success':
+						return 'checkmark-circle-fill';
+						break;
+					case 'error':
+						return 'close-circle-fill';
+						break;
+					case 'warning':
+						return 'error-circle-fill';
+						break;
+					case 'info':
+						return 'info-circle-fill';
+						break;
+					case 'primary':
+						return 'more-circle-fill';
+						break;
+					default: 
+						return 'error-circle-fill';
+				}
+			}
+		},
+		methods: {
+			// 点击内容
+			clickHandler() {
+				this.$emit('click')
+			},
+			// 点击关闭按钮
+			closeHandler() {
+				this.show = false
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-alert {
+		position: relative;
+		background-color: $u-primary;
+		padding: 8px 10px;
+		@include flex(row);
+		align-items: center;
+		border-top-left-radius: 4px;
+		border-top-right-radius: 4px;
+		border-bottom-left-radius: 4px;
+		border-bottom-right-radius: 4px;
+
+		&--primary--dark {
+			background-color: $u-primary;
+		}
+
+		&--primary--light {
+			background-color: #ecf5ff;
+		}
+
+		&--error--dark {
+			background-color: $u-error;
+		}
+
+		&--error--light {
+			background-color: #FEF0F0;
+		}
+
+		&--success--dark {
+			background-color: $u-success;
+		}
+
+		&--success--light {
+			background-color: #f5fff0;
+		}
+
+		&--warning--dark {
+			background-color: $u-warning;
+		}
+
+		&--warning--light {
+			background-color: #FDF6EC;
+		}
+
+		&--info--dark {
+			background-color: $u-info;
+		}
+
+		&--info--light {
+			background-color: #f4f4f5;
+		}
+
+		&__icon {
+			margin-right: 5px;
+		}
+
+		&__content {
+			@include flex(column);
+			flex: 1;
+
+			&__title {
+				color: $u-main-color;
+				font-size: 14px;
+				font-weight: bold;
+				color: #fff;
+				margin-bottom: 2px;
+			}
+
+			&__desc {
+				color: $u-main-color;
+				font-size: 14px;
+				flex-wrap: wrap;
+				color: #fff;
+			}
+		}
+
+		&__title--dark,
+		&__desc--dark {
+			color: #FFFFFF;
+		}
+
+		&__text--primary--light,
+		&__text--primary--light {
+			color: $u-primary;
+		}
+
+		&__text--success--light,
+		&__text--success--light {
+			color: $u-success;
+		}
+
+		&__text--warning--light,
+		&__text--warning--light {
+			color: $u-warning;
+		}
+
+		&__text--error--light,
+		&__text--error--light {
+			color: $u-error;
+		}
+
+		&__text--info--light,
+		&__text--info--light {
+			color: $u-info;
+		}
+
+		&__close {
+			position: absolute;
+			top: 11px;
+			right: 10px;
+		}
+	}
+</style>

+ 52 - 0
uni_modules/uview-ui/components/u-avatar-group/props.js

@@ -0,0 +1,52 @@
+export default {
+    props: {
+        // 头像图片组
+        urls: {
+            type: Array,
+            default: uni.$u.props.avatarGroup.urls
+        },
+        // 最多展示的头像数量
+        maxCount: {
+            type: [String, Number],
+            default: uni.$u.props.avatarGroup.maxCount
+        },
+        // 头像形状
+        shape: {
+            type: String,
+            default: uni.$u.props.avatarGroup.shape
+        },
+        // 图片裁剪模式
+        mode: {
+            type: String,
+            default: uni.$u.props.avatarGroup.mode
+        },
+        // 超出maxCount时是否显示查看更多的提示
+        showMore: {
+            type: Boolean,
+            default: uni.$u.props.avatarGroup.showMore
+        },
+        // 头像大小
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.avatarGroup.size
+        },
+        // 指定从数组的对象元素中读取哪个属性作为图片地址
+        keyName: {
+            type: String,
+            default: uni.$u.props.avatarGroup.keyName
+        },
+		// 头像之间的遮挡比例
+        gap: {
+            type: [String, Number],
+            validator(value) {
+                return value >= 0 && value <= 1
+            },
+            default: uni.$u.props.avatarGroup.gap
+        },
+		// 需额外显示的值
+		extraValue: {
+			type: [Number, String],
+			default: uni.$u.props.avatarGroup.extraValue
+		}
+    }
+}

+ 103 - 0
uni_modules/uview-ui/components/u-avatar-group/u-avatar-group.vue

@@ -0,0 +1,103 @@
+<template>
+	<view class="u-avatar-group">
+		<view
+		    class="u-avatar-group__item"
+		    v-for="(item, index) in showUrl"
+		    :key="index"
+		    :style="{
+				marginLeft: index === 0 ? 0 : $u.addUnit(-size * gap)
+			}"
+		>
+			<u-avatar
+			    :size="size"
+			    :shape="shape"
+			    :mode="mode"
+			    :src="$u.test.object(item) ? keyName && item[keyName] || item.url : item"
+			></u-avatar>
+			<view
+			    class="u-avatar-group__item__show-more"
+			    v-if="showMore && index === showUrl.length - 1 && (urls.length > maxCount || extraValue > 0)"
+				@tap="clickHandler"
+			>
+				<u--text
+				    color="#ffffff"
+				    :size="size * 0.4"
+				    :text="`+${extraValue || urls.length - showUrl.length}`"
+					align="center"
+					customStyle="justify-content: center"
+				></u--text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import props from './props.js';
+	/**
+	 * AvatarGroup  头像组
+	 * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
+	 * @tutorial https://www.uviewui.com/components/avatar.html
+	 * 
+	 * @property {Array}           urls     头像图片组 (默认 [] )
+	 * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )
+	 * @property {String}          shape    头像形状( 'circle' (默认) | 'square' )
+	 * @property {String}          mode     图片裁剪模式(默认 'scaleToFill' )
+	 * @property {Boolean}         showMore 超出maxCount时是否显示查看更多的提示 (默认 true )
+	 * @property {String | Number} size      头像大小 (默认 40 )
+	 * @property {String}          keyName  指定从数组的对象元素中读取哪个属性作为图片地址 
+	 * @property {String | Number} gap      头像之间的遮挡比例(0.4代表遮挡40%)  (默认 0.5 )
+	 * @property {String | Number} extraValue  需额外显示的值
+	 * @event    {Function}        showMore 头像组更多点击
+	 * @example  <u-avatar-group:urls="urls" size="35" gap="0.4" ></u-avatar-group:urls=>
+	 */
+	export default {
+		name: 'u-avatar-group',
+		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
+		data() {
+			return {
+
+			}
+		},
+		computed: {
+			showUrl() {
+				return this.urls.slice(0, this.maxCount)
+			}
+		},
+		methods: {
+			clickHandler() {
+				this.$emit('showMore')
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import "../../libs/css/components.scss";
+
+	.u-avatar-group {
+		@include flex;
+
+		&__item {
+			margin-left: -10px;
+			position: relative;
+
+			&--no-indent {
+				// 如果你想质疑作者不会使用:first-child,说明你太年轻,因为nvue不支持
+				margin-left: 0;
+			}
+
+			&__show-more {
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				background-color: rgba(0, 0, 0, 0.3);
+				@include flex;
+				align-items: center;
+				justify-content: center;
+				border-radius: 100px;
+			}
+		}
+	}
+</style>

+ 78 - 0
uni_modules/uview-ui/components/u-avatar/props.js

@@ -0,0 +1,78 @@
+export default {
+    props: {
+        // 头像图片路径(不能为相对路径)
+        src: {
+            type: String,
+            default: uni.$u.props.avatar.src
+        },
+        // 头像形状,circle-圆形,square-方形
+        shape: {
+            type: String,
+            default: uni.$u.props.avatar.shape
+        },
+        // 头像尺寸
+        size: {
+            type: [String, Number],
+            default: uni.$u.props.avatar.size
+        },
+        // 裁剪模式
+        mode: {
+            type: String,
+            default: uni.$u.props.avatar.mode
+        },
+        // 显示的文字
+        text: {
+            type: String,
+            default: uni.$u.props.avatar.text
+        },
+        // 背景色
+        bgColor: {
+            type: String,
+            default: uni.$u.props.avatar.bgColor
+        },
+        // 文字颜色
+        color: {
+            type: String,
+            default: uni.$u.props.avatar.color
+        },
+        // 文字大小
+        fontSize: {
+            type: [String, Number],
+            default: uni.$u.props.avatar.fontSize
+        },
+        // 显示的图标
+        icon: {
+            type: String,
+            default: uni.$u.props.avatar.icon
+        },
+        // 显示小程序头像,只对百度,微信,QQ小程序有效
+        mpAvatar: {
+            type: Boolean,
+            default: uni.$u.props.avatar.mpAvatar
+        },
+        // 是否使用随机背景色
+        randomBgColor: {
+            type: Boolean,
+            default: uni.$u.props.avatar.randomBgColor
+        },
+        // 加载失败的默认头像(组件有内置默认图片)
+        defaultUrl: {
+            type: String,
+            default: uni.$u.props.avatar.defaultUrl
+        },
+        // 如果配置了randomBgColor为true,且配置了此值,则从默认的背景色数组中取出对应索引的颜色值,取值0-19之间
+        colorIndex: {
+            type: [String, Number],
+            // 校验参数规则,索引在0-19之间
+            validator(n) {
+                return uni.$u.test.range(n, [0, 19]) || n === ''
+            },
+            default: uni.$u.props.avatar.colorIndex
+        },
+        // 组件标识符
+        name: {
+            type: String,
+            default: uni.$u.props.avatar.name
+        }
+    }
+}

Tiedoston diff-näkymää rajattu, sillä se on liian suuri
+ 58 - 0
uni_modules/uview-ui/components/u-avatar/u-avatar.vue


Kaikkia tiedostoja ei voida näyttää, sillä liian monta tiedostoa muuttui tässä diffissä