XSLu08042 il y a 4 mois
Parent
commit
36fd06fbb9
49 fichiers modifiés avec 3022 ajouts et 1790 suppressions
  1. 4 0
      .hbuilderx/launch.json
  2. 45 0
      api/courseManage.js
  3. 2 1
      common/request.js
  4. 2 1
      main.js
  5. 1 0
      pages/auth/login.vue
  6. 42 15
      pages/courseManage/components/courseItem.vue
  7. 265 0
      pages/courseManage/components/dropdownPanel.vue
  8. 132 0
      pages/courseManage/components/vipUserItem.vue
  9. 83 44
      pages/courseManage/course/index.vue
  10. 197 41
      pages/courseManage/dataIndex/index.vue
  11. 6 3
      pages/courseManage/index.vue
  12. 131 40
      pages/courseManage/live/index.vue
  13. 668 518
      pages/courseManage/statistics.vue
  14. 217 4
      pages/courseManage/vip/index.vue
  15. 2 2
      uni_modules/uview-ui/LICENSE
  16. 32 0
      uni_modules/uview-ui/changelog.md
  17. 2 2
      uni_modules/uview-ui/components/u--form/u--form.vue
  18. 1 0
      uni_modules/uview-ui/components/u--input/u--input.vue
  19. 3 2
      uni_modules/uview-ui/components/u--textarea/u--textarea.vue
  20. 1 0
      uni_modules/uview-ui/components/u-avatar/u-avatar.vue
  21. 5 0
      uni_modules/uview-ui/components/u-button/u-button.vue
  22. 4 3
      uni_modules/uview-ui/components/u-calendar/u-calendar.vue
  23. 4 4
      uni_modules/uview-ui/components/u-code/u-code.vue
  24. 1 1
      uni_modules/uview-ui/components/u-datetime-picker/u-datetime-picker.vue
  25. 120 139
      uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue
  26. 118 118
      uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue
  27. 5 0
      uni_modules/uview-ui/components/u-form-item/props.js
  28. 1 1
      uni_modules/uview-ui/components/u-form-item/u-form-item.vue
  29. 5 0
      uni_modules/uview-ui/components/u-input/props.js
  30. 3 2
      uni_modules/uview-ui/components/u-input/u-input.vue
  31. 6 1
      uni_modules/uview-ui/components/u-modal/props.js
  32. 7 6
      uni_modules/uview-ui/components/u-modal/u-modal.vue
  33. 1 0
      uni_modules/uview-ui/components/u-no-network/u-no-network.vue
  34. 5 2
      uni_modules/uview-ui/components/u-picker/u-picker.vue
  35. 11 9
      uni_modules/uview-ui/components/u-radio/u-radio.vue
  36. 4 2
      uni_modules/uview-ui/components/u-rate/u-rate.vue
  37. 10 1
      uni_modules/uview-ui/components/u-tabs/u-tabs.vue
  38. 1 1
      uni_modules/uview-ui/components/u-text/props.js
  39. 5 0
      uni_modules/uview-ui/components/u-textarea/props.js
  40. 2 0
      uni_modules/uview-ui/components/u-textarea/u-textarea.vue
  41. 3 3
      uni_modules/uview-ui/libs/config/config.js
  42. 1 0
      uni_modules/uview-ui/libs/config/props/formItem.js
  43. 2 1
      uni_modules/uview-ui/libs/config/props/modal.js
  44. 1 1
      uni_modules/uview-ui/libs/config/props/textarea.js
  45. 731 705
      uni_modules/uview-ui/libs/function/index.js
  46. 1 1
      uni_modules/uview-ui/libs/function/test.js
  47. 107 107
      uni_modules/uview-ui/libs/util/route.js
  48. 7 9
      uni_modules/uview-ui/package.json
  49. 15 0
      utils/tools.js

+ 4 - 0
.hbuilderx/launch.json

@@ -2,6 +2,10 @@
   // launchtype项可配置值为local或remote, local代表前端连本地云函数,remote代表前端连云端云函数
     "version": "0.0",
     "configurations": [{
+     	"app-plus" : 
+     	{
+     		"launchtype" : "local"
+     	},
      	"default" : 
      	{
      		"launchtype" : "local"

+ 45 - 0
api/courseManage.js

@@ -0,0 +1,45 @@
+import Request from '../common/request.js';
+let request = new Request().http
+
+// 统计经销商会员总数和群管总数
+export function getCompanyUserAndUserCount(data) {
+	return request('/fsUser/getCompanyUserAndUserCount', data,'POST','application/json;charset=UTF-8');
+}
+
+// 课程统计
+export function getCourseCount(data) {
+	return request('/fsCourseWatchLog/getCompanyUserAndUserCount', data,'POST','application/json;charset=UTF-8');
+}
+
+// 答题统计
+export function getQuesCount(data) {
+	return request('/fsAnswerLog/getCompanyUserAndUserCount', data,'POST','application/json;charset=UTF-8');
+}
+
+// 红包统计
+export function getRecPacketCount(data) {
+	return request('/fsCourseRedPacket/getRecPacketCount', data,'POST','application/json;charset=UTF-8');
+}
+
+// 获取课程列表
+export function getFsCourseList(data) {
+	return request('/fsCourseRedPacket/getFsCourseList', data,'POST','application/json;charset=UTF-8');
+}
+
+// 根据训练营查询节目
+export function getCourseVdieoList(data) {
+	const url = '/fsCourseRedPacket/getCourseVdieoPageList?courseId='+data.courseId +'&pageNum='+data.pageNum + '&pageSize='+data.pageSize+'&status='+data.status
+	return request(url, null,'POST','application/json;charset=UTF-8');
+}
+
+// 根据观看记录查询用户
+export function getUserLogListByCourseId(data) {
+	const url = '/fsCourseRedPacket/getUserLogListByCourseId'
+	return request(url, data,'POST','application/json;charset=UTF-8');
+}
+
+// 修改用户
+export function updateFsUser(data) {
+	const url = '/fsUser/updateFsUser'
+	return request(url, data,'POST','application/json;charset=UTF-8');
+}

+ 2 - 1
common/request.js

@@ -2,7 +2,8 @@
 export default class Request {
 	http(router, data = {}, method,contentType) {
 		let that = this;
-		let path = 'http://42.194.245.189:8007';
+		// let path = 'http://42.194.245.189:8007';
+		let path = 'https://43893ep95pe7.vicp.fun'
 		uni.setStorageSync('requestPath',path)
 		// uni.showLoading({
 		// 	title: '加载中'

+ 2 - 1
main.js

@@ -11,7 +11,8 @@ import utils from './utils/common.js'
 Vue.prototype.utils = utils;
 import {setData} from './utils/common.js'
 Vue.prototype.setData = setData;
-
+import {formatSeconds}from './utils/tools.js'
+Vue.prototype.$formatSeconds = formatSeconds;
 
 //import router from '@/router/router.js'  
 //import {RouterMount} from 'uni-simple-router'

+ 1 - 0
pages/auth/login.vue

@@ -81,6 +81,7 @@ export default {
 					if(res.code==200){
 						uni.setStorageSync('AppToken',res.data.token);
 						uni.setStorageSync('companyUserId',res.data.user.userId);
+						uni.setStorageSync('companyUserInfo',JSON.stringify(res.data.user));
 						uni.$emit('initSocket');
 						uni.reLaunch({
 							url: '../user/index',

+ 42 - 15
pages/courseManage/components/courseItem.vue

@@ -2,20 +2,20 @@
 	<view class="courselist-item">
 		<view class="courselist-con x-start">
 			<view class="courselist-img">
-				<view class="status">进行中</view>
-				<image src="https://sg-mmyy-oss.yjfzy.com/sgs/image/20241102/e07b38daf749cb02e9f5ce69c57b44ea.png" mode="aspectFill"></image>
+				<!-- <view class="status">进行中</view> -->
+				<image :src="info.thumbnail" mode="aspectFill"></image>
 			</view>
 			<view class="courselist-con-r">
 				<view>
-					<text class="more-t">7.明医有话说-杜老师给您讲讲抑郁</text>
-					<view class="btn_icon" style="margin-left: 5px;">ID
+					<text class="more-t">{{info.title}}</text>
+					<view class="btn_icon" style="margin-left: 5px;" @click="copyId">ID
 						<image src="@/static/images/copy_icon.png" mode="aspectFill"></image>
 					</view>
 				</view>
-				<view class="courselist-desc one-t" v-show="from != 'course'">明医有话说 2.0-2.0-明医10301111111111</view>
+				<view class="courselist-desc one-t" v-show="from != 'course'">{{info.courseName}}</view>
 				<view :class="from == 'course' ? 'courselist-con-timebox ':'courselist-con-timebox x-f'">
-					<view class="x-f acea-row"><u-icon class="icon" name="file-text" color="#999" size="20"></u-icon>12月28日 00:00-23:59</view>
-					<view class="x-f acea-row"><u-icon class="icon" name="clock" color="#999" size="16"></u-icon>44分36秒</view>
+					<view class="x-f acea-row"><u-icon class="icon" name="file-text" color="#999" size="20"></u-icon>{{info.createTime}}</view>
+					<view class="x-f acea-row"><u-icon class="icon" name="clock" color="#999" size="16"></u-icon>{{$formatSeconds(info.duration,1)}}</view>
 				</view>
 			</view>
 		</view>
@@ -40,7 +40,7 @@
 			</view>
 		</u-popup>
 		<!-- 设置链接有效时长弹窗 -->
-		<u-modal :show="setTimeShow"content='content' class="model" @confirm="confirmTime">
+		<u-modal :show="setTimeShow" content='content' class="model" @confirm="confirmTime">
 			<view class="setTimebox">
 				<view class="timetip">不传默认以系统参数为准</view>
 				<view class="x-f">
@@ -55,12 +55,7 @@
 				</view>
 			</view>
 		</u-modal>
-		<!-- <u-popup :show="setTimeShow" :round="10" mode="center" @close="close" @open="open" :zIndex="10078">
-			<view class="timePop">
-				<view>不传默认以系统参数为准</view>
-				<text>人生若只如初见,何事秋风悲画扇</text>
-			</view>
-		</u-popup> -->
+		<u-notify ref="uNotify" message=""></u-notify>
 	</view>
 </template>
 
@@ -76,6 +71,12 @@
 				type: String,
 				default: 'live'
 			},
+			info: {
+				type: Object,
+				default: ()=>{
+					return {}
+				}
+			},
 		},
 		data() {
 			return {
@@ -104,9 +105,35 @@
 				this.showShare = false
 			},
 			handleStatistics() {
+				const info = {
+					courseId: this.info.courseId,
+					title: this.info.title,
+					thumbnail: this.info.thumbnail,
+					createTime: this.info.createTime,
+					duration: this.info.duration,
+					fileId: this.info.fileId,
+					courseName: this.info.courseName,
+				}
 				uni.navigateTo({
-					url: '/pages/courseManage/statistics'
+					url: '/pages/courseManage/statistics?info='+JSON.stringify(info)
 				})
+			},
+			copyId() {
+				uni.setClipboardData({
+					data: this.info.fileId,
+					success: ()=> {
+						this.$refs.uNotify.show({
+							top: 0,
+							type: 'success',
+							// color: '#000',
+							// bgColor: '#e8e8e8',
+							message: '复制课程ID成功',
+							duration: 1000 * 2,
+							fontSize: 20,
+							safeAreaInsetTop:true
+						})
+					}
+				});
 			}
 		}
 	}

+ 265 - 0
pages/courseManage/components/dropdownPanel.vue

@@ -0,0 +1,265 @@
+<template>
+    <view class="filter-wrapper" :style="{ height: height + 'px'}" @touchmove.stop.prevent="discard">
+        <view class="inner-wrapper">
+            <view class="mask" :class="showMask ? 'show' : 'hide'" :style="{top: top}" @tap="tapMask"></view>
+            <view class="navs">
+                <view class="c-flex-center" :class="{ 'c-flex-center': index > 0, actNav: index === actNav }" v-for="(item, index) in navData" :key="index" @click="navClick(index)">
+					<view v-if="item.special&&item.name=='批量'" class="x-f" style="align-items: end;">
+						<u-icon name="file-text" :color="index === actNav ?'#2979ff':'#222'" size="20"></u-icon>
+						{{index === actNav ? '取消批量':'批量'}}
+					</view>
+					<template v-else>
+						<text>{{item.name}}</text>
+						<image src="https://i.loli.net/2020/07/15/QsHxlr1gbSImvWt.png" mode="" class="icon-triangle" v-if="index === actNav"></image>
+						<image src="https://i.loli.net/2020/07/15/xjVSvzWcH9NO7al.png" mode="" class="icon-triangle" v-else></image>
+					</template>
+				</view>
+            </view>
+            <view scroll-y="true" class="popup" :class="popupShow ? 'popupShow' : ''">
+                <scroll-view scroll-y="true" style="height: 30vh;"><slot></slot></scroll-view>
+				<view class="footer x-bc">
+					<view class="footer-btn" @click="reset">重置</view>
+					<view class="footer-btn footer-blue" @click="confirm">确认</view>
+				</view>
+            </view>
+        </view>
+    </view>
+</template>
+
+<script>
+// import { getCurDateTime } from '@/libs/utils.js';
+export default {
+    props: {
+        height: {
+            type: Number,
+            default: 40
+        },
+        // top: {
+        //     type: String,
+        //     default: 'calc(var(--window-statsu-bar) + 44px)'
+        // },
+        border: {
+            type: Boolean,
+            default: false
+        },
+        filterData: {
+            //必填
+            type: Array,
+            default: () => {
+                return []
+            }
+        },
+        defaultIndex: {
+            //默认选中条件索引,超出一类时必填
+            type: Array,
+            default: () => {
+                return [0];
+            }
+        }
+    },
+    data() {
+        return {
+			top: 0,
+            navData: [],
+            popupShow: false,
+            showMask: false,
+            actNav: null,
+         
+            selIndex: [] //选中条件索引
+        };
+    },
+    created() {
+        this.navData = this.filterData;
+        this.selIndex = this.defaultIndex;
+        // this.keepStatus();
+    },
+    mounted() {
+		const query = uni.createSelectorQuery().in(this);
+		query
+		  .select(".filter-wrapper")
+		  .boundingClientRect((data) => {
+			console.log(data)
+			this.top = data.top + 'px'
+		  })
+		  .exec();
+    },
+    methods: {
+		reset() {
+			this.$emit('reset');
+		},
+		confirm() {
+			this.showMask = false;
+			this.popupShow = false;
+			this.$emit('confirm');
+		},
+        keepStatus() {
+            // this.navData.forEach(itemnavData => {
+            //     itemnavData.map(child => {
+            //         child.select = false;
+            //     });
+            //     return itemnavData;
+            // });
+            // for (let i = 0; i < this.selIndex.length; i++) {
+            //     let selindex = this.selIndex[i];
+            //     this.navData[i][selindex].select = true;
+            // }
+        },
+        navClick(index) {
+            if (index === this.actNav) {
+                this.tapMask();
+                return;
+            }
+            this.popupShow = true;
+            this.showMask = true;
+            this.actNav = index;
+			this.$emit('onChange', this.actNav);
+        },
+        handleOpt(index) {
+            // this.selIndex[this.actNav] = index;
+            // this.keepStatus();
+            // setTimeout(() => {
+            //     this.tapMask();
+            // }, 100);
+            // let data = [];
+            // let res = this.navData.forEach(item => {
+            //     let sel = item.filter(child => child.select);
+            //     data.push(sel);
+            // });
+            // console.log(data);
+            // this.$emit('onSelected', data);
+        },
+        tapMask() {
+            this.showMask = false;
+            this.popupShow = false;
+            this.actNav = null;
+        },
+        discard() {}
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.c-flex-align {
+    display: flex;
+    align-items: center;
+}
+.c-flex-center {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    flex-direction: column;
+}
+.footer {
+	font-family: PingFang SC, PingFang SC;
+	font-weight: 400;
+	font-size: 14px;
+	color: #222;
+	padding: 10px;
+	&-btn {
+		flex: 1;
+		text-align: center;
+		background-color: #fff;
+		border: 1px solid #999;
+		border-radius: 50px;
+		padding: 10px 0;
+	}
+	&-blue {
+		background-color: #1677ff;
+		border: 1px solid #1677ff;
+		color: #fff;
+		margin-left: 10px;
+	}
+}
+.filter-wrapper {
+    // position: fixed;
+    // left: 0;
+    // width: 750rpx;
+    // z-index: 999;
+    .inner-wrapper {
+        // position: relative;
+        .navs {
+            position: relative;
+            font-family: PingFang SC, PingFang SC;
+            font-weight: 400;
+            font-size: 14px;
+            color: #222;
+            padding: 10px;
+            display: flex;
+            align-items: center;
+            justify-content: space-between;
+            background-color: #fff;
+            z-index: 999;
+            box-sizing: border-box;
+            & > view {
+                flex: 1;
+                height: 100%;
+                flex-direction: row;
+                z-index: 999;
+            }
+            .date {
+                justify-content: flex-end;
+            }
+            .actNav {
+                color: #1677ff;
+                font-weight: bold;
+            }
+        }
+        .mask {
+            z-index: 666;
+            position: fixed;
+            top:0;
+            left: 0;
+            right: 0;
+            bottom: 0;
+            background-color: rgba(0, 0, 0, 0);
+            transition: background-color 0.15s linear;
+            &.show {
+                background-color: rgba(0, 0, 0, 0.4);
+            }
+            &.hide {
+                display: none;
+            }
+        }
+        .popup {
+            position: relative;
+            // max-height: 500rpx;
+            background-color: #fff;
+            border-bottom-left-radius: 20rpx;
+            border-bottom-right-radius: 20rpx;
+            overflow: scroll;
+            z-index: 999;
+            transition: all 1s linear;
+            opacity: 0;
+            display: none;
+            .item-opt {
+                height: 100rpx;
+                padding: 0 40rpx;
+                color: #8b9aae;
+                border-bottom: 2rpx solid #f5f6f9;
+            }
+            .actOpt {
+                color: #1677ff;
+                font-weight: bold;
+                position: relative;
+                &::after {
+                    content: '✓';
+                    font-weight: bold;
+                    font-size: 36rpx;
+                    position: absolute;
+                    right: 40rpx;
+                }
+            }
+        }
+        .popupShow {
+            display: block;
+            opacity: 1;
+        }
+    }
+
+    .icon-triangle {
+        width: 16rpx;
+        height: 16rpx;
+        margin-left: 10rpx;
+    }
+}
+</style>

+ 132 - 0
pages/courseManage/components/vipUserItem.vue

@@ -0,0 +1,132 @@
+<template>
+	<view class="list-item">
+		<view class="list-item-head x-bc">
+			<view class="x-f" style="flex: 1;overflow: hidden;">
+				<u-avatar :src='item.avatar'></u-avatar>
+				<view class="list-item-head-l">
+					<view style="flex: 1;overflow: hidden;display: flex;">
+						<text class="list-item-name one-t">{{item.nickName}}</text>
+						<image class="list-item-copy" src="@/static/images/copy_icon.png" mode="aspectFill"></image>
+					</view>
+					<view class="list-item-re">注册时间:{{item.createTime?item.createTime.substring(0,10):'--'}}</view>
+				</view>
+			</view>
+			<image class="phone" src="@/static/logo.png" mode="aspectFill"></image>
+		</view>
+		<view class="list-item-desc">
+			<view class="taglist">
+				<view><u-tag text="归属消失殆尽" size="mini" color="#1677ff" bgColor="#fff" borderColor="#fff"></u-tag></view>
+				<view v-for="(tag,i) in item.tags ? item.tags.split(',') : []">
+					<u-tag :text="tag" size="mini" color="#999" bgColor="#f5f5f5" borderColor="#f5f5f5"></u-tag>
+				</view>
+			</view>
+			<!-- <view style="margin-top: 5px;">
+				<text class="label" style="color:red;margin-right: 20px;">停止看课{{item.watchCount || 0}}天</text>
+				<text class="label">参与营期</text><text class="value-num">{{item.watchCount || 0}}</text>
+				<text class="label">缺课数量</text><text class="value-num" style="color:red">{{item.watchComlpleteCount || 0}}</text>
+			</view> -->
+		</view>
+		<view class="list-item-footer x-f">
+			<view class="list-item-footer-btn footer-tagbtn x-f" @click="openModel('tag',item)">改标签</view>
+			<view class="list-item-footer-btn footer-red x-f" @click="openModel('tag',item)">禁用</view>
+			<view class="list-item-footer-btn footer-red x-f" @click="openModel('tag',item)">更换归属</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		
+		data() {
+			return {
+				item: {
+					tags: '0000,小红,标签'
+				}
+			}
+		},
+		methods: {
+			openModel() {
+				
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	.list-item{
+		padding: 10px;
+		margin-bottom: 10px;
+		background-color: #fff;
+		border-radius: 12px;
+		&-head {
+			.phone {
+				flex-shrink: 0;
+				width: 30px;
+				height: 30px;
+			}
+			&-l {
+				flex: 1;
+				overflow: hidden;
+				margin-left: 10px;
+				margin-right: 10px;
+			}
+		}
+		&-copy {
+			width: 20px;
+			height: 20px;
+		}
+		&-re {
+			font-size: 10px;
+			margin-top: 5px;
+		}
+		&-desc {
+			padding: 5px;
+			color: #999;
+			font-size: 12px;
+		}
+		.label {
+			margin-right: 4px;
+		}
+		.value-num {
+			margin-right: 18px;
+			color: #222;
+			font-weight: bold;
+		}
+		.taglist {
+			display: flex;
+			align-items: center;
+			flex-wrap: wrap;
+			color: #555;
+			padding-top: 5px;
+			view {
+				margin: 0 5px 5px 0;
+			}
+		}
+		&-footer {
+			justify-content: flex-end;
+			padding: 6px 0;
+			.footer-tagbtn {
+				color: #1677ff;
+				background-color: #e7f2fe;
+				border: 1px solid #c9e1fb;
+			}
+			.footer-red {
+				color: #f93e3e;
+				background-color: #fae7e7;
+				border: 1px solid #f7a1a1;
+			}
+			&-btn {
+				height: 26px;
+				padding: 0 10px;
+				margin-left: 10px;
+				background-color: #f5f5f5;
+				border-radius: 25px;
+				font-weight: 400;
+				font-size: 10px;
+				line-height: 26px;
+				border: 1px solid #f5f5f5;
+				box-sizing: border-box;
+			}
+		}
+	}
+</style>

+ 83 - 44
pages/courseManage/course/index.vue

@@ -1,73 +1,111 @@
 <template>
 	<view>
 		<view class="training-camp">
-			<view class="training-camp-btn" @click="choose">请选择训练营</view>
+			<view class="training-camp-btn" @click="choose">{{title}}</view>
 		</view>
-		<u-picker :show="show" :columns="columns" title="训练营选择" @confirm="confirm" @cancel="cancel"></u-picker>
+		<u-picker ref="upicker" :show="show" :columns="columns" title="训练营选择" keyName="courseName" @confirm="confirm" @cancel="cancel"></u-picker>
 		<view class="container-body x-start" :style="{height: contentH}">
-			<view class="container-left">
+			<!-- <view class="container-left">
 				<view class="classification active">全部</view>
 				<view class="classification">测试</view>
-			</view>
+			</view> -->
 			<view class="container-right">
-				<!-- <mescroll-body top="88rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback"> -->
+				<mescroll-body top="0"  bottom="0" ref="mescrollRef" @init="mescrollInit" :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
 					<view class="list">
-						<courseItem :from="'course'" :activeTab="0" v-for="i in 9" />
+						<courseItem :from="'course'" :activeTab="0" v-for="(item,index) in dataList" :key="index" :info="item" />
 					</view>
-				<!-- </mescroll-body> -->
+				</mescroll-body>
 			</view>
 		</view>
 	</view>
 </template>
 
 <script>
+	import { getFsCourseList,getCourseVdieoList } from "@/api/courseManage.js"
 	import courseItem from "../components/courseItem.vue"
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
 	export default {
+		mixins: [MescrollMixin], 
 		components: {
 			courseItem
 		},
 		data() {
 			return {
+				title: '请选择训练营',
 				contentH: 0,
 				show: false,
-				columns: [
-					['中国', '美国', '日本']
-				],
+				columns: [[]],
 				mescroll:null,
 				downOption: {
-					auto:false//不要自动加载
+					use:true,
+					auto: false
 				},
 				upOption: {
 					onScroll:false,
 					use: true, // 是否启用上拉加载; 默认true
+					auto: false,
 					page: {
-						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						pae: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
 						size: 10 // 每页数据的数量,默认10
 					},
 					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
 					textNoMore:"已经到底了",
 					empty: {
-						icon:'/static/images/empty.png',
+						icon:'https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png',
 						tip: '暂无数据'
 					}
 				},
-				dataList: []
+				dataList: [],
+				user: {},
+				courseList: [],
+				chooseIndex: [0],
+				courseId: ''
 			}
 		},
 		mounted() {
+			console.log("======cou")
 			const windowHeight = uni.getSystemInfoSync().windowHeight
 			this.contentH = `calc(${windowHeight}px - 52px - 56px)`
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.getFsCourseList()
 		},
 		methods: {
 			choose() {
 				this.show = true
+				this.$refs.upicker.setIndexs(this.chooseIndex)
 			},
 			cancel() {
 				this.show = false
 			},
 			confirm(e) {
-				console.log('confirm', e)
+				this.chooseIndex = e.indexs
+				this.courseId = e.value[0].courseId
+				this.title = e.value[0].courseName
 				this.show = false
+				this.mescroll.resetUpScroll()
+			},
+			// 训练营
+			getFsCourseList() {
+				const day = uni.$u.timeFormat(new Date(), 'yyyy-mm-dd')
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getFsCourseList(param).then(res=>{
+					if(res.code==200){
+						this.courseList = res.data || []
+						this.columns = [this.courseList]
+						this.courseId = this.courseList && this.courseList.length > 0 ? this.courseList[0].courseId : ''
+						this.title = this.courseList && this.courseList.length > 0 ? this.courseList[0].courseName : '请选择训练营'
+						this.mescroll.resetUpScroll()
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
 			},
 			mescrollInit(mescroll) {
 				this.mescroll = mescroll;
@@ -77,38 +115,39 @@
 				mescroll.resetUpScroll()
 			},
 			upCallback(page) {
-				// //联网加载数据
-				// var that = this;
-				// var data={
-				// 	customerName:this.searchKey,
-				// 	pageNum: page.num,
-				// 	pageSize: page.size
-				// };
-				// uni.showLoading({
-				// 	title:"加载中..."
-				// })
-				// getMyCustomerList(data).then(res => {
-				// 	uni.hideLoading()
-				// 	if(res.code==200){
-				// 		//设置列表数据
-				// 		if (page.num == 1) {
-				// 			that.dataList = res.data.list; 
+				//联网加载数据
+				var that = this;
+				var data={
+					courseId:this.courseId,
+					status: '',
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getCourseVdieoList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
 							
-				// 		} else {
-				// 			that.dataList = that.dataList.concat(res.data.list);
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
 							 
-				// 		}
-				// 		that.mescroll.endBySize(res.data.list.length, res.data.total);
+						}
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
 						
-				// 	}else{
-				// 		uni.showToast({
-				// 			icon:'none',
-				// 			title: "请求失败",
-				// 		});
-				// 		that.dataList = null;
-				// 		that.mescroll.endErr();
-				// 	}
-				// });
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
 			},
 		}
 	}

+ 197 - 41
pages/courseManage/dataIndex/index.vue

@@ -6,11 +6,11 @@
 				<view>数据汇总</view>
 				<view class="x-bc">
 					<view class="topdata-item">
-						<view class="topdata-item-num"><text>0</text>人</view>
+						<view class="topdata-item-num"><text>{{companyUserUserCount}}</text>人</view>
 						<view>会员总数</view>
 					</view>
 					<view class="topdata-item">
-						<view class="topdata-item-num"><text>0</text>人</view>
+						<view class="topdata-item-num"><text>{{companyUserCount}}</text>人</view>
 						<view>今日新增会员</view>
 					</view>
 				</view>
@@ -20,97 +20,102 @@
 					<view :class="queryParam.type == index ? 'searchbox-item searchbox-active':'searchbox-item'"
 						v-for="(item,index) in typeOption" :key="index" @click="handleType(index)">{{item.label}}</view>
 				</view>
+				<view class="x-ac ss" v-show="queryParam.type == 3">
+					<view class="calendar-day x-c" @click="showCalendar = true"><u-icon name="calendar" color="#999" size="20"></u-icon>{{startTime}}</view>
+					<view class="calendar-day x-c" @click="showCalendar = true"><u-icon name="calendar" color="#999" size="20"></u-icon>{{endTime}}</view>
+				</view>
 			</view>
 		</view>
+		<u-calendar :show="showCalendar" :mode="mode" @confirm="confirmCalendar" @close="closeCalendar"></u-calendar>
 		<view class="statistics" :style="{height: contentH}">
 			<u-collapse :border='false' :value='collapseValue' @change="changeCollapse">
 				<u-collapse-item name="course">
 					<text slot="title" class="statistics-title">课程统计</text>
 					<text slot="value" class="statistics-slot-title">{{collapseValue.includes('course')?'收回':'展开'}}</text>
-					<text slot="right-icon">
+					<view slot="right-icon">
 						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-					</text>
+					</view>
 					<view class="collapse-content x-ac">
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">观看人数</view>
-							<view class="collapse-content-num"><text>0</text>人</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0</view>
-							<view v-show="queryParam.type == 0">昨天0</view>
+							<view class="collapse-content-num"><text>{{courseCount.count || 0}}</text>人</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(courseCount.count,courseCount.yesterdayCount)}}</view>
+							<view v-show="queryParam.type == 0">昨天{{courseCount.yesterdayCount || 0}}</view>
 						</view>
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">完播人数</view>
-							<view class="collapse-content-num"><text>0</text>人</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0</view>
-							<view v-show="queryParam.type == 0">昨天0</view>
+							<view class="collapse-content-num"><text>{{courseCount.completeCount || 0}}</text>人</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(courseCount.completeCount,courseCount.yesterdayCountCompleteCount)}}</view>
+							<view v-show="queryParam.type == 0">昨天{{courseCount.yesterdayCountCompleteCount || 0}}</view>
 						</view>
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">完播率</view>
-							<view class="collapse-content-num"><text>0</text>%</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0</view>
-							<view v-show="queryParam.type == 0">昨天0</view>
+							<view class="collapse-content-num"><text>{{courseCount.rate || 0}}</text>%</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(courseCount.rate,courseCount.yesterdayRate)}}</view>
+							<view v-show="queryParam.type == 0">昨天{{courseCount.yesterdayRate || 0}}</view>
 						</view>
 					</view>
 				</u-collapse-item>
 				<u-collapse-item name="questions">
 					<text slot="title" class="statistics-title">答题统计</text>
 					<text slot="value" class="statistics-slot-title">{{collapseValue.includes('questions')?'收回':'展开'}}</text>
-					<text slot="right-icon">
+					<view slot="right-icon">
 						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-					</text>
+					</view>
 					<view class="collapse-content x-ac">
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">答题人数</view>
-							<view class="collapse-content-num"><text>0</text>人</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0</view>
-							<view v-show="queryParam.type == 0">昨天0</view>
+							<view class="collapse-content-num"><text>{{quesCount.count || 0}}</text>人</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(quesCount.count,quesCount.yesterdayCount)}}</view>
+							<view v-show="queryParam.type == 0">昨天{{quesCount.yesterdayCount || 0}}</view>
 						</view>
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">正确人数</view>
-							<view class="collapse-content-num"><text>0</text>人</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0</view>
-							<view v-show="queryParam.type == 0">昨天0</view>
+							<view class="collapse-content-num"><text>{{quesCount.completeCount || 0}}</text>人</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(quesCount.completeCount,quesCount.yesterdayCountCompleteCount)}}</view>
+							<view v-show="queryParam.type == 0">昨天{{quesCount.yesterdayCountCompleteCount || 0}}</view>
 						</view>
 						<view class="collapse-content-item">
 							<view class="collapse-content-title">正确率</view>
-							<view class="collapse-content-num"><text>0</text>%</view>
-							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0</view>
-							<view v-show="queryParam.type == 0">昨天0</view>
+							<view class="collapse-content-num"><text>{{quesCount.rate || 0}}</text>%</view>
+							<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(quesCount.rate,quesCount.yesterdayRate)}}</view>
+							<view v-show="queryParam.type == 0">昨天{{quesCount.yesterdayRate || 0}}</view>
 						</view>
 					</view>
 				</u-collapse-item>
 				<u-collapse-item name="redenvelope">
 					<text slot="title" class="statistics-title">红包统计</text>
 					<text slot="value" class="statistics-slot-title">{{collapseValue.includes('redenvelope')?'收回':'展开'}}</text>
-					<text slot="right-icon">
+					<view slot="right-icon">
 						<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-					</text>
+					</view>
 					<view>
 						<view class="collapse-content x-ac">
 							<view class="collapse-content-item">
 								<view class="collapse-content-title">答题红包数</view>
-								<view class="collapse-content-num"><text>0</text>个</view>
-								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0</view>
-								<view v-show="queryParam.type == 0">昨天0</view>
+								<view class="collapse-content-num"><text>{{redPacketCount.count || 0}}</text>个</view>
+								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(redPacketCount.count,redPacketCount.yesterdayCount)}}</view>
+								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayCount || 0}}</view>
 							</view>
 							<view class="collapse-content-item">
 								<view class="collapse-content-title">答题红包金额</view>
-								<view class="collapse-content-num"><text>0.00</text>元</view>
-								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0.00</view>
-								<view v-show="queryParam.type == 0">昨天0.00</view>
+								<view class="collapse-content-num"><text>{{redPacketCount.amount || '0.00'}}</text>元</view>
+								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(redPacketCount.amount,redPacketCount.yesterdayAmount,1)}}</view>
+								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayAmount || '0.00'}}</view>
 							</view>
 						</view>
 						<view class="collapse-content x-ac" style="padding: 8px 0;">
 							<view class="collapse-content-item">
 								<view class="collapse-content-title">新会员奖励数</view>
-								<view class="collapse-content-num"><text>0</text>个</view>
-								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0</view>
-								<view v-show="queryParam.type == 0">昨天0</view>
+								<view class="collapse-content-num"><text>{{redPacketCount.newCount || 0}}</text>个</view>
+								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(redPacketCount.newCount,redPacketCount.yesterdayNewCount)}}</view>
+								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayNewCount || 0}}</view>
 							</view>
 							<view class="collapse-content-item">
 								<view class="collapse-content-title">新会员奖励金额</view>
-								<view class="collapse-content-num"><text>0.00</text>元</view>
-								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平0.00</view>
-								<view v-show="queryParam.type == 0">昨天0.00</view>
+								<view class="collapse-content-num"><text>{{redPacketCount.totalAmount || '0.00'}}</text>元</view>
+								<view style="color: #1677ff;" v-show="queryParam.type == 0">较昨天持平{{compare(redPacketCount.totalAmount,redPacketCount.yesterdayTotalAmount,1)}}</view>
+								<view v-show="queryParam.type == 0">昨天{{redPacketCount.yesterdayTotalAmount || '0.00'}}</view>
 							</view>
 						</view>
 					</view>
@@ -121,9 +126,12 @@
 </template>
 
 <script>
+	import {getCompanyUserAndUserCount,getCourseCount,getQuesCount,getRecPacketCount} from "@/api/courseManage.js"
 	export default {
 		data() {
 			return {
+				showCalendar: false,
+				mode: 'range',
 				typeOption: [{
 					label: '今日',
 					value: 0
@@ -141,19 +149,157 @@
 					type: 0
 				},
 				contentH: 0,
-				collapseValue:['course','questions','redenvelope']
+				collapseValue:['course','questions','redenvelope'],
+				user: {},
+				todayday: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
+				startTime: '',
+				endTime: '',
+				companyUserCount: 0,
+				companyUserUserCount: 0,
+				courseCount: {},
+				quesCount: {},
+				redPacketCount: {}
+			}
+		},
+		computed: {
+			compare() {
+				return (today,yesterday,type)=>{
+					const num = Number(yesterday || 0) - Number(today || 0)
+					return type == 1 ? num.toFixed(2) : 0
+				}
 			}
 		},
 		mounted() {
-			const windowHeight = uni.getSystemInfoSync().windowHeight
-			this.contentH = `calc(${windowHeight}px - 180px - 56px)`
+			this.getHeight()
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			// this.getUserCount()
+			// this.resetDate()
 		},
 		methods: {
+			getHeight() {
+				const windowHeight = uni.getSystemInfoSync().windowHeight
+				const query = uni.createSelectorQuery().in(this);
+				query
+				  .select(".topdata")
+				  .boundingClientRect((data) => {
+					this.contentH = `calc(${windowHeight}px - ${data.height}px - 56px)`
+				  })
+				  .exec();
+			},
+			resetDate() {
+				if(this.queryParam.type == 0) {
+					this.startTime = this.todayday
+					this.endTime = this.todayday
+				} else if(this.queryParam.type == 1) {
+					let yesterday = new Date();
+					yesterday.setDate(yesterday.getDate() - 1);
+					
+					this.startTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd')
+					this.endTime = uni.$u.timeFormat(yesterday, 'yyyy-mm-dd')
+				} else if(this.queryParam.type == 2) {
+					let today = new Date();
+					let lastDayOfMonth = new Date(today.getFullYear(), today.getMonth() + 1, 0);
+				
+					this.startTime = uni.$u.timeFormat(this.todayday, 'yyyy-mm') + '-01'
+					this.endTime = uni.$u.timeFormat(lastDayOfMonth, 'yyyy-mm-dd')
+				} else if(this.queryParam.type == 3) {
+					this.startTime = this.todayday
+					this.endTime = this.todayday
+				}
+				this.getCount()
+			},
 			handleType(type) {
+				if(this.queryParam.type == type) {
+					return
+				}
 				this.queryParam.type = type
+				if(this.queryParam.type == 3) {
+					this.getHeight()
+				}
+				this.resetDate()
 			},
 			changeCollapse(e) {
 				this.collapseValue = e.filter(item=>item.status == 'open').map(it=>it.name)
+			},
+			closeCalendar() {
+				this.showCalendar = false
+			},
+			confirmCalendar(e) {
+				this.startTime = e[0]
+				this.endTime =  e[e.length-1]
+				this.showCalendar = false
+				this.getCount()
+			},
+			getUserCount() {
+				const day = uni.$u.timeFormat(new Date(), 'yyyy-mm-dd')
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: '',
+					endTime: day + ' 23:59:59',
+					startTime: day + ' 00:00:00',
+					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getCompanyUserAndUserCount(param).then(res=>{
+					if(res.code==200){
+						this.companyUserCount = res.data.companyUserCount || 0
+						this.companyUserUserCount = res.data.companyUserUserCount || 0
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			getCount() {
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: '',
+					endTime: this.endTime + ' 23:59:59',
+					startTime: this.startTime + ' 00:00:00',
+					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				this.getCourseCount(param)
+				this.getQuesCount(param)
+				this.getRecPacketCount(param)
+			},
+			getCourseCount(param){
+				getCourseCount(param).then(res=>{
+					if(res.code==200){
+						this.courseCount = res.data
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			getQuesCount(param){
+				getQuesCount(param).then(res=>{
+					if(res.code==200){
+						this.quesCount = res.data
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			getRecPacketCount(param){
+				getRecPacketCount(param).then(res=>{
+					if(res.code==200){
+						this.redPacketCount = res.data
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
 			}
 		}
 	}
@@ -166,6 +312,16 @@
 		font-size: 14px;
 		color: #222222;
 	}
+	.calendar-day {
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 400;
+		font-size: 12px;
+		color: #999;
+		flex: 1;
+		margin: 10px 10px 0 10px;
+		background-color: #f5f5f5;
+		border-radius: 4px;
+	}
 	.topdata {
 		padding-top: 25px;
 		background-color: rgb(216, 232, 255);

+ 6 - 3
pages/courseManage/index.vue

@@ -2,9 +2,9 @@
 	<view>
 		<view class="app-wrapper" :style="{height: bodyHeight}">
 			<dataIndex v-if="templateView == 'dataIndex'"></dataIndex>
-			<liveIndex v-show="templateView == 'liveIndex'"></liveIndex>
-			<courseIndex v-show="templateView == 'courseIndex'"></courseIndex>
-			<vipIndex v-show="templateView == 'vipIndex'"></vipIndex>
+			<!-- <liveIndex ref="mescrollItem1" v-show="templateView == 'liveIndex'"></liveIndex>
+			<courseIndex ref="mescrollItem2" v-show="templateView == 'courseIndex'"></courseIndex> -->
+			<vipIndex ref="mescrollItem3" v-show="templateView == 'vipIndex'"></vipIndex>
 		</view>
 		<view class="myTabBar x-f x-bc">
 			<view class="myTabBar-item x-f y-f" @click="handleTab('dataIndex')">
@@ -28,11 +28,13 @@
 </template>
 
 <script>
+	import MescrollCompMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mixins/mescroll-comp.js";
 	import dataIndex from "./dataIndex/index.vue"
 	import liveIndex from "./live/index.vue"
 	import courseIndex from "./course/index.vue"
 	import vipIndex from "./vip/index.vue"
 	export default {
+		mixins: [MescrollCompMixin],
 		components: {
 			dataIndex,
 			liveIndex,
@@ -45,6 +47,7 @@
 				statusBarHeight: uni.getSystemInfoSync().statusBarHeight,
 				windowHeight: uni.getSystemInfoSync().windowHeight,
 				bodyHeight: 0,
+				tabIndex: 0
 			}
 		},
 		onLoad() {

+ 131 - 40
pages/courseManage/live/index.vue

@@ -18,33 +18,62 @@
 					<image src="@/static/images/finished.png" mode="aspectFill"></image>
 				</view>
 			</view>
-			<view class="x-bc">
+			<!-- <view class="x-bc">
 				<view :class="searchbarNav == index ? 'searchbar x-c searchbar-active':'searchbar x-c'" v-for="(item,index) in searchbar" :key="index" @click="clickSearchbar(index)">
 					<text>{{item.name}}</text><u-icon class="arrow-down" name="arrow-down" :color="searchbarNav == index ?'#1677ff':'#999'" size="12"></u-icon>
 				</view>
+			</view> -->
+			<view>
+				<dropdownPanel :filterData='filterData' @onChange="onChange" @confirm="confirm" @reset="reset">
+					<view v-if="searchbarNav == 0">
+						<view class="boxnav x-bc">
+							<view class="boxnav-item" v-for="(item,index) in courseList" :key="index">
+								<view class="boxnav-item-info one-t" :class="courserIndex == item.courseId ? 'boxnav-active':''" @click="handleCourse(item)">{{item.courseName}}</view>
+							</view>
+						</view>
+					</view>
+				</dropdownPanel>
 			</view>
 		</view>
 		<view class="coursebox" :style="{height: contentH}">
-			<!-- <mescroll-body top="88rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback"> -->
+			<mescroll-body  bottom="0" ref="mescrollRef" @init="mescrollInit" :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
 				<view class="courselist">
-					<courseItem :activeTab="activeTab" />
+					<courseItem :activeTab="activeTab" v-for="(item,index) in dataList" :key="index" :info="item" />
 				</view>
-			<!-- </mescroll-body> -->
+			</mescroll-body>
 		</view>
 	</view>
 </template>
 
 <script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	import { getFsCourseList,getCourseVdieoList } from "@/api/courseManage.js"
+	import dropdownPanel  from "../components/dropdownPanel.vue"
 	import courseItem from "../components/courseItem.vue"
 	export default {
+		mixins: [MescrollMixin], 
 		components: {
-			courseItem
+			courseItem,
+			dropdownPanel
 		},
 		data() {
 			return {
+				user: {},
+				filterData:[{
+					name: '训练营-营期',
+					value: 0,
+				},
+				{
+					name: '课程状态',
+					value: 1,
+				}],
+				defaultIndex:[0,0],
 				contentH: 0,
-				activeTab: 0,
-				searchbarNav: 'colse',
+				activeTab: 1,
+				courseList: [],
+				courseId: '',
+				searchbarNav: 0,
+				courserIndex: 0,
 				searchbar: [{
 					name: '训练营-营期'
 				},
@@ -53,30 +82,69 @@
 				}],
 				mescroll:null,
 				downOption: {
-					auto:false//不要自动加载
+					use:true,
+					auto: false
 				},
 				upOption: {
 					onScroll:false,
 					use: true, // 是否启用上拉加载; 默认true
+					auto: true,
 					page: {
-						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						pae: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
 						size: 10 // 每页数据的数量,默认10
 					},
 					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
 					textNoMore:"已经到底了",
 					empty: {
-						icon:'/static/images/empty.png',
+						icon:'https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png',
 						tip: '暂无数据'
 					}
 				},
-				dataList: []
+				dataList: [],
 			}
 		},
 		mounted() {
 			const windowHeight = uni.getSystemInfoSync().windowHeight
 			this.contentH = `calc(${windowHeight}px - 132px - 56px)`
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.getFsCourseList()
 		},
 		methods: {
+			onChange(index) {
+				this.searchbarNav = index
+			},
+			confirm() {
+				this.courseId = this.courserIndex
+				this.mescroll.resetUpScroll()
+			},
+			reset() {
+				this.courserIndex = ''
+			},
+			handleCourse(item) {
+				this.courserIndex = item.courseId
+			},
+			// 训练营
+			getFsCourseList() {
+				const day = uni.$u.timeFormat(new Date(), 'yyyy-mm-dd')
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				getFsCourseList(param).then(res=>{
+					if(res.code==200){
+						this.courseList = res.data || []
+						// this.courseId = this.courseList && this.courseList.length > 0 ? this.courseList[0].courseId : ''
+						// this.searchbar[0].name = this.courseList && this.courseList.length > 0 ? this.courseList[0].courseName : '训练营-营期'
+						// this.mescroll.resetUpScroll()
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
 			handleNav(type) {
 				this.activeTab = type
 			},
@@ -91,38 +159,39 @@
 				mescroll.resetUpScroll()
 			},
 			upCallback(page) {
-				// //联网加载数据
-				// var that = this;
-				// var data={
-				// 	customerName:this.searchKey,
-				// 	pageNum: page.num,
-				// 	pageSize: page.size
-				// };
-				// uni.showLoading({
-				// 	title:"加载中..."
-				// })
-				// getMyCustomerList(data).then(res => {
-				// 	uni.hideLoading()
-				// 	if(res.code==200){
-				// 		//设置列表数据
-				// 		if (page.num == 1) {
-				// 			that.dataList = res.data.list; 
+				//联网加载数据
+				var that = this;
+				var data={
+					courseId:this.courseId,
+					status: '',
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				getCourseVdieoList(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
 							
-				// 		} else {
-				// 			that.dataList = that.dataList.concat(res.data.list);
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
 							 
-				// 		}
-				// 		that.mescroll.endBySize(res.data.list.length, res.data.total);
+						}
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
 						
-				// 	}else{
-				// 		uni.showToast({
-				// 			icon:'none',
-				// 			title: "请求失败",
-				// 		});
-				// 		that.dataList = null;
-				// 		that.mescroll.endErr();
-				// 	}
-				// });
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
 			},
 		}
 	}
@@ -135,6 +204,28 @@
 		font-size: 14px;
 		color: #222;
 	}
+	.boxnav {
+		flex-wrap: wrap;
+		padding: 0 0 0 10px;
+		&-item {
+			width: 50%;
+			overflow: hidden;
+		}
+		&-item-info {
+			border: 1px solid #f5f5f5;
+			text-align: center;
+			color: #222;
+			background-color: #f5f5f5;
+			border-radius: 3px;
+			padding: 5px;
+			margin: 0 10px 10px 0;
+		}
+		&-active {
+			border: 1px solid #1677ff !important;
+			color: #1677ff !important;
+			background-color: #e7f1fe !important;
+		}
+	}
 	.headbox {
 		background-color: #fff;
 	}

+ 668 - 518
pages/courseManage/statistics.vue

@@ -1,526 +1,676 @@
-<template>
-	<view class="container">
-		<image class="topbg" src="https://sg-mmyy-oss.yjfzy.com/sgs/image/20241102/e07b38daf749cb02e9f5ce69c57b44ea.png" mode="scaleToFill"></image>
-		<view class="container-body">
-			<view class="coursebox">
-				<view class="coursebox-info">
-					<view class="status">进行中</view>
-					<!-- <view class="status" style="background-color: #ff4746;">已结束</view> -->
-					<view class="coursebox-name">
-						<text class="more-t">7.明医有话说-杜老师给您讲讲抑郁杜老师给您讲讲抑郁</text>
-						<view class="btn_icon">ID
-							<image src="@/static/images/copy_icon.png" mode="aspectFill"></image>
-						</view>
-					</view>
-					<!-- <view class="coursebox-name">7.明医有话说-杜老师给您讲讲抑郁杜老师给您讲讲抑郁</view> -->
-					<view style="margin-top: 5px;">明医有话说-七天先导课(长期有效,自行选择)简约课程</view>
-					<view class="coursebox-time x-start">
-						<view class="coursebox-time-item" style="margin-right: 14px;">
-							<view>直播时间</view>
-							<view class="coursebox-time-t">2024-08-20 00:00:00-2029-09-30 23:59:59</view>
-						</view>
-						<view class="coursebox-time-item duration">
-							<view>课程时长</view>
-							<view class="coursebox-time-t">12分18秒</view>
-						</view>
-					</view>
-				</view>
-			</view>
-			<view class="statistics">
-				<u-tabs :list="list1" :current='current' :scrollable="false" :lineWidth="40" lineColor="#1677ff" :activeStyle="activeStyle" :inactiveStyle="inactiveStyle" @click="clickTab"></u-tabs>
-				<u-collapse :border='false' :value='collapseValue' @change="changeCollapse" v-if="current == 0">
-					<u-collapse-item name="live">
-						<view slot="title" class="statistics-title">直播数据<text class="statistics-title-tip">2分钟自动更新</text></view>
-						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('live')?'收回':'展开'}}</text>
-						<text slot="right-icon">
-							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-						</text>
-						<view class="collapse-content x-ac">
-							<view class="collapse-content-item">
-								<view class="collapse-content-title">观看人数</view>
-								<view class="collapse-content-num"><text>0</text>人</view>
-							</view>
-							<view class="collapse-content-item">
-								<view class="collapse-content-title">完播人数</view>
-								<view class="collapse-content-num"><text>0</text>人</view>
-							</view>
-							<view class="collapse-content-item">
-								<view class="collapse-content-title">完播率</view>
-								<view class="collapse-content-num"><text>0</text>%</view>
-							</view>
-						</view>
-					</u-collapse-item>
-					<u-collapse-item name="questions">
-						<text slot="title" class="statistics-title">答题数据<text class="statistics-title-tip">2分钟自动更新</text></text>
-						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('questions')?'收回':'展开'}}</text>
-						<text slot="right-icon">
-							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-						</text>
-						<view class="collapse-content">
-							<view class="x-ac">
-								<view class="collapse-content-item">
-									<view class="collapse-content-title">答题人数</view>
-									<view class="collapse-content-num"><text>0</text>人</view>
-								</view>
-								<view class="collapse-content-item">
-									<view class="collapse-content-title">正确人数</view>
-									<view class="collapse-content-num"><text>0</text>人</view>
-								</view>
-								<view class="collapse-content-item">
-									<view class="collapse-content-title">答题红包数</view>
-									<view class="collapse-content-num"><text>0</text>个</view>
-								</view>
-							</view>
-							<view class="redenvelope x-bc">
-								<view class="x-f">
-									<image src="@/static/images/redenvelope.png" mode="aspectFill"></image>
-									<text>答题红包金额</text>
-								</view>
-								<view class="collapse-content-num"><text>0.00</text>元</view>
-							</view>
-						</view>
-					</u-collapse-item>
-					<u-collapse-item name="funnelplot">
-						<text slot="title" class="statistics-title">转化漏斗图<text class="statistics-title-tip">2分钟自动更新</text></text>
-						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('funnelplot')?'收回':'展开'}}</text>
-						<text slot="right-icon">
-							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
-						</text>
-						<view>
-							ddd
-						</view>
-					</u-collapse-item>
-				</u-collapse>
-			</view>
-			<!-- 参与记录 -->
-			<view>
-				<view class="participate-search">
-					<view class="x-bc">
-						<u-search placeholder="会员ID、昵称、姓名、手机" v-model="keyword" :showAction="false" height="30px"></u-search>
-						<u-button type="primary" class="refresh" color='#1677ff' size="small" :disabled="disabled" text="刷新"></u-button>
-					</view>
-					<view class="x-bc">
-						<u-tabs :list="list2" :current='currentType' :lineWidth="40" lineColor="#1677ff" :activeStyle="activeStyle" :inactiveStyle="inactiveStyle" @click="clickTypeTab"></u-tabs>
-						<view class="participate-order x-f">
-							<image src="@/static/images/order_icon2.png" mode="aspectFill" v-if="searchTypeIndex == 3"></image>
-							<image src="@/static/images/order_icon.png" mode="aspectFill" v-else></image>
-							<picker @change="bindPickerChange" :value="searchTypeIndex" :range="typeArray">
-								{{typeoption[searchTypeIndex]}}
-							</picker>
-						</view>
-					</view>
-				</view>
-				<!-- <mescroll-body top="88rpx" bottom="0" ref="mescrollRef" @init="mescrollInit"    :down="downOption" :up="upOption" @down="downCallback" @up="upCallback"> -->
-					<view class="list">
-						<view class="list-item">
-							<view class="list-item-head x-bc">
-								<view class="x-f" style="flex: 1;overflow: hidden;">
-									<u-avatar src='http://pic2.sc.chinaz.com/Files/pic/pic9/202002/hpic2119_s.jpg'></u-avatar>
-									<view class="list-item-head-l">
-										<view style="flex: 1;overflow: hidden;display: flex;">
-											<text class="list-item-name one-t">7.明医有话说-杜老师给您讲讲抑郁杜老师给您讲讲抑郁</text>
-											<image class="list-item-copy" src="@/static/images/copy_icon.png" mode="aspectFill"></image>
-										</view>
-										<view class="list-item-re">注册时间:2022-12-12</view>
-									</view>
-								</view>
-								<image class="phone" src="@/static/logo.png" mode="aspectFill"></image>
-							</view>
-							<view class="list-item-desc">
-								<view class="taglist">
-									<view>测试</view>
-								</view>
-								<view style="margin-top: 5px;">
-									<text class="label">观看次数</text><text class="value-num">1</text>
-									<text class="label">完播次数</text><text class="value-num">2</text>
-									<text class="label">累计时长</text><text class="value-num">04分50秒</text>
-								</view>
-								<view style="margin-top: 5px;"><text class="label">红包领取状态</text><text class="value-num">1</text></view>
-								<view style="margin-top: 5px;"><text class="label">观看时间</text><text class="value-num">2023-12-01 12:33</text></view>
-							</view>
-							<view class="list-item-footer x-f">
-								<view class="list-item-footer-btn x-f">改姓名</view>
-								<view class="list-item-footer-btn x-f">改备注</view>
-								<view class="list-item-footer-btn footer-tagbtn x-f">改标签</view>
-							</view>
-						</view>
-					</view>
-				<!-- </mescroll-body> -->
-			</view>
-		</view>
+<template>
+	<view class="container">
+		<image class="topbg" :src="info.thumbnail" mode="scaleToFill"></image>
+		<view class="container-body">
+			<view class="coursebox">
+				<view class="coursebox-info">
+					<!-- <view class="status">进行中</view> -->
+					<!-- <view class="status" style="background-color: #ff4746;">已结束</view> -->
+					<view class="coursebox-name">
+						<text class="more-t">{{info.title}}</text>
+						<view class="btn_icon" @click="copyId">ID
+							<image src="@/static/images/copy_icon.png" mode="aspectFill"></image>
+						</view>
+					</view>
+					<!-- <view class="coursebox-name">7.明医有话说-杜老师给您讲讲抑郁杜老师给您讲讲抑郁</view> -->
+					<view style="margin-top: 5px;">{{info.courseName}}</view>
+					<view class="coursebox-time x-start">
+						<view class="coursebox-time-item" style="margin-right: 14px;">
+							<view>直播时间</view>
+							<view class="coursebox-time-t">{{info.createTime}}</view>
+						</view>
+						<view class="coursebox-time-item duration">
+							<view>课程时长</view>
+							<view class="coursebox-time-t">{{$formatSeconds(info.duration || 0,1)}}</view>
+						</view>
+					</view>
+				</view>
+			</view>
+			<view class="statistics">
+				<u-tabs :list="list1" :current='current' :scrollable="false" :lineWidth="40" lineColor="#1677ff" :activeStyle="activeStyle" :inactiveStyle="inactiveStyle" @click="clickTab"></u-tabs>
+				<u-collapse :border='false' :value='collapseValue' @change="changeCollapse" v-if="current == 0">
+					<u-collapse-item name="live">
+						<view slot="title" class="statistics-title">直播数据<text class="statistics-title-tip">2分钟自动更新</text></view>
+						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('live')?'收回':'展开'}}</text>
+						<text slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</text>
+						<view class="collapse-content x-ac">
+							<view class="collapse-content-item">
+								<view class="collapse-content-title">观看人数</view>
+								<view class="collapse-content-num"><text>{{courseCount.count || 0}}</text>人</view>
+							</view>
+							<view class="collapse-content-item">
+								<view class="collapse-content-title">完播人数</view>
+								<view class="collapse-content-num"><text>{{courseCount.completeCount || 0}}</text>人</view>
+							</view>
+							<view class="collapse-content-item">
+								<view class="collapse-content-title">完播率</view>
+								<view class="collapse-content-num"><text>{{courseCount.rate || 0}}</text>%</view>
+							</view>
+						</view>
+					</u-collapse-item>
+					<u-collapse-item name="questions">
+						<text slot="title" class="statistics-title">答题数据<text class="statistics-title-tip">2分钟自动更新</text></text>
+						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('questions')?'收回':'展开'}}</text>
+						<text slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</text>
+						<view class="collapse-content">
+							<view class="x-ac">
+								<view class="collapse-content-item">
+									<view class="collapse-content-title">答题人数</view>
+									<view class="collapse-content-num"><text>{{quesCount.count || 0}}</text>人</view>
+								</view>
+								<view class="collapse-content-item">
+									<view class="collapse-content-title">正确人数</view>
+									<view class="collapse-content-num"><text>{{quesCount.completeCount || 0}}</text>人</view>
+								</view>
+								<view class="collapse-content-item">
+									<view class="collapse-content-title">答题红包数</view>
+									<view class="collapse-content-num"><text>{{redPacketCount.count || 0}}</text>个</view>
+								</view>
+							</view>
+							<view class="redenvelope x-bc">
+								<view class="x-f">
+									<image src="@/static/images/redenvelope.png" mode="aspectFill"></image>
+									<text>答题红包金额</text>
+								</view>
+								<view class="collapse-content-num"><text>{{redPacketCount.amount || '0.00'}}</text>元</view>
+							</view>
+						</view>
+					</u-collapse-item>
+					<u-collapse-item name="funnelplot">
+						<text slot="title" class="statistics-title">转化漏斗图<text class="statistics-title-tip">2分钟自动更新</text></text>
+						<text slot="value" class="statistics-slot-title">{{collapseValue.includes('funnelplot')?'收回':'展开'}}</text>
+						<text slot="right-icon">
+							<u-icon name="arrow-right" color="#1677ff" size="12"></u-icon>
+						</text>
+						<view>
+							漏斗
+						</view>
+					</u-collapse-item>
+				</u-collapse>
+			</view>
+			<!-- 参与记录 -->
+			<view v-if="current == 1">
+				<view class="participate-search">
+					<view class="x-bc">
+						<u-search placeholder="会员ID、昵称、姓名、手机" v-model="keyword" :showAction="false" height="30px"></u-search>
+						<u-button type="primary" class="refresh" color='#1677ff' size="small" :disabled="disabled" text="刷新" @click="refresh"></u-button>
+					</view>
+					<view class="x-bc">
+						<u-tabs :list="list2" :current='currentType' :lineWidth="40" lineColor="#1677ff" :activeStyle="activeStyle" :inactiveStyle="inactiveStyle" @click="clickTypeTab"></u-tabs>
+						<view class="participate-order x-f">
+							<image src="@/static/images/order_icon2.png" mode="aspectFill" v-if="searchTypeIndex == 3"></image>
+							<image src="@/static/images/order_icon.png" mode="aspectFill" v-else></image>
+							<picker @change="bindPickerChange" :value="searchTypeIndex" :range="typeArray">
+								{{typeoption[searchTypeIndex]}}
+							</picker>
+						</view>
+					</view>
+				</view>
+				<mescroll-body top="0" bottom="0" ref="mescrollRef" @init="mescrollInit" :down="downOption" :up="upOption" @down="downCallback" @up="upCallback">
+					<view class="list">
+						<view class="list-item" v-for="(item,index) in dataList" :key="index">
+							<view class="list-item-head x-bc">
+								<view class="x-f" style="flex: 1;overflow: hidden;">
+									<u-avatar :src='item.avatar'></u-avatar>
+									<view class="list-item-head-l">
+										<view style="flex: 1;overflow: hidden;display: flex;">
+											<text class="list-item-name one-t">{{item.nickName}}</text>
+											<image class="list-item-copy" src="@/static/images/copy_icon.png" mode="aspectFill"></image>
+										</view>
+										<view class="list-item-re">注册时间:{{item.createTime?item.createTime.substring(0,10):'--'}}</view>
+									</view>
+								</view>
+								<image class="phone" src="@/static/logo.png" mode="aspectFill"></image>
+							</view>
+							<view class="list-item-desc">
+								<view class="taglist">
+									<view v-for="(tag,i) in item.tags ? item.tags.split(',') : []">
+										<u-tag :text="tag" size="mini" color="#999" bgColor="#f5f5f5" borderColor="#f5f5f5"></u-tag>
+									</view>
+								</view>
+								<view style="margin-top: 5px;">
+									<text class="label">观看次数</text><text class="value-num">{{item.watchCount || 0}}</text>
+									<text class="label">完播次数</text><text class="value-num">{{item.watchComlpleteCount || 0}}</text>
+									<text class="label">累计时长</text><text class="value-num">{{item.watchTime || '--'}}</text>
+								</view>
+								<view style="margin-top: 5px;">
+									<text class="label">红包领取状态</text>
+									<!-- 发送中  1  已发送 -->
+									<text class="value-num">{{item.redStatus == 1 ? '已发送': item.redStatus == 0 ? '发送中': item.redStatus}}</text>
+								</view>
+								<view style="margin-top: 5px;"><text class="label">观看时间</text><text class="value-num">{{item.watchDate}}</text></view>
+							</view>
+							<view class="list-item-footer x-f">
+								<view class="list-item-footer-btn x-f" @click="openModel('name',item)">改姓名</view>
+								<view class="list-item-footer-btn x-f" @click="openModel('remark',item)">改备注</view>
+								<view class="list-item-footer-btn footer-tagbtn x-f" @click="openModel('tag',item)">改标签</view>
+							</view>
+						</view>
+					</view>
+				</mescroll-body>
+			</view>
+			<!-- 修改名字/备注-->
+			<u-modal :show="modelShow" :title="modelTitle" content='content' class="model" :showCancelButton='true' @cancel="closeModel" @confirm="confirmModel">
+				<view class="setTimebox">
+					<u-input
+						fontSize="14px"
+						:placeholder="modelTitle == '修改姓名' ? '请输入姓名':''"
+						v-model="inputVal"
+						maxlength="5"
+						style="width: 520rpx;"
+					  ></u-input>
+				</view>
+			</u-modal>
+		</view>
 	</view>
 </template>
 
-<script>
-	export default{
-		data() {
-			return {
-				list1: [{
-					name: '课程数据',
-				}, {
-					name: '参与记录',
-				}, {
-					name: '活动节目'
-				}],
-				current: 1,
-				list2: [{
-					name: '答题正确',
-				}, {
-					name: '仅播完',
-				}, {
-					name: '未完播'
-				}, {
-					name: '未播放'
-				}],
-				currentType: 0,
-				activeStyle:{
-					color: '#1677ff',
-					fontSize: '14px',
-					fontWeight: 'bold'
-				},
-				inactiveStyle:{
-					fontSize: '14px'
-				},
-				collapseValue:['live','questions','funnelplot'],
-				keyword: '',
-				disabled: false,
-				searchTypeIndex: 0,
-				typeArray: ['按看课时间从晚到早', '按完播时间从晚到早', '按观看次数从多到少', '按注册时间从晚到早','按会员姓名0-9-A-Z'],
-				typeoption: ['看课时间', '完播时间', '观看次数', '注册时间','会员姓名'],
-				mescroll:null,
-				downOption: {
-					auto:false//不要自动加载
-				},
-				upOption: {
-					onScroll:false,
-					use: true, // 是否启用上拉加载; 默认true
-					page: {
-						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
-						size: 10 // 每页数据的数量,默认10
-					},
-					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
-					textNoMore:"已经到底了",
-					empty: {
-						icon:'/static/images/empty.png',
-						tip: '暂无数据'
-					}
-				},
-				dataList: []
-			}
-		},
-		onLoad() {
-			uni.setNavigationBarTitle({
-				title: '数据统计'
-			});
-		},
-		methods: {
-			clickTab(e) {
-				this.current = e.index
-			},
-			clickTypeTab(e) {
-				this.currentType = e.index
-			},
-			bindPickerChange(e) {
-				console.log('picker发送选择改变,携带值为', e.detail.value)
-				this.searchTypeIndex = e.detail.value
-			},
-			changeCollapse(e) {
-				this.collapseValue = e.filter(item=>item.status == 'open').map(it=>it.name)
-			},
-			mescrollInit(mescroll) {
-				this.mescroll = mescroll;
-			},
-			/*下拉刷新的回调 */
-			downCallback(mescroll) {
-				mescroll.resetUpScroll()
-			},
-			upCallback(page) {
-				// //联网加载数据
-				// var that = this;
-				// var data={
-				// 	customerName:this.searchKey,
-				// 	pageNum: page.num,
-				// 	pageSize: page.size
-				// };
-				// uni.showLoading({
-				// 	title:"加载中..."
-				// })
-				// getMyCustomerList(data).then(res => {
-				// 	uni.hideLoading()
-				// 	if(res.code==200){
-				// 		//设置列表数据
-				// 		if (page.num == 1) {
-				// 			that.dataList = res.data.list; 
-							
-				// 		} else {
-				// 			that.dataList = that.dataList.concat(res.data.list);
-							 
-				// 		}
-				// 		that.mescroll.endBySize(res.data.list.length, res.data.total);
-						
-				// 	}else{
-				// 		uni.showToast({
-				// 			icon:'none',
-				// 			title: "请求失败",
-				// 		});
-				// 		that.dataList = null;
-				// 		that.mescroll.endErr();
-				// 	}
-				// });
-			},
-		}
+<script>
+	import {getCompanyUserAndUserCount,getCourseCount,getQuesCount,getRecPacketCount,getUserLogListByCourseId,updateFsUser} from "@/api/courseManage.js"
+	export default{
+		data() {
+			return {
+				modelShow: false,
+				modelTitle: '',
+				inputVal: '',
+				info: {},
+				list1: [{
+					name: '课程数据',
+				}, {
+					name: '参与记录',
+				}, {
+					name: '活动节目'
+				}],
+				current: 0,
+				list2: [{
+					name: '答题正确',
+					label: '答题正确',
+					value: 0
+				}, {
+					name: '仅播完',
+					label: '仅播完',
+					value: 2
+				}, {
+					name: '未完播',
+					label: '未完播',
+					value: 1
+				}, {
+					name: '未播放',
+					label: '未播放',
+					value: 3
+				}],
+				currentType: 0,
+				activeStyle:{
+					color: '#1677ff',
+					fontSize: '14px',
+					fontWeight: 'bold'
+				},
+				inactiveStyle:{
+					fontSize: '14px'
+				},
+				collapseValue:['live','questions','funnelplot'],
+				keyword: '',
+				disabled: false,
+				searchTypeIndex: 0,
+				typeArray: ['按看课时间从晚到早', '按完播时间从晚到早', '按注册时间从晚到早','按会员姓名0-9-A-Z'],
+				typeoption: ['看课时间', '完播时间', '注册时间','会员姓名'],
+				typeoptionOrder: ['desc', 'watchDesc', 'registerDesc','nameDesc'],
+				mescroll:null,
+				downOption: {
+					auto:false//不要自动加载
+				},
+				upOption: {
+					onScroll:false,
+					use: true, // 是否启用上拉加载; 默认true
+					page: {
+						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						size: 10 // 每页数据的数量,默认10
+					},
+					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
+					textNoMore:"已经到底了",
+					empty: {
+						icon:'/static/images/empty.png',
+						tip: '暂无数据'
+					}
+				},
+				dataList: [],
+				user: {},
+				todayday: uni.$u.timeFormat(new Date(), 'yyyy-mm-dd'),
+				courseCount: {},
+				quesCount: {},
+				redPacketCount: {},
+				courseId: '',
+				chooseUserId: ''
+			}
+		},
+		onLoad(option) {
+			this.info = option.info ? JSON.parse(option.info) : {}
+			uni.setNavigationBarTitle({
+				title: '数据统计'
+			});
+			this.user = uni.getStorageSync("companyUserInfo") ? JSON.parse(uni.getStorageSync("companyUserInfo")) : {}
+			this.courseId = this.info.courseId || ''
+			if(this.courseId)
+				this.getCount()
+		},
+		methods: {
+			copyId() {
+				uni.setClipboardData({
+					data: this.info.fileId,
+					success: ()=> {
+						uni.showToast({
+							title: '复制课程ID成功',
+							icon: 'success'
+						})
+					}
+				});
+			},
+			clickTab(e) {
+				this.current = e.index
+			},
+			clickTypeTab(e) {
+				this.currentType = e.index
+				this.mescroll.resetUpScroll()
+			},
+			bindPickerChange(e) {
+				console.log('picker发送选择改变,携带值为', e.detail.value)
+				this.searchTypeIndex = e.detail.value
+			},
+			changeCollapse(e) {
+				this.collapseValue = e.filter(item=>item.status == 'open').map(it=>it.name)
+			},
+			refresh() {
+				this.mescroll.resetUpScroll()
+			},
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					courseId:this.courseId,
+					order: this.typeoptionOrder[this.searchTypeIndex],
+					tagIds: [],
+					type: this.list2[this.currentType].value, // 0:答题正确 1:未完播 2:已完播 3:未播放
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				this.list2[this.currentType].name = this.list2[this.currentType].label
+				getUserLogListByCourseId(data).then(res => {
+					uni.hideLoading()
+					if(res.code==200){
+						//设置列表数据
+						if (page.num == 1) {
+							that.dataList = res.data.list; 
+							
+						} else {
+							that.dataList = that.dataList.concat(res.data.list);
+							 
+						}
+						this.list2[this.currentType].name = this.list2[this.currentType].label + (res.data.total)
+						that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: "请求失败",
+						});
+						that.dataList = null;
+						that.mescroll.endErr();
+					}
+				});
+			},
+			getCount() {
+				const param = {
+					companyId: this.user.companyId,
+					companyUserId: this.user.userId,
+					courseId: this.courseId,
+					endTime: this.todayday + ' 23:59:59',
+					startTime: this.todayday + ' 00:00:00',
+					type: this.user.userType=='00' ? 0 : 1, // 0:经销商/1:群管
+				}
+				this.getCourseCount(param)
+				this.getQuesCount(param)
+				this.getRecPacketCount(param)
+			},
+			getCourseCount(param){
+				getCourseCount(param).then(res=>{
+					if(res.code==200){
+						this.courseCount = res.data
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			getQuesCount(param){
+				getQuesCount(param).then(res=>{
+					if(res.code==200){
+						this.quesCount = res.data
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			getRecPacketCount(param){
+				getRecPacketCount(param).then(res=>{
+					if(res.code==200){
+						this.redPacketCount = res.data
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
+			},
+			openModel(type,item) {
+				this.chooseUserId = item.userId
				if(type == 'name') {
					this.modelTitle = '修改姓名'
+					this.inputVal = item.nickName || ''
				} else if(type == 'remark') {
					this.modelTitle = '修改备注'
+					this.inputVal = item.remark || ''
				}
				this.modelShow = true
			},
+			closeModel() {
+				this.modelShow = false
+				this.inputVal = ''
+			},
+			confirmModel() {
+				this.modelShow = false
+				if(this.modelTitle == '修改姓名') {
+					const param = {
+						userId: this.chooseUserId,
+						nickName: this.inputVal,
+					}
+					this.updateFsUser(param)
+				} else if(this.modelTitle == '修改备注') {
+					const param = {
+						userId: this.chooseUserId,
+						remark: this.inputVal,
+					}
+					this.updateFsUser(param)
+				}
+			},
+			updateFsUser(param) {
+				console.log(param)
+				updateFsUser(param).then(res=>{
+					if(res.code==200){
+						this.redPacketCount = res.data
+					}else{
+						uni.showToast({
+							icon:'none',
+							title: res.msg,
+						});
+					}
+				})
+			}
+		}
 	}
 </script>
 
-<style lang="scss" scoped>
-.container {
-	position: relative;
-	z-index: 2;
-	font-family: PingFang SC, PingFang SC;
-	font-weight: 400;
-	font-size: 14px;
-	color: #222;
-	.topbg {
-		position: absolute;
-		z-index: -1;
-		top: 0;
-		left: 0;
-		width: 100%;
-		height: 210px;
-	}
-	&-body {
-		
-	}
-	.coursebox {
-		padding: 120px 50rpx 20px 50rpx;
-		font-family: PingFang SC, PingFang SC;
-		font-weight: 400;
-		font-size: 14px;
-		color: #999;
-		.status {
-			position: absolute;
-			top: 0;
-			right: 0;
-			z-index: 2;
-			height: 30px;
-			padding: 0 8px;
-			box-sizing: border-box;
-			line-height: 30px;
-			border-radius: 0 12px 0 12px;
-			text-align: center;
-			color: #fff;
-			background-color: #08ce36;
-		}
-		&-info {
-			padding: 12px;
-			background-color: rgba(255,255,255,0.88);
-			border-radius: 12px;
-			overflow: hidden;
-			position: relative;
-		}
-		&-name {
-			color: #222;
-			font-weight: bold;
-			font-size: 16px;
-			padding-right: 50px;
-			.more-t {
-				flex: 1;
-				font-size: 14px;
-				color: #222;
-				display: inline;
-			}
-		}
-		&-time-item {
-			flex: 1;
-			margin-top: 5px;
-		}
-		&-time-t {
-			color: #222;
-			font-weight: bold;
-			margin-top: 5px;
-			font-size: 12px;
-		}
-		.duration {
-			position: relative;
-			padding-left: 14px;
-			&::after {
-				content: "";
-				height: 30px;
-				width: 1px;
-				background-color: #f5f5f5;
-				position: absolute;
-				left: 0;
-				top: 5px;
-			}
-		}
-	}
-}
-.statistics {
-	background-color: #fff;
-	&-title {
-		font-family: PingFang SC, PingFang SC;
-		font-weight: bold;
-		font-size: 16px;
-		color: #222222;
-	}
-	&-title-tip {
-		font-weight: 400;
-		font-size: 11px;
-		color: #999;
-		padding-left: 10px;
-	}
-	&-slot-title {
-		font-size: 12px;
-		color: #1677ff;
-	}
-	.collapse-content {
-		margin: 0 -8px -8rpx 0;
-		&-item {
-			flex: 1;
-			padding: 12px;
-			box-sizing: border-box;
-			border-radius: 10px;
-			background: #f5f5f5;
-			font-family: PingFang SC, PingFang SC;
-			font-weight: 400;
-			font-size: 10px;
-			color: #222222;
-			margin: 0 8px 8rpx 0;
-		}
-		&-title {
-			font-size: 14px;
-			margin-bottom: 10px;
-		}
-		&-num {
-			color: #1677ff;
-			font-size: 10px;
-			text {
-				font-family: DIN, DIN;
-				font-weight: bold;
-				font-size: 25px;
-			}
-		}
-	}
-	.redenvelope {
-		background-color: #f5f5f5;
-		margin-right: 8px;
-		margin-top: 4px;
-		padding: 15px;
-		box-sizing: border-box;
-		border-radius: 10px;
-		color: #222222;
-		image {
-			width: 50px;
-			height: 50px;
-			margin-right: 10px;
-		}
-	}
-}
-.participate-search {
-	background-color: #fff;
-	padding: 10px 15px;
-	.refresh {
-		height: 26px;
-		min-width: 40px;
-		width: 40px;
-		padding: 0;
-		display: inline-flex;
-		margin-left: 15px;
-		border-radius: 5px;
-		overflow: hidden;
-	}
-}
-.participate-order {
-	font-size: 12px;
-	image {
-		width: 15px;
-		height: 15px;
-	}
-}
-.btn_icon {
-	font-weight: 400;
-	font-size: 14px;
-	color: #1677ff;
-	display: inline-flex;
-	align-items: center;
-	margin-left: 5px;
-	image {
-		width: 20px;
-		height: 20px;
-	}
-}
-.list {
-	padding: 20px 10px;
-}
-.list-item{
-	padding: 10px;
-	margin-bottom: 10px;
-	background-color: #fff;
-	border-radius: 12px;
-	&-head {
-		.phone {
-			flex-shrink: 0;
-			width: 30px;
-			height: 30px;
-		}
-		&-l {
-			flex: 1;
-			overflow: hidden;
-			margin-left: 10px;
-			margin-right: 10px;
-		}
-	}
-	&-copy {
-		width: 20px;
-		height: 20px;
-	}
-	&-re {
-		font-size: 10px;
-		margin-top: 5px;
-	}
-	&-desc {
-		padding: 5px;
-		color: #999;
-		font-size: 12px;
-	}
-	.label {
-		margin-right: 4px;
-	}
-	.value-num {
-		margin-right: 18px;
-		color: #222;
-		font-weight: bold;
-	}
-	.taglist {
-		display: flex;
-		align-items: center;
-		flex-wrap: wrap;
-		color: #555;
-		padding-top: 5px;
-		view {
-			margin: 0 5px 5px 0;
-		}
-	}
-	&-footer {
-		justify-content: flex-end;
-		padding: 6px 0;
-		.footer-tagbtn {
-			color: #1677ff;
-			background-color: #e7f2fe;
-			border: 1px solid #c9e1fb;
-		}
-		&-btn {
-			height: 26px;
-			padding: 0 10px;
-			margin-left: 10px;
-			background-color: #f5f5f5;
-			border-radius: 25px;
-			font-weight: 400;
-			font-size: 10px;
-			line-height: 26px;
-			border: 1px solid #f5f5f5;
-			box-sizing: border-box;
-		}
-	}
+<style lang="scss" scoped>
+.container {
+	position: relative;
+	z-index: 2;
+	font-family: PingFang SC, PingFang SC;
+	font-weight: 400;
+	font-size: 14px;
+	color: #222;
+	.topbg {
+		position: absolute;
+		z-index: -1;
+		top: 0;
+		left: 0;
+		width: 100%;
+		height: 210px;
+	}
+	&-body {
+		
+	}
+	.coursebox {
+		padding: 120px 50rpx 20px 50rpx;
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 400;
+		font-size: 14px;
+		color: #999;
+		.status {
+			position: absolute;
+			top: 0;
+			right: 0;
+			z-index: 2;
+			height: 30px;
+			padding: 0 8px;
+			box-sizing: border-box;
+			line-height: 30px;
+			border-radius: 0 12px 0 12px;
+			text-align: center;
+			color: #fff;
+			background-color: #08ce36;
+		}
+		&-info {
+			padding: 12px;
+			background-color: rgba(255,255,255,0.88);
+			border-radius: 12px;
+			overflow: hidden;
+			position: relative;
+		}
+		&-name {
+			color: #222;
+			font-weight: bold;
+			font-size: 16px;
+			padding-right: 50px;
+			.more-t {
+				flex: 1;
+				font-size: 14px;
+				color: #222;
+				display: inline;
+			}
+		}
+		&-time-item {
+			flex: 1;
+			margin-top: 5px;
+		}
+		&-time-t {
+			color: #222;
+			font-weight: bold;
+			margin-top: 5px;
+			font-size: 12px;
+		}
+		.duration {
+			position: relative;
+			padding-left: 14px;
+			&::after {
+				content: "";
+				height: 30px;
+				width: 1px;
+				background-color: #f5f5f5;
+				position: absolute;
+				left: 0;
+				top: 5px;
+			}
+		}
+	}
+}
+.statistics {
+	background-color: #fff;
+	&-title {
+		font-family: PingFang SC, PingFang SC;
+		font-weight: bold;
+		font-size: 16px;
+		color: #222222;
+	}
+	&-title-tip {
+		font-weight: 400;
+		font-size: 11px;
+		color: #999;
+		padding-left: 10px;
+	}
+	&-slot-title {
+		font-size: 12px;
+		color: #1677ff;
+	}
+	.collapse-content {
+		margin: 0 -8px -8rpx 0;
+		&-item {
+			flex: 1;
+			padding: 12px;
+			box-sizing: border-box;
+			border-radius: 10px;
+			background: #f5f5f5;
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+			font-size: 10px;
+			color: #222222;
+			margin: 0 8px 8rpx 0;
+		}
+		&-title {
+			font-size: 14px;
+			margin-bottom: 10px;
+		}
+		&-num {
+			color: #1677ff;
+			font-size: 10px;
+			text {
+				font-family: DIN, DIN;
+				font-weight: bold;
+				font-size: 25px;
+			}
+		}
+	}
+	.redenvelope {
+		background-color: #f5f5f5;
+		margin-right: 8px;
+		margin-top: 4px;
+		padding: 15px;
+		box-sizing: border-box;
+		border-radius: 10px;
+		color: #222222;
+		image {
+			width: 50px;
+			height: 50px;
+			margin-right: 10px;
+		}
+	}
+}
+.participate-search {
+	background-color: #fff;
+	padding: 10px 15px;
+	.refresh {
+		height: 26px;
+		min-width: 40px;
+		width: 40px;
+		padding: 0;
+		display: inline-flex;
+		margin-left: 15px;
+		border-radius: 5px;
+		overflow: hidden;
+	}
+}
+.participate-order {
+	font-size: 12px;
+	image {
+		width: 15px;
+		height: 15px;
+	}
+}
+.btn_icon {
+	font-weight: 400;
+	font-size: 14px;
+	color: #1677ff;
+	display: inline-flex;
+	align-items: center;
+	margin-left: 5px;
+	image {
+		width: 20px;
+		height: 20px;
+	}
+}
+.list {
+	padding: 20px 10px;
+}
+.list-item{
+	padding: 10px;
+	margin-bottom: 10px;
+	background-color: #fff;
+	border-radius: 12px;
+	&-head {
+		.phone {
+			flex-shrink: 0;
+			width: 30px;
+			height: 30px;
+		}
+		&-l {
+			flex: 1;
+			overflow: hidden;
+			margin-left: 10px;
+			margin-right: 10px;
+		}
+	}
+	&-copy {
+		width: 20px;
+		height: 20px;
+	}
+	&-re {
+		font-size: 10px;
+		margin-top: 5px;
+	}
+	&-desc {
+		padding: 5px;
+		color: #999;
+		font-size: 12px;
+	}
+	.label {
+		margin-right: 4px;
+	}
+	.value-num {
+		margin-right: 18px;
+		color: #222;
+		font-weight: bold;
+	}
+	.taglist {
+		display: flex;
+		align-items: center;
+		flex-wrap: wrap;
+		color: #555;
+		padding-top: 5px;
+		view {
+			margin: 0 5px 5px 0;
+		}
+	}
+	&-footer {
+		justify-content: flex-end;
+		padding: 6px 0;
+		.footer-tagbtn {
+			color: #1677ff;
+			background-color: #e7f2fe;
+			border: 1px solid #c9e1fb;
+		}
+		&-btn {
+			height: 26px;
+			padding: 0 10px;
+			margin-left: 10px;
+			background-color: #f5f5f5;
+			border-radius: 25px;
+			font-weight: 400;
+			font-size: 10px;
+			line-height: 26px;
+			border: 1px solid #f5f5f5;
+			box-sizing: border-box;
+		}
+	}
 }
 </style>

+ 217 - 4
pages/courseManage/vip/index.vue

@@ -1,6 +1,7 @@
 <template>
 	<view class="container">
 		<view class="headbox">
+			<view style="padding: 10px 10px 0 10px;">
 			<u-subsection class="subsection" :list="list" :current="current" bgColor="#e7f1fe" activeColor="#1677ff" :fontSize="15" :bold="false" @change="sectionChange"></u-subsection>
 			<view style="margin-top: 10px;">
 				<u-search placeholder="会员ID、昵称、姓名、手机" v-model="keyword" :showAction="false" height="30px"></u-search>
@@ -25,7 +26,40 @@
 				</view>
 				<view class="tagbox-right x-f" @click="openPop('tag')">筛选<u-icon name="arrow-down" color="#1677ff" size="12"></u-icon></view>
 			</view>
+			</view>
+			<view>
+				<dropdownPanel :filterData='filterData' @onChange="onChange" @confirm="confirm" @reset="reset">
+					<view v-if="filterData[searchbarNav].type == 'registerTime'">
+						<view class="boxnav x-bc">
+							<view class="boxnav-item" style="width: 25%;" v-for="(item,index) in filterData[searchbarNav].option" :key="index">
+								<view class="boxnav-item-info one-t" :class="registerTimeIndex == index ? 'boxnav-active':''" @click="handleParamItem('registerTime',index)">{{item.label}}</view>
+							</view>
+							<view class="boxnav-item x-ac" style="width: 100%;" v-show="registerTimeIndex == 4">
+								<view class="calendar-day x-c" @click="showCalendar = true">
+									<u-icon name="calendar" color="#999" size="20"></u-icon>{{queryParam.startTime || '选择日期'}}</view>
+								<view class="calendar-day x-c" @click="showCalendar = true">
+									<u-icon name="calendar" color="#999" size="20"></u-icon>{{queryParam.endTime || '选择日期'}}</view>
+							</view>
+						</view>
+					</view>
+					<view v-if="filterData[searchbarNav].type == 'watchStatus'">
+						<view class="boxnav x-bc">
+							<view class="boxnav-item" v-for="(item,index) in filterData[searchbarNav].option" :key="index">
+								<view class="boxnav-item-info one-t" :class="registerTimeIndex == index ? 'boxnav-active':''" @click="handleParamItem('registerTime',index)">{{item.label}}</view>
+							</view>
+						</view>
+					</view>
+					<view v-if="filterData[searchbarNav].type == 'nowatchStatus'">
+						<view class="boxnav x-bc">
+							<view class="boxnav-item" v-for="(item,index) in filterData[searchbarNav].option" :key="index">
+								<view class="boxnav-item-info one-t" :class="registerTimeIndex == index ? 'boxnav-active':''" @click="handleParamItem('registerTime',index)">{{item.label}}</view>
+							</view>
+						</view>
+					</view>
+				</dropdownPanel>
+			</view>
 		</view>
+		<u-calendar :show="showCalendar" :mode="mode" @confirm="confirmCalendar" @close="closeCalendar"></u-calendar>
 		<!-- 搜索选择弹窗/标签筛选 -->
 		<u-popup :show="showPop" :round="12" @close="closePop" class="model" :zIndex="10076">
 			<view class="popbox">
@@ -52,6 +86,13 @@
 				</view>
 			</view>
 		</u-popup>
+		<view class="userbox">
+			<!-- <mescroll-body  bottom="0" ref="mescrollRef" @init="mescrollInit" :down="downOption" :up="upOption" @down="downCallback" @up="upCallback"> -->
+				<view class="userlist">
+					<vipUserItem></vipUserItem>
+				</view>
+			<!-- </mescroll-body> -->
+		</view>
 		<image class="invite-member" src="@/static/images/invite-member-icon.png" mode="aspectFill" @click="vipInvite"></image>
 		<!-- 邀请弹窗 -->
 		<u-popup :show="invitePop" :round="12" @close="invitePop = false" :zIndex="10074">
@@ -106,7 +147,15 @@
 </template>
 
 <script>
+	import MescrollMixin from "@/uni_modules/mescroll-uni/components/mescroll-uni/mescroll-mixins.js";
+	import vipUserItem from "../components/vipUserItem.vue"
+	import dropdownPanel  from "../components/dropdownPanel.vue"
 	export default {
+		mixins: [MescrollMixin], 
+		components: {
+			vipUserItem,
+			dropdownPanel
+		},
 		data() {
 			return {
 				list: [{name: '会员(0)'}, {name: '小黑屋(0)'}],
@@ -143,24 +192,79 @@
 				typeIndex: 0,
 				popTitle: '搜索选择',
 				showPop: false,
+				filterData:[{
+					name: '注册时间',
+					value: 0,
+					type: 'registerTime',
+					option: [{
+						label: '全部',
+					},{
+						label: '今天',
+					},{
+						label: '昨天',
+					},{
+						label: '近7天',
+					},{
+						label: '自定义',
+					}]
+				},{
+					name: '看课状态',
+					value: 1,
+					type: 'watchStatus',
+					option: [{
+						label: '全部',
+					},{
+						label: '未看过课',
+					},{
+						label: '正常看课',
+					},{
+						label: '停止看课',
+					}]
+				},
+				// {
+				// 	name: '缺课状态',
+				// 	value: 2,
+				// 	type: 'nowatchStatus',
+				// 	option: [{
+				// 		label: '全部',
+				// 	},{
+				// 		label: '已缺课',
+				// 	},{
+				// 		label: '未缺课',
+				// 	}]
+				// },
+				{
+					name: '批量',
+					value: 3,
+					type: '',
+					special: true
+				}],
+				searchbarNav: 0,
+				registerTimeIndex: 0,
+				showCalendar: false,
+				mode: 'range',
 				queryParam: {
 					typeIndex: 0,
+					startTime: '',
+					endTime: ''
 				},
 				mescroll:null,
 				downOption: {
-					auto:false//不要自动加载
+					use:true,
+					auto: false
 				},
 				upOption: {
 					onScroll:false,
 					use: true, // 是否启用上拉加载; 默认true
+					auto: true,
 					page: {
-						num: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
+						pae: 0, // 当前页码,默认0,回调之前会加1,即callback(page)会从1开始
 						size: 10 // 每页数据的数量,默认10
 					},
 					noMoreSize: 10, // 配置列表的总数量要大于等于5条才显示'-- END --'的提示
 					textNoMore:"已经到底了",
 					empty: {
-						icon:'/static/images/empty.png',
+						icon:'https://cos.his.cdwjyyh.com/fs/20240423/cf4a86b913a04341bb44e34bb4d37aa2.png',
 						tip: '暂无数据'
 					}
 				},
@@ -176,6 +280,29 @@
 			});
 		},
 		methods: {
+			onChange(index) {
+				this.searchbarNav = index
+			},
+			confirm() {
+				const type = this.filterData[this.searchbarNav].type
+				if(type=='registerTime') {
+					
+				}
+				// this.mescroll.resetUpScroll()
+			},
+			reset() {
+				const type = this.filterData[this.searchbarNav].type
+				if(type=='registerTime') {
+					this.registerTimeIndex = ''
+					this.queryParam.endTime = ''
+					this.queryParam.startTime = ''
+				}
+			},
+			handleParamItem(type,index) {
+				if(type=='registerTime') {
+					this.registerTimeIndex = index
+				}
+			},
 			sectionChange(index) {
 				this.current = index;
 			},
@@ -196,6 +323,15 @@
 			},
 			resetPop() {
 				
+			},
+			closeCalendar() {
+				this.showCalendar = false
+			},
+			confirmCalendar(e) {
+				// this.startTime = e[0]
+				// this.endTime =  e[e.length-1]
+				this.showCalendar = false
+				// this.getCount()
 			},
 			bindTypeChange(i) {
 				this.typeIndex = i
@@ -217,6 +353,48 @@
 				this.setTimeShow = false
 				this.showShare = false
 			},
+			mescrollInit(mescroll) {
+				this.mescroll = mescroll;
+			},
+			/*下拉刷新的回调 */
+			downCallback(mescroll) {
+				mescroll.resetUpScroll()
+			},
+			upCallback(page) {
+				//联网加载数据
+				var that = this;
+				var data={
+					courseId:this.courseId,
+					status: '',
+					pageNum: page.num,
+					pageSize: page.size
+				};
+				uni.showLoading({
+					title:"加载中..."
+				})
+				// getCourseVdieoList(data).then(res => {
+				// 	uni.hideLoading()
+				// 	if(res.code==200){
+				// 		//设置列表数据
+				// 		if (page.num == 1) {
+				// 			that.dataList = res.data.list; 
+							
+				// 		} else {
+				// 			that.dataList = that.dataList.concat(res.data.list);
+							 
+				// 		}
+				// 		that.mescroll.endBySize(res.data.list.length, res.data.total);
+						
+				// 	}else{
+				// 		uni.showToast({
+				// 			icon:'none',
+				// 			title: "请求失败",
+				// 		});
+				// 		that.dataList = null;
+				// 		that.mescroll.endErr();
+				// 	}
+				// });
+			},
 		}
 	}
 </script>
@@ -233,6 +411,39 @@
 		font-size: 14px;
 		color: #222222;
 	}
+	.boxnav {
+		flex-wrap: wrap;
+		padding: 0 0 0 10px;
+		&-item {
+			width: 50%;
+			overflow: hidden;
+		}
+		&-item-info {
+			border: 1px solid #f5f5f5;
+			text-align: center;
+			color: #222;
+			background-color: #f5f5f5;
+			border-radius: 3px;
+			padding: 5px;
+			margin: 0 10px 10px 0;
+		}
+		&-active {
+			border: 1px solid #1677ff !important;
+			color: #1677ff !important;
+			background-color: #e7f1fe !important;
+		}
+	}
+	.calendar-day {
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 400;
+		font-size: 12px;
+		color: #999;
+		flex: 1;
+		min-height: 30px;
+		background-color: #f5f5f5;
+		border-radius: 4px;
+		margin: 0 10px 10px 0;
+	}
 	.setTimebox {
 		font-family: PingFang SC, PingFang SC;
 		font-weight: 400;
@@ -328,7 +539,6 @@
 		}
 	}
 	.headbox {
-		padding: 10px;
 		background-color: #fff;
 		.subsection {
 			height: 45px;
@@ -375,4 +585,7 @@
 		right: 10px;
 		cursor: pointer;
 	}
+	.userlist {
+		padding: 24rpx;
+	}
 </style>

+ 2 - 2
uni_modules/uview-ui/LICENSE

@@ -1,6 +1,6 @@
 MIT License
 
-Copyright (c) 2020 www.uviewui.com
+Copyright (c) 2023 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
@@ -18,4 +18,4 @@ 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.
+SOFTWARE.

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

@@ -1,3 +1,35 @@
+## 2.0.38(2024-06-12)
+插件市场处理
+## 2.0.37(2024-03-17)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 修复表单校验`trigger`触发器参数无效问题
+2. 修复`u-input`组件的`password`属性在动态切换为`false`时失效的问题
+3. 添加微信小程序用户同意隐私协议事件回调
+4. 修复支付宝小程序picker样式问题
+5. `u-modal`添加`duration`字段控制动画过度时间
+6. 修复`picker` `lastIndex`异常导致的`column`异常问题
+7. `tabs`增加长按事件支持
+8. 修复`u-avatar` `square`属性在小程序`open-data`下无效问题
+9. 其他一些修复
+## 2.0.36(2023-03-27)
+# uView2.0重磅发布,利剑出鞘,一统江湖
+
+1. 重构`deepClone` & `deepMerge`方法
+2. 其他优化
+## 2.0.34(2022-09-24)
+# 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重磅发布,利剑出鞘,一统江湖
 

+ 2 - 2
uni_modules/uview-ui/components/u--form/u--form.vue

@@ -52,11 +52,11 @@
 				// #endif
 				return this.$refs.uForm.validate()
 			},
-			validateField(value, callback) {
+			validateField(value, callback, event) {
 				// #ifdef MP-WEIXIN
 				this.setMpData()
 				// #endif
-				return this.$refs.uForm.validateField(value, callback)
+				return this.$refs.uForm.validateField(value, callback, event)
 			},
 			resetFields() {
 				// #ifdef MP-WEIXIN

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

@@ -35,6 +35,7 @@
 		:shape="shape"
 		:customStyle="customStyle"
 		:formatter="formatter"
+		:ignoreCompositionEvent="ignoreCompositionEvent"
 		@focus="$emit('focus')"
 		@blur="e => $emit('blur', e)"
 		@keyboardheightchange="$emit('keyboardheightchange')"

+ 3 - 2
uni_modules/uview-ui/components/u--textarea/u--textarea.vue

@@ -19,8 +19,9 @@
 		:holdKeyboard="holdKeyboard"
 		:maxlength="maxlength"
 		:border="border"
-		:customStyle="customStyle"
-		:formatter="formatter"
+		:customStyle="customStyle"
+		:formatter="formatter"
+		:ignoreCompositionEvent="ignoreCompositionEvent"
 		@focus="e => $emit('focus')"
 		@blur="e => $emit('blur')"
 		@linechange="e => $emit('linechange', e)"

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

@@ -162,6 +162,7 @@
 		&__image {
 			&--circle {
 				border-radius: 100px;
+				overflow: hidden;
 			}
 
 			&--square {

+ 5 - 0
uni_modules/uview-ui/components/u-button/u-button.vue

@@ -19,6 +19,7 @@
         @error="error"
         @opensetting="opensetting"
         @launchapp="launchapp"
+        @agreeprivacyauthorization="agreeprivacyauthorization"
         :hover-class="!disabled && !loading ? 'u-button--active' : ''"
         class="u-button u-reset-button"
         :style="[baseColor, $u.addStyle(customStyle)]"
@@ -153,6 +154,7 @@ import props from "./props.js";
  * @event {Function}	error			当使用开放能力时,发生错误的回调
  * @event {Function}	opensetting		在打开授权设置页并关闭后回调
  * @event {Function}	launchapp		打开 APP 成功的回调
+ * @event {Function}	agreeprivacyauthorization	用户同意隐私协议事件回调
  * @example <u-button>月落</u-button>
  */
 export default {
@@ -286,6 +288,9 @@ export default {
         launchapp(res) {
             this.$emit("launchapp", res);
         },
+        agreeprivacyauthorization(res) {
+            this.$emit("agreeprivacyauthorization", res);
+        },
     },
 };
 </script>

+ 4 - 3
uni_modules/uview-ui/components/u-calendar/u-calendar.vue

@@ -209,12 +209,13 @@ export default {
 			}
 		},
 		init() {
-			// 校验maxDate,不能小于当前时间
+			// 校验maxDate,不能小于minDate
 			if (
 				this.innerMaxDate &&
-				new Date(this.innerMaxDate).getTime() <= Date.now()
+				this.innerMinDate &&
+				new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
 			) {
-				return uni.$u.error('maxDate不能小于当前时间')
+				return uni.$u.error('maxDate不能小于minDate')
 			}
 			// 滚动区域的高度
 			this.listHeight = this.rowHeight * 5 + 30

+ 4 - 4
uni_modules/uview-ui/components/u-code/u-code.vue

@@ -16,11 +16,11 @@
 	 * @property {String}			endText			倒计结束的提示语,见官网说明(默认 '重新获取' )
 	 * @property {Boolean}			keepRunning		是否在H5刷新或各端返回再进入时继续倒计时( 默认false )
 	 * @property {String}			uniqueKey		为了区分多个页面,或者一个页面多个倒计时组件本地存储的继续倒计时变了
-	 * 
+	 *
 	 * @event {Function}	change	倒计时期间,每秒触发一次
 	 * @event {Function}	start	开始倒计时触发
 	 * @event {Function}	end		结束倒计时触发
-	 * @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code> 
+	 * @example <u-code ref="uCode" @change="codeChange" seconds="20"></u-code>
 	 */
 	export default {
 		name: "u-code",
@@ -74,7 +74,6 @@
 				this.canGetCode = false
 				// 这里放这句,是为了一开始时就提示,否则要等setInterval的1秒后才会有提示
 				this.changeEvent(this.changeText.replace(/x|X/, this.secNum))
-				this.setTimeToStorage()
 				this.timer = setInterval(() => {
 					if (--this.secNum) {
 						// 用当前倒计时的秒数替换提示字符串中的"x"字母
@@ -88,7 +87,8 @@
 						this.canGetCode = true
 					}
 				}, 1000)
-			},
+        this.setTimeToStorage()
+      },
 			// 重置,可以让用户再次获取验证码
 			reset() {
 				this.canGetCode = true

+ 1 - 1
uni_modules/uview-ui/components/u-datetime-picker/u-datetime-picker.vue

@@ -171,7 +171,7 @@
 				// 发出change时间,value为当前选中的时间戳
 				this.$emit('change', {
 					value: selectValue,
-					// #ifndef MP-WEIXIN
+					// #ifndef MP-WEIXIN || MP-TOUTIAO
 					// 微信小程序不能传递this实例,会因为循环引用而报错
 					picker: this.$refs.picker,
 					// #endif

+ 120 - 139
uni_modules/uview-ui/components/u-dropdown-item/u-dropdown-item.vue

@@ -1,146 +1,127 @@
 <template>
-	<view class="u-drawdown-item">
-		<u-overlay
-			customStyle="top: 126px"
-			:show="show"
-			:closeOnClickOverlay="closeOnClickOverlay"
-			@click="overlayClick"
-		></u-overlay>
-		<view
-			class="u-drawdown-item__content"
-			:style="[style]"
-			:animation="animationData"
-			ref="animation"
-		>
-			<slot />
-		</view>
-	</view>
+  <view class="u-drawdown">
+    <view
+      class="u-dropdown__menu"
+      :style="{
+				height: $u.addUnit(height)
+			}"
+      ref="u-dropdown__menu"
+    >
+      <view
+        class="u-dropdown__menu__item"
+        v-for="(item, index) in menuList"
+        :key="index"
+        @tap.stop="clickHandler(item, index)"
+      >
+        <view class="u-dropdown__menu__item__content">
+          <text
+            class="u-dropdown__menu__item__content__text"
+            :style="[index === current ? activeStyle : inactiveStyle]"
+          >{{item.title}}</text>
+          <view
+            class="u-dropdown__menu__item__content__arrow"
+            :class="[index === current && 'u-dropdown__menu__item__content__arrow--rotate']"
+          >
+            <u-icon
+              :name="menuIcon"
+              :size="$u.addUnit(menuIconSize)"
+            ></u-icon>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="u-dropdown__content">
+      <slot />
+    </view>
+  </view>
 </template>
 
 <script>
-	// #ifdef APP-NVUE
-	const animation = uni.requireNativePlugin('animation')
-	const dom = uni.requireNativePlugin('dom')
-	// #endif
-	import props from './props.js';
-	/**
-	 * Drawdownitem
-	 * @description 
-	 * @tutorial url
-	 * @property {String}
-	 * @event {Function}
-	 * @example
-	 */
-	export default {
-		name: 'u-drawdown-item',
-		mixins: [uni.$u.mpMixin, uni.$u.mixin, props],
-		data() {
-			return {
-				show: false,
-				top: '126px',
-				// uni.createAnimation的导出数据
-				animationData: {},
-			}
-		},
-		mounted() {
-			this.init()
-		},
-		watch: {
-			// 发生变化时,需要去更新父组件对应的值
-			dataChange(newValue, oldValue) {
-				this.updateParentData()
-			}
-		},
-		computed: {
-			// 监听对应变量的变化
-			dataChange() {
-				return [this.title, this.disabled]
-			},
-			style() {
-				const style = {
-					zIndex: 10071,
-					position: 'fixed',
-					display: 'flex',
-					left: 0,
-					right: 0
-				}
-				style.top = uni.$u.addUnit(this.top)
-				return style
-			}
-		},
-		methods: {
-			init() {
-				this.updateParentData()
-			},
-			// 更新父组件所需的数据
-			updateParentData() {
-				// 获取父组件u-dropdown
-				this.getParentData('u-dropdown')
-				if (!this.parent) uni.$u.error('u-dropdown-item必须配合u-dropdown使用')
-				// 查找父组件menuList数组中对应的标题数据
-				const menuIndex = this.parent.menuList.findIndex(item => item.title === this.title)
-				const menuContent = {
-					title: this.title,
-					disabled: this.disabled
-				}
-				if (menuIndex >= 0) {
-					// 如果能找到,则直接修改
-					this.parent.menuList[menuIndex] = menuContent;
-				} else {
-					// 如果无法找到,则为第一次添加,直接push即可
-					this.parent.menuList.push(menuContent);
-				}
-			},
-			async setContentAnimate(height) {
-				this.animating = true
-				// #ifdef APP-NVUE
-				const ref = this.$refs['animation'].ref
-				animation.transition(ref, {
-					styles: {
-						height: uni.$u.addUnit(height)
-					},
-					duration: this.duration,
-					timingFunction: 'ease-in-out',
-				}, () => {
-					this.animating = false
-				})
-				// #endif
-			
-				// #ifndef APP-NVUE
-				const animation = uni.createAnimation({
-					timingFunction: 'ease-in-out',
-				});
-				animation
-					.height(height)
-					.step({
-						duration: this.duration,
-					})
-					.step()
-				// 导出动画数据给面板的animationData值
-				this.animationData = animation.export()
-				// 标识动画结束
-				uni.$u.sleep(this.duration).then(() => {
-					this.animating = false
-				})
-				// #endif
-			},
-			overlayClick() {
-				this.show = false
-				this.setContentAnimate(0)
-			}
-		},
-	}
+import props from './props.js';
+/**
+ * Dropdown
+ * @description
+ * @tutorial url
+ * @property {String}
+ * @event {Function}
+ * @example
+ */
+export default {
+  name: 'u-dropdown',
+  mixins: [uni.$u.mixin, props],
+  data() {
+    return {
+      // �˵�����
+      menuList: [],
+      current: 0
+    }
+  },
+  computed: {
+  
+  },
+  created() {
+    // �������������(u-dropdown-item)��this��������data������������������΢��С��������ѭ�����ö�����
+    this.children = [];
+  },
+  methods: {
+    clickHandler(item, index) {
+      this.children.map(child => {
+        if(child.title === item.title) {
+          // this.queryRect('u-dropdown__menu').then(size => {
+          child.$emit('click')
+          child.setContentAnimate(child.show ? 0 : 300)
+          child.show = !child.show
+          // })
+        } else {
+          child.show = false
+          child.setContentAnimate(0)
+        }
+      })
+    },
+    // ��ȡ��ǩ�ijߴ�λ��
+    queryRect(el) {
+      // #ifndef APP-NVUE
+      // $uGetRectΪuView�Դ��Ľڵ��ѯ�򻯷���������ĵ����ܣ�https://www.uviewui.com/js/getRect.html
+      // ����ڲ�һ����this.$uGetRect�������Ϊthis.$u.getRect�����߹���һ�£����Ʋ�ͬ
+      return new Promise(resolve => {
+        this.$uGetRect(`.${el}`).then(size => {
+          resolve(size)
+        })
+      })
+      // #endif
+      
+      // #ifdef APP-NVUE
+      // nvue�£�ʹ��domģ���ѯԪ�ظ߶�
+      // ����һ��promise���õ��ô˷�����������ʹ��then�ص�
+      return new Promise(resolve => {
+        dom.getComponentRect(this.$refs[el], res => {
+          resolve(res.size)
+        })
+      })
+      // #endif
+    },
+  },
+}
 </script>
 
-<style lang="scss" scoped>
-	@import '../../libs/css/components.scss';
-	
-	.u-drawdown-item {
-		
-		&__content {
-			background-color: #FFFFFF;
-			overflow: hidden;
-			height: 0;
-		}
-	}
+<style lang="scss">
+@import '../../libs/css/components.scss';
+
+.u-dropdown {
+  
+  &__menu {
+    @include flex;
+    
+    &__item {
+      flex: 1;
+      @include flex;
+      justify-content: center;
+      
+      &__content {
+        @include flex;
+        align-items: center;
+      }
+    }
+  }
+}
 </style>

+ 118 - 118
uni_modules/uview-ui/components/u-dropdown/u-dropdown.vue

@@ -1,127 +1,127 @@
 <template>
-	<view class="u-drawdown">
-		<view
-			class="u-dropdown__menu"
-			:style="{
+  <view class="u-drawdown">
+    <view
+      class="u-dropdown__menu"
+      :style="{
 				height: $u.addUnit(height)
-			}"
-			ref="u-dropdown__menu"
-		>
-			<view
-				class="u-dropdown__menu__item"
-				v-for="(item, index) in menuList"
-				:key="index"
-				@tap.stop="clickHandler(item, index)"
-			>
-				<view class="u-dropdown__menu__item__content">
-					<text
-						class="u-dropdown__menu__item__content__text"
-						:style="[index === current ? activeStyle : inactiveStyle]"
-					>{{item.title}}</text>
-					<view
-						class="u-dropdown__menu__item__content__arrow"
-						:class="[index === current && 'u-dropdown__menu__item__content__arrow--rotate']"
-					>
-						<u-icon
-							:name="menuIcon"
-							:size="$u.addUnit(menuIconSize)"
-						></u-icon>
-					</view>
-				</view>
-			</view>
-		</view>
-		<view class="u-dropdown__content">
-			<slot />
-		</view>
-	</view>
+			}"
+      ref="u-dropdown__menu"
+    >
+      <view
+        class="u-dropdown__menu__item"
+        v-for="(item, index) in menuList"
+        :key="index"
+        @tap.stop="clickHandler(item, index)"
+      >
+        <view class="u-dropdown__menu__item__content">
+          <text
+            class="u-dropdown__menu__item__content__text"
+            :style="[index === current ? activeStyle : inactiveStyle]"
+          >{{item.title}}</text>
+          <view
+            class="u-dropdown__menu__item__content__arrow"
+            :class="[index === current && 'u-dropdown__menu__item__content__arrow--rotate']"
+          >
+            <u-icon
+              :name="menuIcon"
+              :size="$u.addUnit(menuIconSize)"
+            ></u-icon>
+          </view>
+        </view>
+      </view>
+    </view>
+    <view class="u-dropdown__content">
+      <slot />
+    </view>
+  </view>
 </template>
 
 <script>
-	import props from './props.js';
-	/**
-	 * Dropdown  
-	 * @description 
-	 * @tutorial url
-	 * @property {String}
-	 * @event {Function}
-	 * @example
-	 */
-	export default {
-		name: 'u-dropdown',
-		mixins: [uni.$u.mixin, props],
-		data() {
-			return {
-				// 菜单数组
-				menuList: [],
-				current: 0
-			}
-		},
-		computed: {
-		
-		},
-		created() {
-			// 引用所有子组件(u-dropdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错
-			this.children = [];
-		},
-		methods: {
-			clickHandler(item, index) {
-				this.children.map(child => {
-					if(child.title === item.title) {
-						// this.queryRect('u-dropdown__menu').then(size => {
-							child.$emit('click')
-							child.setContentAnimate(child.show ? 0 : 300)
-							child.show = !child.show
-						// })
-					} else {
-						child.show = false
-						child.setContentAnimate(0)
-					}
-				})
-			},
-			// 获取标签的尺寸位置
-			queryRect(el) {
-				// #ifndef APP-NVUE
-				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
-				// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不同
-				return new Promise(resolve => {
-					this.$uGetRect(`.${el}`).then(size => {
-						resolve(size)
-					})
-				})
-				// #endif
-			
-				// #ifdef APP-NVUE 
-				// nvue下,使用dom模块查询元素高度
-				// 返回一个promise,让调用此方法的主体能使用then回调
-				return new Promise(resolve => {
-					dom.getComponentRect(this.$refs[el], res => {
-						resolve(res.size)
-					})
-				})
-				// #endif
-			},
-		},
-	}
+import props from './props.js';
+/**
+ * Dropdown
+ * @description
+ * @tutorial url
+ * @property {String}
+ * @event {Function}
+ * @example
+ */
+export default {
+  name: 'u-dropdown',
+  mixins: [uni.$u.mixin, props],
+  data() {
+    return {
+      // 锟剿碉拷锟斤拷锟斤拷
+      menuList: [],
+      current: 0
+    }
+  },
+  computed: {
+  
+  },
+  created() {
+    // 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟�(u-dropdown-item)锟斤拷this锟斤拷锟斤拷锟斤拷锟斤拷data锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷小锟斤拷锟斤拷锟斤拷锟斤拷循锟斤拷锟斤拷锟矫讹拷锟斤拷锟斤拷
+    this.children = [];
+  },
+  methods: {
+    clickHandler(item, index) {
+      this.children.map(child => {
+        if(child.title === item.title) {
+          // this.queryRect('u-dropdown__menu').then(size => {
+          child.$emit('click')
+          child.setContentAnimate(child.show ? 0 : 300)
+          child.show = !child.show
+          // })
+        } else {
+          child.show = false
+          child.setContentAnimate(0)
+        }
+      })
+    },
+    // 锟斤拷取锟斤拷签锟侥尺达拷位锟斤拷
+    queryRect(el) {
+      // #ifndef APP-NVUE
+      // $uGetRect为uView锟皆达拷锟侥节碉拷锟窖�拷蚧�锟斤拷锟斤拷锟斤拷锟斤拷锟侥碉拷锟斤拷锟杰o拷https://www.uviewui.com/js/getRect.html
+      // 锟斤拷锟斤拷诓锟揭伙拷锟斤拷锟絫his.$uGetRect锟斤拷锟斤拷锟斤拷锟轿猼his.$u.getRect锟斤拷锟斤拷锟竭癸拷锟斤拷一锟铰o拷锟斤拷锟狡诧拷同
+      return new Promise(resolve => {
+        this.$uGetRect(`.${el}`).then(size => {
+          resolve(size)
+        })
+      })
+      // #endif
+      
+      // #ifdef APP-NVUE
+      // nvue锟铰o拷使锟斤拷dom模锟斤拷锟窖��拷馗叨锟�
+      // 锟斤拷锟斤拷一锟斤拷promise锟斤拷锟矫碉拷锟矫此凤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷使锟斤拷then锟截碉拷
+      return new Promise(resolve => {
+        dom.getComponentRect(this.$refs[el], res => {
+          resolve(res.size)
+        })
+      })
+      // #endif
+    },
+  },
+}
 </script>
 
 <style lang="scss">
-	@import '../../libs/css/components.scss';
+@import '../../libs/css/components.scss';
 
-	.u-dropdown {
-
-		&__menu {
-			@include flex;
-
-			&__item {
-				flex: 1;
-				@include flex;
-				justify-content: center;
-
-				&__content {
-					@include flex;
-					align-items: center;
-				}
-			}
-		}
-	}
-</style>
+.u-dropdown {
+  
+  &__menu {
+    @include flex;
+    
+    &__item {
+      flex: 1;
+      @include flex;
+      justify-content: center;
+      
+      &__content {
+        @include flex;
+        align-items: center;
+      }
+    }
+  }
+}
+</style>

+ 5 - 0
uni_modules/uview-ui/components/u-form-item/props.js

@@ -15,6 +15,11 @@ export default {
             type: [String, Boolean],
             default: uni.$u.props.formItem.borderBottom
         },
+        // label的位置,left-左边,top-上边
+        labelPosition: {
+            type: String,
+            default: uni.$u.props.formItem.labelPosition
+        },
         // label的宽度,单位px
         labelWidth: {
             type: [String, Number],

+ 1 - 1
uni_modules/uview-ui/components/u-form-item/u-form-item.vue

@@ -4,7 +4,7 @@
 			class="u-form-item__body"
 			@tap="clickHandler"
 			:style="[$u.addStyle(customStyle), {
-				flexDirection: parentData.labelPosition === 'left' ? 'row' : 'column'
+				flexDirection: (labelPosition || parentData.labelPosition) === 'left' ? 'row' : 'column'
 			}]"
 		>
 			<!-- 微信小程序中,将一个参数设置空字符串,结果会变成字符串"true" -->

+ 5 - 0
uni_modules/uview-ui/components/u-input/props.js

@@ -177,6 +177,11 @@ export default {
 		formatter: {
 			type: [Function, null],
 			default: uni.$u.props.input.formatter
+		},
+		// 是否忽略组件内对文本合成系统事件的处理
+		ignoreCompositionEvent: {
+			type: Boolean,
+			default: true
 		}
 	}
 }

+ 3 - 2
uni_modules/uview-ui/components/u-input/u-input.vue

@@ -37,7 +37,8 @@
             	    :adjust-position="adjustPosition"
             	    :selection-end="selectionEnd"
             	    :selection-start="selectionStart"
-            	    :password="password || type === 'password' || undefined"
+            	    :password="password || type === 'password' || false"
+                    :ignoreCompositionEvent="ignoreCompositionEvent"
             	    @input="onInput"
             	    @blur="onBlur"
             	    @focus="onFocus"
@@ -114,7 +115,7 @@ import props from "./props.js";
  * @property {Boolean}			readonly				是否只读,与disabled不同之处在于disabled会置灰组件,而readonly则不会 ( 默认 false )
  * @property {String}			shape					输入框形状,circle-圆形,square-方形 ( 默认 'square' )
  * @property {Object}			customStyle				定义需要用到的外部样式
- *
+ * @property {Boolean}			ignoreCompositionEvent	是否忽略组件内对文本合成系统事件的处理。
  * @example <u-input v-model="value" :password="true" suffix-icon="lock-fill" />
  */
 export default {

+ 6 - 1
uni_modules/uview-ui/components/u-modal/props.js

@@ -79,6 +79,11 @@ export default {
         confirmButtonShape: {
             type: String,
             default: uni.$u.props.modal.confirmButtonShape
-        }
+        },
+		// 弹窗动画过度时间
+		duration:{
+			type:String | Number,
+			default: uni.$u.props.modal.duration
+		}
     }
 }

+ 7 - 6
uni_modules/uview-ui/components/u-modal/u-modal.vue

@@ -4,13 +4,13 @@
 		:zoom="zoom"
 		:show="show"
 		:customStyle="{
-			borderRadius: '6px', 
+			borderRadius: '6px',
 			overflow: 'hidden',
 			marginTop: `-${$u.addUnit(negativeTop)}`
 		}"
 		:closeOnClickOverlay="closeOnClickOverlay"
 		:safeAreaInsetBottom="false"
-		:duration="400"
+		:duration="duration"
 		@click="clickHandler"
 	>
 		<view
@@ -104,6 +104,7 @@
 	 * @property {Boolean}			showCancelButton	是否显示取消按钮 (默认 false )
 	 * @property {String}			confirmColor		确认按钮的颜色 (默认 '#2979ff' )
 	 * @property {String}			cancelColor			取消按钮的颜色 (默认 '#606266' )
+	 * @property {Number}			duration			弹窗动画过度时间 (默认 400 )
 	 * @property {Boolean}			buttonReverse		对调确认和取消的位置 (默认 false )
 	 * @property {Boolean}			zoom				是否开启缩放模式 (默认 true )
 	 * @property {Boolean}			asyncClose			是否异步关闭,只对确定按钮有效,见上方说明 (默认 false )
@@ -114,7 +115,7 @@
 	 * @event {Function} confirm	点击确认按钮时触发
 	 * @event {Function} cancel		点击取消按钮时触发
 	 * @event {Function} close		点击遮罩关闭出发,closeOnClickOverlay为true有效
-	 * @example <u-loadmore :status="status" icon-type="iconType" load-text="loadText" />
+	 * @example <u-modal :show="true" title="title" content="content"></u-modal>
 	 */
 	export default {
 		name: 'u-modal',
@@ -146,7 +147,7 @@
 			},
 			// 点击遮罩
 			// 从原理上来说,modal的遮罩点击,并不是真的点击到了遮罩
-			// 因为modal依赖于popup的中部弹窗类型,中部弹窗比较特殊,虽然遮罩,但是为了让弹窗内容能flex居中
+			// 因为modal依赖于popup的中部弹窗类型,中部弹窗比较特殊,虽然遮罩,但是为了让弹窗内容能flex居中
 			// 多了一个透明的遮罩,此透明的遮罩会覆盖在灰色的遮罩上,所以实际上是点击不到灰色遮罩的,popup内部在
 			// 透明遮罩的子元素做了.stop处理,所以点击内容区,也不会导致误触发
 			clickHandler() {
@@ -201,12 +202,12 @@
 				justify-content: center;
 				align-items: center;
 				height: 48px;
-				
+
 				&--confirm,
 				&--only-cancel {
 					border-bottom-right-radius: $u-modal-border-radius;
 				}
-				
+
 				&--cancel,
 				&--only-confirm {
 					border-bottom-left-radius: $u-modal-border-radius;

+ 1 - 0
uni_modules/uview-ui/components/u-no-network/u-no-network.vue

@@ -1,6 +1,7 @@
 <template>
 	<u-overlay
 	    :show="!isConnected"
+		:zIndex="zIndex"
 	    @touchmove.stop.prevent="noop"
 		:customStyle="{
 			backgroundColor: '#fff',

+ 5 - 2
uni_modules/uview-ui/components/u-picker/u-picker.vue

@@ -37,7 +37,8 @@
 						:style="{
 							height: $u.addUnit(itemHeight),
 							lineHeight: $u.addUnit(itemHeight),
-							fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal'
+							fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal',
+							display: 'block'
 						}"
 					>{{ getItemText(item1) }}</text>
 				</picker-view-column>
@@ -160,7 +161,7 @@ export default {
 			this.setIndexs(value)
 
 			this.$emit('change', {
-				// #ifndef MP-WEIXIN || MP-LARK
+				// #ifndef MP-WEIXIN || MP-LARK || MP-TOUTIAO
 				// 微信小程序不能传递this,会因为循环引用而报错
 				picker: this,
 				// #endif
@@ -189,6 +190,8 @@ export default {
 		setColumnValues(columnIndex, values) {
 			// 替换innerColumns数组中columnIndex索引的值为values,使用的是数组的splice方法
 			this.innerColumns.splice(columnIndex, 1, values)
+			// 替换完成之后将修改列之后的已选值置空
+			this.setLastIndex(this.innerIndex.slice(0,columnIndex))
 			// 拷贝一份原有的innerIndex做临时变量,将大于当前变化列的所有的列的默认索引设置为0
 			let tmpIndex = uni.$u.deepClone(this.innerIndex)
 			for (let i = 0; i < this.innerColumns.length; i++) {

+ 11 - 9
uni_modules/uview-ui/components/u-radio/u-radio.vue

@@ -20,15 +20,17 @@
 				/>
 			</slot>
 		</view>
-		<text
-			class="u-radio__text"
-		    @tap.stop="labelClickHandler"
-		    :style="{
-				color: elDisabled ? elInactiveColor : elLabelColor,
-				fontSize: elLabelSize,
-				lineHeight: elLabelSize
-			}"
-		>{{label}}</text>
+		<slot>
+			<text
+				class="u-radio__text"
+				@tap.stop="labelClickHandler"
+				:style="{
+					color: elDisabled ? elInactiveColor : elLabelColor,
+					fontSize: elLabelSize,
+					lineHeight: elLabelSize
+				}"
+			>{{label}}</text>
+		</slot>
 	</view>
 </template>
 

+ 4 - 2
uni_modules/uview-ui/components/u-rate/u-rate.vue

@@ -35,7 +35,8 @@
                                 : inactiveColor
                         "
                         :custom-style="{
-                            padding: `0 ${$u.addUnit(gutter / 2)}`,
+                            'padding-left': $u.addUnit(gutter / 2),
+							'padding-right': $u.addUnit(gutter / 2)
                         }"
                         :size="size"
                     ></u-icon>
@@ -63,7 +64,8 @@
                                 : inactiveColor
                         "
                         :custom-style="{
-                            padding: `0 ${$u.addUnit(gutter / 2)}`
+							'padding-left': $u.addUnit(gutter / 2),
+							'padding-right': $u.addUnit(gutter / 2)
                         }"
                         :size="size"
                     ></u-icon>

+ 10 - 1
uni_modules/uview-ui/components/u-tabs/u-tabs.vue

@@ -20,6 +20,7 @@
 							v-for="(item, index) in list"
 							:key="index"
 							@tap="clickHandler(item, index)"
+							@longpress="longPressHandler(item,index)"
 							:ref="`u-tabs__wrapper__nav__item-${index}`"
 							:style="[$u.addStyle(itemStyle), {flex: scrollable ? '' : 1}]"
 							:class="[`u-tabs__wrapper__nav__item-${index}`, item.disabled && 'u-tabs__wrapper__nav__item--disabled']"
@@ -94,7 +95,8 @@
 	 * @property {String}	keyName	 从`list`元素对象中读取的键名(默认 'name' )
 	 * @event {Function(index)} change 标签改变时触发 index: 点击了第几个tab,索引从0开始
 	 * @event {Function(index)} click 点击标签时触发 index: 点击了第几个tab,索引从0开始
-	 * @example <u-tabs :list="list" :is-scroll="false" :current="current" @change="change"></u-tabs>
+  	 * @event {Function(index)} longPress 长按标签时触发 index: 点击了第几个tab,索引从0开始
+	 * @example <u-tabs :list="list" :is-scroll="false" :current="current" @change="change" @longPress="longPress"></u-tabs>
 	 */
 	export default {
 		name: 'u-tabs',
@@ -208,6 +210,13 @@
 					index
 				})
 			},
+			// 长按事件
+			longPressHandler(item, index) {
+				this.$emit('longPress', {
+					...item,
+					index
+				})
+			},
 			init() {
 				uni.$u.sleep().then(() => {
 					this.resize()

+ 1 - 1
uni_modules/uview-ui/components/u-text/props.js

@@ -83,7 +83,7 @@ export default {
         },
         // 文字装饰,下划线,中划线等,可选值 none|underline|line-through
         decoration: {
-            tepe: String,
+            type: String,
             default: uni.$u.props.text.decoration
         },
         // 外边距,对象、字符串,数值形式均可

+ 5 - 0
uni_modules/uview-ui/components/u-textarea/props.js

@@ -109,6 +109,11 @@ export default {
 		formatter: {
 			type: [Function, null],
 			default: uni.$u.props.textarea.formatter
+		},
+		// 是否忽略组件内对文本合成系统事件的处理
+		ignoreCompositionEvent: {
+			type: Boolean,
+			default: true
 		}
 	}
 }

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

@@ -21,6 +21,7 @@
             :holdKeyboard="holdKeyboard"
             :maxlength="maxlength"
             :confirmType="confirmType"
+            :ignoreCompositionEvent="ignoreCompositionEvent"
             @focus="onFocus"
             @blur="onBlur"
             @linechange="onLinechange"
@@ -68,6 +69,7 @@ import props from "./props.js";
  * @property {Boolean}				holdKeyboard			focus时,点击页面的时候不收起键盘,只微信小程序有效(默认 false )
  * @property {String | Number}		maxlength				最大输入长度,设置为 -1 的时候不限制最大长度(默认 140 )
  * @property {String}				border					边框类型,surround-四周边框,none-无边框,bottom-底部边框(默认 'surround' )
+ * @property {Boolean}				ignoreCompositionEvent	是否忽略组件内对文本合成系统事件的处理
  *
  * @event {Function(e)} focus					输入框聚焦时触发,event.detail = { value, height },height 为键盘高度
  * @event {Function(e)} blur					输入框失去焦点时触发,event.detail = {value, cursor}

+ 3 - 3
uni_modules/uview-ui/libs/config/config.js

@@ -1,9 +1,9 @@
-// 此版本发布于2022-06-17
-const version = '2.0.33'
+// 此版本发布于2024-03-17
+const version = '2.0.37'
 
 // 开发环境才提示,生产环境不会提示
 if (process.env.NODE_ENV === 'development') {
-	console.log(`\n %c uView V${version} %c https://www.uviewui.com/ \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0;', 'color: #3c9cff;background: #ffffff; padding:5px 0;');
+	console.log(`\n %c uView V${version} %c https://uviewui.com/ \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0; border-radius: 5px;');
 }
 
 export default {

+ 1 - 0
uni_modules/uview-ui/libs/config/props/formItem.js

@@ -13,6 +13,7 @@ export default {
         label: '',
         prop: '',
         borderBottom: '',
+        labelPosition: '',
         labelWidth: '',
         rightIcon: '',
         leftIcon: '',

+ 2 - 1
uni_modules/uview-ui/libs/config/props/modal.js

@@ -25,6 +25,7 @@ export default {
         closeOnClickOverlay: false,
         negativeTop: 0,
         width: '650rpx',
-        confirmButtonShape: ''
+        confirmButtonShape: '',
+		duration:400,
     }
 }

+ 1 - 1
uni_modules/uview-ui/libs/config/props/textarea.js

@@ -15,7 +15,7 @@ export default {
 		placeholderClass: 'textarea-placeholder',
 		placeholderStyle: 'color: #c0c4cc',
 		height: 70,
-		confirmType: '',
+		confirmType: 'done',
 		disabled: false,
 		count: false,
 		focus: false,

+ 731 - 705
uni_modules/uview-ui/libs/function/index.js

@@ -1,705 +1,731 @@
-import test from './test.js'
-import { round } from './digit.js'
-/**
- * @description 如果value小于min,取min;如果value大于max,取max
- * @param {number} min 
- * @param {number} max 
- * @param {number} value
- */
-function range(min = 0, max = 0, value = 0) {
-	return Math.max(min, Math.min(max, Number(value)))
-}
-
-/**
- * @description 用于获取用户传递值的px值  如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
- * @param {number|string} value 用户传递值的px值
- * @param {boolean} unit 
- * @returns {number|string}
- */
-function getPx(value, unit = false) {
-	if (test.number(value)) {
-		return unit ? `${value}px` : Number(value)
-	}
-	// 如果带有rpx,先取出其数值部分,再转为px值
-	if (/(rpx|upx)$/.test(value)) {
-		return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
-	}
-	return unit ? `${parseInt(value)}px` : parseInt(value)
-}
-
-/**
- * @description 进行延时,以达到可以简写代码的目的 比如: await uni.$u.sleep(20)将会阻塞20ms
- * @param {number} value 堵塞时间 单位ms 毫秒
- * @returns {Promise} 返回promise
- */
-function sleep(value = 30) {
-	return new Promise((resolve) => {
-		setTimeout(() => {
-			resolve()
-		}, value)
-	})
-}
-/**
- * @description 运行期判断平台
- * @returns {string} 返回所在平台(小写) 
- * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
- */
-function os() {
-	return uni.getSystemInfoSync().platform.toLowerCase()
-}
-/**
- * @description 获取系统信息同步接口
- * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync 
- */
-function sys() {
-	return uni.getSystemInfoSync()
-}
-
-/**
- * @description 取一个区间数
- * @param {Number} min 最小值
- * @param {Number} max 最大值
- */
-function random(min, max) {
-	if (min >= 0 && max > 0 && max >= min) {
-		const gab = max - min + 1
-		return Math.floor(Math.random() * gab + min)
-	}
-	return 0
-}
-
-/**
- * @param {Number} len uuid的长度
- * @param {Boolean} firstU 将返回的首字母置为"u"
- * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
- */
-function guid(len = 32, firstU = true, radix = null) {
-	const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
-	const uuid = []
-	radix = radix || chars.length
-
-	if (len) {
-		// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
-		for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
-	} else {
-		let r
-		// rfc4122标准要求返回的uuid中,某些位为固定的字符
-		uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
-		uuid[14] = '4'
-
-		for (let i = 0; i < 36; i++) {
-			if (!uuid[i]) {
-				r = 0 | Math.random() * 16
-				uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
-			}
-		}
-	}
-	// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
-	if (firstU) {
-		uuid.shift()
-		return `u${uuid.join('')}`
-	}
-	return uuid.join('')
-}
-
-/**
-* @description 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
-   this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
-   这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
-   值(默认为undefined),就是查找最顶层的$parent
-*  @param {string|undefined} name 父组件的参数名
-*/
-function $parent(name = undefined) {
-	let parent = this.$parent
-	// 通过while历遍,这里主要是为了H5需要多层解析的问题
-	while (parent) {
-		// 父组件
-		if (parent.$options && parent.$options.name !== name) {
-			// 如果组件的name不相等,继续上一级寻找
-			parent = parent.$parent
-		} else {
-			return parent
-		}
-	}
-	return false
-}
-
-/**
- * @description 样式转换
- * 对象转字符串,或者字符串转对象
- * @param {object | string} customStyle 需要转换的目标
- * @param {String} target 转换的目的,object-转为对象,string-转为字符串
- * @returns {object|string}
- */
-function addStyle(customStyle, target = 'object') {
-	// 字符串转字符串,对象转对象情形,直接返回
-	if (test.empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&
-		typeof(customStyle) === 'string') {
-		return customStyle
-	}
-	// 字符串转对象
-	if (target === 'object') {
-		// 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
-		customStyle = trim(customStyle)
-		// 根据";"将字符串转为数组形式
-		const styleArray = customStyle.split(';')
-		const style = {}
-		// 历遍数组,拼接成对象
-		for (let i = 0; i < styleArray.length; i++) {
-			// 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
-			if (styleArray[i]) {
-				const item = styleArray[i].split(':')
-				style[trim(item[0])] = trim(item[1])
-			}
-		}
-		return style
-	}
-	// 这里为对象转字符串形式
-	let string = ''
-	for (const i in customStyle) {
-		// 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
-		const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
-		string += `${key}:${customStyle[i]};`
-	}
-	// 去除两端空格
-	return trim(string)
-}
-
-/**
- * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
- * @param {string|number} value 需要添加单位的值
- * @param {string} unit 添加的单位名 比如px
- */
-function addUnit(value = 'auto', unit = uni?.$u?.config?.unit ?? 'px') {
-	value = String(value)
-	// 用uView内置验证规则中的number判断是否为数值
-	return test.number(value) ? `${value}${unit}` : value
-}
-
-/**
- * @description 深度克隆
- * @param {object} obj 需要深度克隆的对象
- * @returns {*} 克隆后的对象或者原值(不是对象)
- */
-function deepClone(obj) {
-	// 对常见的“非”值,直接返回原来值
-	if ([null, undefined, NaN, false].includes(obj)) return obj
-	if (typeof obj !== 'object' && typeof obj !== 'function') {
-		// 原始类型直接返回
-		return obj
-	}
-	const o = test.array(obj) ? [] : {}
-	for (const i in obj) {
-		if (obj.hasOwnProperty(i)) {
-			o[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]
-		}
-	}
-	return o
-}
-
-/**
- * @description JS对象深度合并
- * @param {object} target 需要拷贝的对象
- * @param {object} source 拷贝的来源对象
- * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
- */
-function deepMerge(target = {}, source = {}) {
-	target = deepClone(target)
-	if (typeof target !== 'object' || typeof source !== 'object') return false
-	for (const prop in source) {
-		if (!source.hasOwnProperty(prop)) continue
-		if (prop in target) {
-			if (typeof target[prop] !== 'object') {
-				target[prop] = source[prop]
-			} else if (typeof source[prop] !== 'object') {
-				target[prop] = source[prop]
-			} else if (target[prop].concat && source[prop].concat) {
-				target[prop] = target[prop].concat(source[prop])
-			} else {
-				target[prop] = deepMerge(target[prop], source[prop])
-			}
-		} else {
-			target[prop] = source[prop]
-		}
-	}
-	return target
-}
-
-/**
- * @description error提示
- * @param {*} err 错误内容
- */
-function error(err) {
-	// 开发环境才提示,生产环境不会提示
-	if (process.env.NODE_ENV === 'development') {
-		console.error(`uView提示:${err}`)
-	}
-}
-
-/**
- * @description 打乱数组
- * @param {array} array 需要打乱的数组
- * @returns {array} 打乱后的数组
- */
-function randomArray(array = []) {
-	// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
-	return array.sort(() => Math.random() - 0.5)
-}
-
-// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
-// 所以这里做一个兼容polyfill的兼容处理
-if (!String.prototype.padStart) {
-	// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
-	String.prototype.padStart = function(maxLength, fillString = ' ') {
-		if (Object.prototype.toString.call(fillString) !== '[object String]') {
-			throw new TypeError(
-				'fillString must be String'
-			)
-		}
-		const str = this
-		// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
-		if (str.length >= maxLength) return String(str)
-
-		const fillLength = maxLength - str.length
-		let times = Math.ceil(fillLength / fillString.length)
-		while (times >>= 1) {
-			fillString += fillString
-			if (times === 1) {
-				fillString += fillString
-			}
-		}
-		return fillString.slice(0, fillLength) + str
-	}
-}
-
-/**
- * @description 格式化时间
- * @param {String|Number} dateTime 需要格式化的时间戳
- * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
- * @returns {string} 返回格式化后的字符串
- */
- function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
-  let date
-	// 若传入时间为假值,则取当前时间
-  if (!dateTime) {
-    date = new Date()
-  }
-  // 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容)
-  else if (/^\d{10}$/.test(dateTime?.toString().trim())) {
-    date = new Date(dateTime * 1000)
-  }
-  // 若用户传入字符串格式时间戳,new Date无法解析,需做兼容
-  else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
-    date = new Date(Number(dateTime))
-  }
-  // 其他都认为符合 RFC 2822 规范
-  else {
-    // 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间
-    date = new Date(
-      typeof dateTime === 'string'
-        ? dateTime.replace(/-/g, '/')
-        : dateTime
-    )
-  }
-
-	const timeSource = {
-		'y': date.getFullYear().toString(), // 年
-		'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
-		'd': date.getDate().toString().padStart(2, '0'), // 日
-		'h': date.getHours().toString().padStart(2, '0'), // 时
-		'M': date.getMinutes().toString().padStart(2, '0'), // 分
-		's': date.getSeconds().toString().padStart(2, '0') // 秒
-		// 有其他格式化字符需求可以继续添加,必须转化成字符串
-	}
-
-  for (const key in timeSource) {
-    const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
-    if (ret) {
-      // 年可能只需展示两位
-      const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
-      formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
-    }
-  }
-
-  return formatStr
-}
-
-/**
- * @description 时间戳转为多久之前
- * @param {String|Number} timestamp 时间戳
- * @param {String|Boolean} format 
- * 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
- * 如果为布尔值false,无论什么时间,都返回多久以前的格式
- * @returns {string} 转化后的内容
- */
-function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
-	if (timestamp == null) timestamp = Number(new Date())
-	timestamp = parseInt(timestamp)
-	// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
-	if (timestamp.toString().length == 10) timestamp *= 1000
-	let timer = (new Date()).getTime() - timestamp
-	timer = parseInt(timer / 1000)
-	// 如果小于5分钟,则返回"刚刚",其他以此类推
-	let tips = ''
-	switch (true) {
-		case timer < 300:
-			tips = '刚刚'
-			break
-		case timer >= 300 && timer < 3600:
-			tips = `${parseInt(timer / 60)}分钟前`
-			break
-		case timer >= 3600 && timer < 86400:
-			tips = `${parseInt(timer / 3600)}小时前`
-			break
-		case timer >= 86400 && timer < 2592000:
-			tips = `${parseInt(timer / 86400)}天前`
-			break
-		default:
-			// 如果format为false,则无论什么时间戳,都显示xx之前
-			if (format === false) {
-				if (timer >= 2592000 && timer < 365 * 86400) {
-					tips = `${parseInt(timer / (86400 * 30))}个月前`
-				} else {
-					tips = `${parseInt(timer / (86400 * 365))}年前`
-				}
-			} else {
-				tips = timeFormat(timestamp, format)
-			}
-	}
-	return tips
-}
-
-/**
- * @description 去除空格
- * @param String str 需要去除空格的字符串
- * @param String pos both(左右)|left|right|all 默认both
- */
-function trim(str, pos = 'both') {
-	str = String(str)
-	if (pos == 'both') {
-		return str.replace(/^\s+|\s+$/g, '')
-	}
-	if (pos == 'left') {
-		return str.replace(/^\s*/, '')
-	}
-	if (pos == 'right') {
-		return str.replace(/(\s*$)/g, '')
-	}
-	if (pos == 'all') {
-		return str.replace(/\s+/g, '')
-	}
-	return str
-}
-
-/**
- * @description 对象转url参数
- * @param {object} data,对象
- * @param {Boolean} isPrefix,是否自动加上"?"
- * @param {string} arrayFormat 规则 indices|brackets|repeat|comma
- */
-function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
-	const prefix = isPrefix ? '?' : ''
-	const _result = []
-	if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
-	for (const key in data) {
-		const value = data[key]
-		// 去掉为空的参数
-		if (['', undefined, null].indexOf(value) >= 0) {
-			continue
-		}
-		// 如果值为数组,另行处理
-		if (value.constructor === Array) {
-			// e.g. {ids: [1, 2, 3]}
-			switch (arrayFormat) {
-				case 'indices':
-					// 结果: ids[0]=1&ids[1]=2&ids[2]=3
-					for (let i = 0; i < value.length; i++) {
-						_result.push(`${key}[${i}]=${value[i]}`)
-					}
-					break
-				case 'brackets':
-					// 结果: ids[]=1&ids[]=2&ids[]=3
-					value.forEach((_value) => {
-						_result.push(`${key}[]=${_value}`)
-					})
-					break
-				case 'repeat':
-					// 结果: ids=1&ids=2&ids=3
-					value.forEach((_value) => {
-						_result.push(`${key}=${_value}`)
-					})
-					break
-				case 'comma':
-					// 结果: ids=1,2,3
-					let commaStr = ''
-					value.forEach((_value) => {
-						commaStr += (commaStr ? ',' : '') + _value
-					})
-					_result.push(`${key}=${commaStr}`)
-					break
-				default:
-					value.forEach((_value) => {
-						_result.push(`${key}[]=${_value}`)
-					})
-			}
-		} else {
-			_result.push(`${key}=${value}`)
-		}
-	}
-	return _result.length ? prefix + _result.join('&') : ''
-}
-
-/**
- * 显示消息提示框
- * @param {String} title 提示的内容,长度与 icon 取值有关。
- * @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000
- */
-function toast(title, duration = 2000) {
-	uni.showToast({
-		title: String(title),
-		icon: 'none',
-		duration
-	})
-}
-
-/**
- * @description 根据主题type值,获取对应的图标
- * @param {String} type 主题名称,primary|info|error|warning|success
- * @param {boolean} fill 是否使用fill填充实体的图标
- */
-function type2icon(type = 'success', fill = false) {
-	// 如果非预置值,默认为success
-	if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'
-	let iconName = ''
-	// 目前(2019-12-12),info和primary使用同一个图标
-	switch (type) {
-		case 'primary':
-			iconName = 'info-circle'
-			break
-		case 'info':
-			iconName = 'info-circle'
-			break
-		case 'error':
-			iconName = 'close-circle'
-			break
-		case 'warning':
-			iconName = 'error-circle'
-			break
-		case 'success':
-			iconName = 'checkmark-circle'
-			break
-		default:
-			iconName = 'checkmark-circle'
-	}
-	// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
-	if (fill) iconName += '-fill'
-	return iconName
-}
-
-/**
- * @description 数字格式化
- * @param {number|string} number 要格式化的数字
- * @param {number} decimals 保留几位小数
- * @param {string} decimalPoint 小数点符号
- * @param {string} thousandsSeparator 千分位符号
- * @returns {string} 格式化后的数字
- */
-function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
-	number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
-	const n = !isFinite(+number) ? 0 : +number
-	const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
-	const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
-	const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
-	let s = ''
-
-	s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
-	const re = /(-?\d+)(\d{3})/
-	while (re.test(s[0])) {
-		s[0] = s[0].replace(re, `$1${sep}$2`)
-	}
-	
-	if ((s[1] || '').length < prec) {
-		s[1] = s[1] || ''
-		s[1] += new Array(prec - s[1].length + 1).join('0')
-	}
-	return s.join(dec)
-}
-
-/**
- * @description 获取duration值
- * 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位
- * 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画
- * @param {String|number} value 比如: "1s"|"100ms"|1|100
- * @param {boolean} unit  提示: 如果是false 默认返回number
- * @return {string|number} 
- */
-function getDuration(value, unit = true) {
-	const valueNum = parseInt(value)
-	if (unit) {
-		if (/s$/.test(value)) return value
-		return value > 30 ? `${value}ms` : `${value}s`
-	}
-	if (/ms$/.test(value)) return valueNum
-	if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000
-	return valueNum
-}
-
-/**
- * @description 日期的月或日补零操作
- * @param {String} value 需要补零的值
- */
-function padZero(value) {
-	return `00${value}`.slice(-2)
-}
-
-/**
- * @description 在u-form的子组件内容发生变化,或者失去焦点时,尝试通知u-form执行校验方法
- * @param {*} instance
- * @param {*} event
- */
-function formValidate(instance, event) {
-	const formItem = uni.$u.$parent.call(instance, 'u-form-item')
-	const form = uni.$u.$parent.call(instance, 'u-form')
-	// 如果发生变化的input或者textarea等,其父组件中有u-form-item或者u-form等,就执行form的validate方法
-	// 同时将form-item的pros传递给form,让其进行精确对象验证
-	if (formItem && form) {
-		form.validateField(formItem.prop, () => {}, event)
-	}
-}
-
-/**
- * @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
- * @param {object} obj 对象
- * @param {string} key 需要获取的属性字段
- * @returns {*}
- */
-function getProperty(obj, key) {
-	if (!obj) {
-		return
-	}
-	if (typeof key !== 'string' || key === '') {
-		return ''
-	}
-	if (key.indexOf('.') !== -1) {
-		const keys = key.split('.')
-		let firstObj = obj[keys[0]] || {}
-
-		for (let i = 1; i < keys.length; i++) {
-			if (firstObj) {
-				firstObj = firstObj[keys[i]]
-			}
-		}
-		return firstObj
-	}
-	return obj[key]
-}
-
-/**
- * @description 设置对象的属性值,如果'a.b.c'的形式进行设置
- * @param {object} obj 对象
- * @param {string} key 需要设置的属性
- * @param {string} value 设置的值
- */
-function setProperty(obj, key, value) {
-	if (!obj) {
-		return
-	}
-	// 递归赋值
-	const inFn = function(_obj, keys, v) {
-		// 最后一个属性key
-		if (keys.length === 1) {
-			_obj[keys[0]] = v
-			return
-		}
-		// 0~length-1个key
-		while (keys.length > 1) {
-			const k = keys[0]
-			if (!_obj[k] || (typeof _obj[k] !== 'object')) {
-				_obj[k] = {}
-			}
-			const key = keys.shift()
-			// 自调用判断是否存在属性,不存在则自动创建对象
-			inFn(_obj[k], keys, v)
-		}
-	}
-
-	if (typeof key !== 'string' || key === '') {
-
-	} else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
-		const keys = key.split('.')
-		inFn(obj, keys, value)
-	} else {
-		obj[key] = value
-	}
-}
-
-/**
- * @description 获取当前页面路径
- */
-function page() {
-	const pages = getCurrentPages()
-	// 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组
-	return `/${pages[pages.length - 1]?.route ?? ''}`
-}
-
-/**
- * @description 获取当前路由栈实例数组
- */
-function pages() {
-	const pages = getCurrentPages()
-	return pages
-}
-
-/**
- * @description 修改uView内置属性值
- * @param {object} props 修改内置props属性
- * @param {object} config 修改内置config属性
- * @param {object} color 修改内置color属性
- * @param {object} zIndex 修改内置zIndex属性
- */
-function setConfig({
-	props = {},
-	config = {},
-	color = {},
-	zIndex = {}
-}) {
-	const {
-		deepMerge,
-	} = uni.$u
-	uni.$u.config = deepMerge(uni.$u.config, config)
-	uni.$u.props = deepMerge(uni.$u.props, props)
-	uni.$u.color = deepMerge(uni.$u.color, color)
-	uni.$u.zIndex = deepMerge(uni.$u.zIndex, zIndex)
-}
-
-export default {
-	range,
-	getPx,
-	sleep,
-	os,
-	sys,
-	random,
-	guid,
-	$parent,
-	addStyle,
-	addUnit,
-	deepClone,
-	deepMerge,
-	error,
-	randomArray,
-	timeFormat,
-	timeFrom,
-	trim,
-	queryParams,
-	toast,
-	type2icon,
-	priceFormat,
-	getDuration,
-	padZero,
-	formValidate,
-	getProperty,
-	setProperty,
-	page,
-	pages,
-	setConfig
-}
+import test from './test.js'
+import { round } from './digit.js'
+/**
+ * @description 如果value小于min,取min;如果value大于max,取max
+ * @param {number} min
+ * @param {number} max
+ * @param {number} value
+ */
+function range(min = 0, max = 0, value = 0) {
+	return Math.max(min, Math.min(max, Number(value)))
+}
+
+/**
+ * @description 用于获取用户传递值的px值  如果用户传递了"xxpx"或者"xxrpx",取出其数值部分,如果是"xxxrpx"还需要用过uni.upx2px进行转换
+ * @param {number|string} value 用户传递值的px值
+ * @param {boolean} unit
+ * @returns {number|string}
+ */
+function getPx(value, unit = false) {
+	if (test.number(value)) {
+		return unit ? `${value}px` : Number(value)
+	}
+	// 如果带有rpx,先取出其数值部分,再转为px值
+	if (/(rpx|upx)$/.test(value)) {
+		return unit ? `${uni.upx2px(parseInt(value))}px` : Number(uni.upx2px(parseInt(value)))
+	}
+	return unit ? `${parseInt(value)}px` : parseInt(value)
+}
+
+/**
+ * @description 进行延时,以达到可以简写代码的目的 比如: await uni.$u.sleep(20)将会阻塞20ms
+ * @param {number} value 堵塞时间 单位ms 毫秒
+ * @returns {Promise} 返回promise
+ */
+function sleep(value = 30) {
+	return new Promise((resolve) => {
+		setTimeout(() => {
+			resolve()
+		}, value)
+	})
+}
+/**
+ * @description 运行期判断平台
+ * @returns {string} 返回所在平台(小写)
+ * @link 运行期判断平台 https://uniapp.dcloud.io/frame?id=判断平台
+ */
+function os() {
+	return uni.getSystemInfoSync().platform.toLowerCase()
+}
+/**
+ * @description 获取系统信息同步接口
+ * @link 获取系统信息同步接口 https://uniapp.dcloud.io/api/system/info?id=getsysteminfosync
+ */
+function sys() {
+	return uni.getSystemInfoSync()
+}
+
+/**
+ * @description 取一个区间数
+ * @param {Number} min 最小值
+ * @param {Number} max 最大值
+ */
+function random(min, max) {
+	if (min >= 0 && max > 0 && max >= min) {
+		const gab = max - min + 1
+		return Math.floor(Math.random() * gab + min)
+	}
+	return 0
+}
+
+/**
+ * @param {Number} len uuid的长度
+ * @param {Boolean} firstU 将返回的首字母置为"u"
+ * @param {Nubmer} radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
+ */
+function guid(len = 32, firstU = true, radix = null) {
+	const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
+	const uuid = []
+	radix = radix || chars.length
+
+	if (len) {
+		// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
+		for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
+	} else {
+		let r
+		// rfc4122标准要求返回的uuid中,某些位为固定的字符
+		uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
+		uuid[14] = '4'
+
+		for (let i = 0; i < 36; i++) {
+			if (!uuid[i]) {
+				r = 0 | Math.random() * 16
+				uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
+			}
+		}
+	}
+	// 移除第一个字符,并用u替代,因为第一个字符为数值时,该guuid不能用作id或者class
+	if (firstU) {
+		uuid.shift()
+		return `u${uuid.join('')}`
+	}
+	return uuid.join('')
+}
+
+/**
+* @description 获取父组件的参数,因为支付宝小程序不支持provide/inject的写法
+   this.$parent在非H5中,可以准确获取到父组件,但是在H5中,需要多次this.$parent.$parent.xxx
+   这里默认值等于undefined有它的含义,因为最顶层元素(组件)的$parent就是undefined,意味着不传name
+   值(默认为undefined),就是查找最顶层的$parent
+*  @param {string|undefined} name 父组件的参数名
+*/
+function $parent(name = undefined) {
+	let parent = this.$parent
+	// 通过while历遍,这里主要是为了H5需要多层解析的问题
+	while (parent) {
+		// 父组件
+		if (parent.$options && parent.$options.name !== name) {
+			// 如果组件的name不相等,继续上一级寻找
+			parent = parent.$parent
+		} else {
+			return parent
+		}
+	}
+	return false
+}
+
+/**
+ * @description 样式转换
+ * 对象转字符串,或者字符串转对象
+ * @param {object | string} customStyle 需要转换的目标
+ * @param {String} target 转换的目的,object-转为对象,string-转为字符串
+ * @returns {object|string}
+ */
+function addStyle(customStyle, target = 'object') {
+	// 字符串转字符串,对象转对象情形,直接返回
+	if (test.empty(customStyle) || typeof(customStyle) === 'object' && target === 'object' || target === 'string' &&
+		typeof(customStyle) === 'string') {
+		return customStyle
+	}
+	// 字符串转对象
+	if (target === 'object') {
+		// 去除字符串样式中的两端空格(中间的空格不能去掉,比如padding: 20px 0如果去掉了就错了),空格是无用的
+		customStyle = trim(customStyle)
+		// 根据";"将字符串转为数组形式
+		const styleArray = customStyle.split(';')
+		const style = {}
+		// 历遍数组,拼接成对象
+		for (let i = 0; i < styleArray.length; i++) {
+			// 'font-size:20px;color:red;',如此最后字符串有";"的话,会导致styleArray最后一个元素为空字符串,这里需要过滤
+			if (styleArray[i]) {
+				const item = styleArray[i].split(':')
+				style[trim(item[0])] = trim(item[1])
+			}
+		}
+		return style
+	}
+	// 这里为对象转字符串形式
+	let string = ''
+	for (const i in customStyle) {
+		// 驼峰转为中划线的形式,否则css内联样式,无法识别驼峰样式属性名
+		const key = i.replace(/([A-Z])/g, '-$1').toLowerCase()
+		string += `${key}:${customStyle[i]};`
+	}
+	// 去除两端空格
+	return trim(string)
+}
+
+/**
+ * @description 添加单位,如果有rpx,upx,%,px等单位结尾或者值为auto,直接返回,否则加上px单位结尾
+ * @param {string|number} value 需要添加单位的值
+ * @param {string} unit 添加的单位名 比如px
+ */
+function addUnit(value = 'auto', unit = uni?.$u?.config?.unit ?? 'px') {
+	value = String(value)
+	// 用uView内置验证规则中的number判断是否为数值
+	return test.number(value) ? `${value}${unit}` : value
+}
+
+/**
+ * @description 深度克隆
+ * @param {object} obj 需要深度克隆的对象
+ * @param cache 缓存
+ * @returns {*} 克隆后的对象或者原值(不是对象)
+ */
+function deepClone(obj, cache = new WeakMap()) {
+	if (obj === null || typeof obj !== 'object') return obj;
+	if (cache.has(obj)) return cache.get(obj);
+	let clone;
+	if (obj instanceof Date) {
+		clone = new Date(obj.getTime());
+	} else if (obj instanceof RegExp) {
+		clone = new RegExp(obj);
+	} else if (obj instanceof Map) {
+		clone = new Map(Array.from(obj, ([key, value]) => [key, deepClone(value, cache)]));
+	} else if (obj instanceof Set) {
+		clone = new Set(Array.from(obj, value => deepClone(value, cache)));
+	} else if (Array.isArray(obj)) {
+		clone = obj.map(value => deepClone(value, cache));
+	} else if (Object.prototype.toString.call(obj) === '[object Object]') {
+		clone = Object.create(Object.getPrototypeOf(obj));
+		cache.set(obj, clone);
+		for (const [key, value] of Object.entries(obj)) {
+			clone[key] = deepClone(value, cache);
+		}
+	} else {
+		clone = Object.assign({}, obj);
+	}
+	cache.set(obj, clone);
+	return clone;
+}
+
+/**
+ * @description JS对象深度合并
+ * @param {object} target 需要拷贝的对象
+ * @param {object} source 拷贝的来源对象
+ * @returns {object|boolean} 深度合并后的对象或者false(入参有不是对象)
+ */
+function deepMerge(target = {}, source = {}) {
+	target = deepClone(target)
+	if (typeof target !== 'object' || target === null || typeof source !== 'object' || source === null) return target;
+	const merged = Array.isArray(target) ? target.slice() : Object.assign({}, target);
+	for (const prop in source) {
+		if (!source.hasOwnProperty(prop)) continue;
+		const sourceValue = source[prop];
+		const targetValue = merged[prop];
+		if (sourceValue instanceof Date) {
+			merged[prop] = new Date(sourceValue);
+		} else if (sourceValue instanceof RegExp) {
+			merged[prop] = new RegExp(sourceValue);
+		} else if (sourceValue instanceof Map) {
+			merged[prop] = new Map(sourceValue);
+		} else if (sourceValue instanceof Set) {
+			merged[prop] = new Set(sourceValue);
+		} else if (typeof sourceValue === 'object' && sourceValue !== null) {
+			merged[prop] = deepMerge(targetValue, sourceValue);
+		} else {
+			merged[prop] = sourceValue;
+		}
+	}
+	return merged;
+}
+
+/**
+ * @description error提示
+ * @param {*} err 错误内容
+ */
+function error(err) {
+	// 开发环境才提示,生产环境不会提示
+	if (process.env.NODE_ENV === 'development') {
+		console.error(`uView提示:${err}`)
+	}
+}
+
+/**
+ * @description 打乱数组
+ * @param {array} array 需要打乱的数组
+ * @returns {array} 打乱后的数组
+ */
+function randomArray(array = []) {
+	// 原理是sort排序,Math.random()产生0<= x < 1之间的数,会导致x-0.05大于或者小于0
+	return array.sort(() => Math.random() - 0.5)
+}
+
+// padStart 的 polyfill,因为某些机型或情况,还无法支持es7的padStart,比如电脑版的微信小程序
+// 所以这里做一个兼容polyfill的兼容处理
+if (!String.prototype.padStart) {
+	// 为了方便表示这里 fillString 用了ES6 的默认参数,不影响理解
+	String.prototype.padStart = function(maxLength, fillString = ' ') {
+		if (Object.prototype.toString.call(fillString) !== '[object String]') {
+			throw new TypeError(
+				'fillString must be String'
+			)
+		}
+		const str = this
+		// 返回 String(str) 这里是为了使返回的值是字符串字面量,在控制台中更符合直觉
+		if (str.length >= maxLength) return String(str)
+
+		const fillLength = maxLength - str.length
+		let times = Math.ceil(fillLength / fillString.length)
+		while (times >>= 1) {
+			fillString += fillString
+			if (times === 1) {
+				fillString += fillString
+			}
+		}
+		return fillString.slice(0, fillLength) + str
+	}
+}
+
+/**
+ * @description 格式化时间
+ * @param {String|Number} dateTime 需要格式化的时间戳
+ * @param {String} fmt 格式化规则 yyyy:mm:dd|yyyy:mm|yyyy年mm月dd日|yyyy年mm月dd日 hh时MM分等,可自定义组合 默认yyyy-mm-dd
+ * @returns {string} 返回格式化后的字符串
+ */
+ function timeFormat(dateTime = null, formatStr = 'yyyy-mm-dd') {
+  let date
+	// 若传入时间为假值,则取当前时间
+  if (!dateTime) {
+    date = new Date()
+  }
+  // 若为unix秒时间戳,则转为毫秒时间戳(逻辑有点奇怪,但不敢改,以保证历史兼容)
+  else if (/^\d{10}$/.test(dateTime?.toString().trim())) {
+    date = new Date(dateTime * 1000)
+  }
+  // 若用户传入字符串格式时间戳,new Date无法解析,需做兼容
+  else if (typeof dateTime === 'string' && /^\d+$/.test(dateTime.trim())) {
+    date = new Date(Number(dateTime))
+  }
+	// 处理平台性差异,在Safari/Webkit中,new Date仅支持/作为分割符的字符串时间
+	// 处理 '2022-07-10 01:02:03',跳过 '2022-07-10T01:02:03'
+	else if (typeof dateTime === 'string' && dateTime.includes('-') && !dateTime.includes('T')) {
+		date = new Date(dateTime.replace(/-/g, '/'))
+	}
+	// 其他都认为符合 RFC 2822 规范
+	else {
+		date = new Date(dateTime)
+	}
+
+	const timeSource = {
+		'y': date.getFullYear().toString(), // 年
+		'm': (date.getMonth() + 1).toString().padStart(2, '0'), // 月
+		'd': date.getDate().toString().padStart(2, '0'), // 日
+		'h': date.getHours().toString().padStart(2, '0'), // 时
+		'M': date.getMinutes().toString().padStart(2, '0'), // 分
+		's': date.getSeconds().toString().padStart(2, '0') // 秒
+		// 有其他格式化字符需求可以继续添加,必须转化成字符串
+	}
+
+  for (const key in timeSource) {
+    const [ret] = new RegExp(`${key}+`).exec(formatStr) || []
+    if (ret) {
+      // 年可能只需展示两位
+      const beginIndex = key === 'y' && ret.length === 2 ? 2 : 0
+      formatStr = formatStr.replace(ret, timeSource[key].slice(beginIndex))
+    }
+  }
+
+  return formatStr
+}
+
+/**
+ * @description 时间戳转为多久之前
+ * @param {String|Number} timestamp 时间戳
+ * @param {String|Boolean} format
+ * 格式化规则如果为时间格式字符串,超出一定时间范围,返回固定的时间格式;
+ * 如果为布尔值false,无论什么时间,都返回多久以前的格式
+ * @returns {string} 转化后的内容
+ */
+function timeFrom(timestamp = null, format = 'yyyy-mm-dd') {
+	if (timestamp == null) timestamp = Number(new Date())
+	timestamp = parseInt(timestamp)
+	// 判断用户输入的时间戳是秒还是毫秒,一般前端js获取的时间戳是毫秒(13位),后端传过来的为秒(10位)
+	if (timestamp.toString().length == 10) timestamp *= 1000
+	let timer = (new Date()).getTime() - timestamp
+	timer = parseInt(timer / 1000)
+	// 如果小于5分钟,则返回"刚刚",其他以此类推
+	let tips = ''
+	switch (true) {
+		case timer < 300:
+			tips = '刚刚'
+			break
+		case timer >= 300 && timer < 3600:
+			tips = `${parseInt(timer / 60)}分钟前`
+			break
+		case timer >= 3600 && timer < 86400:
+			tips = `${parseInt(timer / 3600)}小时前`
+			break
+		case timer >= 86400 && timer < 2592000:
+			tips = `${parseInt(timer / 86400)}天前`
+			break
+		default:
+			// 如果format为false,则无论什么时间戳,都显示xx之前
+			if (format === false) {
+				if (timer >= 2592000 && timer < 365 * 86400) {
+					tips = `${parseInt(timer / (86400 * 30))}个月前`
+				} else {
+					tips = `${parseInt(timer / (86400 * 365))}年前`
+				}
+			} else {
+				tips = timeFormat(timestamp, format)
+			}
+	}
+	return tips
+}
+
+/**
+ * @description 去除空格
+ * @param String str 需要去除空格的字符串
+ * @param String pos both(左右)|left|right|all 默认both
+ */
+function trim(str, pos = 'both') {
+	str = String(str)
+	if (pos == 'both') {
+		return str.replace(/^\s+|\s+$/g, '')
+	}
+	if (pos == 'left') {
+		return str.replace(/^\s*/, '')
+	}
+	if (pos == 'right') {
+		return str.replace(/(\s*$)/g, '')
+	}
+	if (pos == 'all') {
+		return str.replace(/\s+/g, '')
+	}
+	return str
+}
+
+/**
+ * @description 对象转url参数
+ * @param {object} data,对象
+ * @param {Boolean} isPrefix,是否自动加上"?"
+ * @param {string} arrayFormat 规则 indices|brackets|repeat|comma
+ */
+function queryParams(data = {}, isPrefix = true, arrayFormat = 'brackets') {
+	const prefix = isPrefix ? '?' : ''
+	const _result = []
+	if (['indices', 'brackets', 'repeat', 'comma'].indexOf(arrayFormat) == -1) arrayFormat = 'brackets'
+	for (const key in data) {
+		const value = data[key]
+		// 去掉为空的参数
+		if (['', undefined, null].indexOf(value) >= 0) {
+			continue
+		}
+		// 如果值为数组,另行处理
+		if (value.constructor === Array) {
+			// e.g. {ids: [1, 2, 3]}
+			switch (arrayFormat) {
+				case 'indices':
+					// 结果: ids[0]=1&ids[1]=2&ids[2]=3
+					for (let i = 0; i < value.length; i++) {
+						_result.push(`${key}[${i}]=${value[i]}`)
+					}
+					break
+				case 'brackets':
+					// 结果: ids[]=1&ids[]=2&ids[]=3
+					value.forEach((_value) => {
+						_result.push(`${key}[]=${_value}`)
+					})
+					break
+				case 'repeat':
+					// 结果: ids=1&ids=2&ids=3
+					value.forEach((_value) => {
+						_result.push(`${key}=${_value}`)
+					})
+					break
+				case 'comma':
+					// 结果: ids=1,2,3
+					let commaStr = ''
+					value.forEach((_value) => {
+						commaStr += (commaStr ? ',' : '') + _value
+					})
+					_result.push(`${key}=${commaStr}`)
+					break
+				default:
+					value.forEach((_value) => {
+						_result.push(`${key}[]=${_value}`)
+					})
+			}
+		} else {
+			_result.push(`${key}=${value}`)
+		}
+	}
+	return _result.length ? prefix + _result.join('&') : ''
+}
+
+/**
+ * 显示消息提示框
+ * @param {String} title 提示的内容,长度与 icon 取值有关。
+ * @param {Number} duration 提示的延迟时间,单位毫秒,默认:2000
+ */
+function toast(title, duration = 2000) {
+	uni.showToast({
+		title: String(title),
+		icon: 'none',
+		duration
+	})
+}
+
+/**
+ * @description 根据主题type值,获取对应的图标
+ * @param {String} type 主题名称,primary|info|error|warning|success
+ * @param {boolean} fill 是否使用fill填充实体的图标
+ */
+function type2icon(type = 'success', fill = false) {
+	// 如果非预置值,默认为success
+	if (['primary', 'info', 'error', 'warning', 'success'].indexOf(type) == -1) type = 'success'
+	let iconName = ''
+	// 目前(2019-12-12),info和primary使用同一个图标
+	switch (type) {
+		case 'primary':
+			iconName = 'info-circle'
+			break
+		case 'info':
+			iconName = 'info-circle'
+			break
+		case 'error':
+			iconName = 'close-circle'
+			break
+		case 'warning':
+			iconName = 'error-circle'
+			break
+		case 'success':
+			iconName = 'checkmark-circle'
+			break
+		default:
+			iconName = 'checkmark-circle'
+	}
+	// 是否是实体类型,加上-fill,在icon组件库中,实体的类名是后面加-fill的
+	if (fill) iconName += '-fill'
+	return iconName
+}
+
+/**
+ * @description 数字格式化
+ * @param {number|string} number 要格式化的数字
+ * @param {number} decimals 保留几位小数
+ * @param {string} decimalPoint 小数点符号
+ * @param {string} thousandsSeparator 千分位符号
+ * @returns {string} 格式化后的数字
+ */
+function priceFormat(number, decimals = 0, decimalPoint = '.', thousandsSeparator = ',') {
+	number = (`${number}`).replace(/[^0-9+-Ee.]/g, '')
+	const n = !isFinite(+number) ? 0 : +number
+	const prec = !isFinite(+decimals) ? 0 : Math.abs(decimals)
+	const sep = (typeof thousandsSeparator === 'undefined') ? ',' : thousandsSeparator
+	const dec = (typeof decimalPoint === 'undefined') ? '.' : decimalPoint
+	let s = ''
+
+	s = (prec ? round(n, prec) + '' : `${Math.round(n)}`).split('.')
+	const re = /(-?\d+)(\d{3})/
+	while (re.test(s[0])) {
+		s[0] = s[0].replace(re, `$1${sep}$2`)
+	}
+
+	if ((s[1] || '').length < prec) {
+		s[1] = s[1] || ''
+		s[1] += new Array(prec - s[1].length + 1).join('0')
+	}
+	return s.join(dec)
+}
+
+/**
+ * @description 获取duration值
+ * 如果带有ms或者s直接返回,如果大于一定值,认为是ms单位,小于一定值,认为是s单位
+ * 比如以30位阈值,那么300大于30,可以理解为用户想要的是300ms,而不是想花300s去执行一个动画
+ * @param {String|number} value 比如: "1s"|"100ms"|1|100
+ * @param {boolean} unit  提示: 如果是false 默认返回number
+ * @return {string|number}
+ */
+function getDuration(value, unit = true) {
+	const valueNum = parseInt(value)
+	if (unit) {
+		if (/s$/.test(value)) return value
+		return value > 30 ? `${value}ms` : `${value}s`
+	}
+	if (/ms$/.test(value)) return valueNum
+	if (/s$/.test(value)) return valueNum > 30 ? valueNum : valueNum * 1000
+	return valueNum
+}
+
+/**
+ * @description 日期的月或日补零操作
+ * @param {String} value 需要补零的值
+ */
+function padZero(value) {
+	return `00${value}`.slice(-2)
+}
+
+/**
+ * @description 在u-form的子组件内容发生变化,或者失去焦点时,尝试通知u-form执行校验方法
+ * @param {*} instance
+ * @param {*} event
+ */
+function formValidate(instance, event) {
+	const formItem = uni.$u.$parent.call(instance, 'u-form-item')
+	const form = uni.$u.$parent.call(instance, 'u-form')
+	// 如果发生变化的input或者textarea等,其父组件中有u-form-item或者u-form等,就执行form的validate方法
+	// 同时将form-item的pros传递给form,让其进行精确对象验证
+	if (formItem && form) {
+		form.validateField(formItem.prop, () => {}, event)
+	}
+}
+
+/**
+ * @description 获取某个对象下的属性,用于通过类似'a.b.c'的形式去获取一个对象的的属性的形式
+ * @param {object} obj 对象
+ * @param {string} key 需要获取的属性字段
+ * @returns {*}
+ */
+function getProperty(obj, key) {
+	if (!obj) {
+		return
+	}
+	if (typeof key !== 'string' || key === '') {
+		return ''
+	}
+	if (key.indexOf('.') !== -1) {
+		const keys = key.split('.')
+		let firstObj = obj[keys[0]] || {}
+
+		for (let i = 1; i < keys.length; i++) {
+			if (firstObj) {
+				firstObj = firstObj[keys[i]]
+			}
+		}
+		return firstObj
+	}
+	return obj[key]
+}
+
+/**
+ * @description 设置对象的属性值,如果'a.b.c'的形式进行设置
+ * @param {object} obj 对象
+ * @param {string} key 需要设置的属性
+ * @param {string} value 设置的值
+ */
+function setProperty(obj, key, value) {
+	if (!obj) {
+		return
+	}
+	// 递归赋值
+	const inFn = function(_obj, keys, v) {
+		// 最后一个属性key
+		if (keys.length === 1) {
+			_obj[keys[0]] = v
+			return
+		}
+		// 0~length-1个key
+		while (keys.length > 1) {
+			const k = keys[0]
+			if (!_obj[k] || (typeof _obj[k] !== 'object')) {
+				_obj[k] = {}
+			}
+			const key = keys.shift()
+			// 自调用判断是否存在属性,不存在则自动创建对象
+			inFn(_obj[k], keys, v)
+		}
+	}
+
+	if (typeof key !== 'string' || key === '') {
+
+	} else if (key.indexOf('.') !== -1) { // 支持多层级赋值操作
+		const keys = key.split('.')
+		inFn(obj, keys, value)
+	} else {
+		obj[key] = value
+	}
+}
+
+/**
+ * @description 获取当前页面路径
+ */
+function page() {
+	const pages = getCurrentPages()
+	// 某些特殊情况下(比如页面进行redirectTo时的一些时机),pages可能为空数组
+	return `/${pages[pages.length - 1]?.route ?? ''}`
+}
+
+/**
+ * @description 获取当前路由栈实例数组
+ */
+function pages() {
+	const pages = getCurrentPages()
+	return pages
+}
+
+/**
+ * 获取页面历史栈指定层实例
+ * @param back {number} [0] - 0或者负数,表示获取历史栈的哪一层,0表示获取当前页面实例,-1 表示获取上一个页面实例。默认0。
+ */
+function getHistoryPage(back = 0) {
+	const pages = getCurrentPages()
+	const len = pages.length
+	return pages[len - 1 + back]
+}
+
+/**
+ * @description 修改uView内置属性值
+ * @param {object} props 修改内置props属性
+ * @param {object} config 修改内置config属性
+ * @param {object} color 修改内置color属性
+ * @param {object} zIndex 修改内置zIndex属性
+ */
+function setConfig({
+	props = {},
+	config = {},
+	color = {},
+	zIndex = {}
+}) {
+	const {
+		deepMerge,
+	} = uni.$u
+	uni.$u.config = deepMerge(uni.$u.config, config)
+	uni.$u.props = deepMerge(uni.$u.props, props)
+	uni.$u.color = deepMerge(uni.$u.color, color)
+	uni.$u.zIndex = deepMerge(uni.$u.zIndex, zIndex)
+}
+
+export default {
+	range,
+	getPx,
+	sleep,
+	os,
+	sys,
+	random,
+	guid,
+	$parent,
+	addStyle,
+	addUnit,
+	deepClone,
+	deepMerge,
+	error,
+	randomArray,
+	timeFormat,
+	timeFrom,
+	trim,
+	queryParams,
+	toast,
+	type2icon,
+	priceFormat,
+	getDuration,
+	padZero,
+	formValidate,
+	getProperty,
+	setProperty,
+	page,
+	pages,
+	getHistoryPage,
+	setConfig
+}

+ 1 - 1
uni_modules/uview-ui/libs/function/test.js

@@ -9,7 +9,7 @@ function email(value) {
  * 验证手机格式
  */
 function mobile(value) {
-    return /^1[23456789]\d{9}$/.test(value)
+    return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
 }
 
 /**

+ 107 - 107
uni_modules/uview-ui/libs/util/route.js

@@ -4,121 +4,121 @@
  */
 
 class Router {
-    constructor() {
-        // 原始属性定义
-        this.config = {
-            type: 'navigateTo',
-            url: '',
-            delta: 1, // navigateBack页面后退时,回退的层数
-            params: {}, // 传递的参数
-            animationType: 'pop-in', // 窗口动画,只在APP有效
-            animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
-            intercept: false // 是否需要拦截
-        }
-        // 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文
-        // 这里在构造函数中进行this绑定
-        this.route = this.route.bind(this)
-    }
+	constructor() {
+		// 原始属性定义
+		this.config = {
+			type: 'navigateTo',
+			url: '',
+			delta: 1, // navigateBack页面后退时,回退的层数
+			params: {}, // 传递的参数
+			animationType: 'pop-in', // 窗口动画,只在APP有效
+			animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
+			intercept: false // 是否需要拦截
+		}
+		// 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文
+		// 这里在构造函数中进行this绑定
+		this.route = this.route.bind(this)
+	}
 
-    // 判断url前面是否有"/",如果没有则加上,否则无法跳转
-    addRootPath(url) {
-        return url[0] === '/' ? url : `/${url}`
-    }
+	// 判断url前面是否有"/",如果没有则加上,否则无法跳转
+	addRootPath(url) {
+		return url[0] === '/' ? url : `/${url}`
+	}
 
-    // 整合路由参数
-    mixinParam(url, params) {
-        url = url && this.addRootPath(url)
+	// 整合路由参数
+	mixinParam(url, params) {
+		url = url && this.addRootPath(url)
 
-        // 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
-        // 如果有url中有get参数,转换后无需带上"?"
-        let query = ''
-        if (/.*\/.*\?.*=.*/.test(url)) {
-            // object对象转为get类型的参数
-            query = uni.$u.queryParams(params, false)
-            // 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
-            return url += `&${query}`
-        }
-        // 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号
-        query = uni.$u.queryParams(params)
-        return url += query
-    }
+		// 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
+		// 如果有url中有get参数,转换后无需带上"?"
+		let query = ''
+		if (/.*\/.*\?.*=.*/.test(url)) {
+			// object对象转为get类型的参数
+			query = uni.$u.queryParams(params, false)
+			// 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
+			return url += `&${query}`
+		}
+		// 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号
+		query = uni.$u.queryParams(params)
+		return url += query
+	}
 
-    // 对外的方法名称
-    async route(options = {}, params = {}) {
-        // 合并用户的配置和内部的默认配置
-        let mergeConfig = {}
+	// 对外的方法名称
+	async route(options = {}, params = {}) {
+		// 合并用户的配置和内部的默认配置
+		let mergeConfig = {}
 
-        if (typeof options === 'string') {
-            // 如果options为字符串,则为route(url, params)的形式
-            mergeConfig.url = this.mixinParam(options, params)
-            mergeConfig.type = 'navigateTo'
-        } else {
-            mergeConfig = uni.$u.deepClone(options, this.config)
-            // 否则正常使用mergeConfig中的url和params进行拼接
-            mergeConfig.url = this.mixinParam(options.url, options.params)
-        }
+		if (typeof options === 'string') {
+			// 如果options为字符串,则为route(url, params)的形式
+			mergeConfig.url = this.mixinParam(options, params)
+			mergeConfig.type = 'navigateTo'
+		} else {
+			mergeConfig = uni.$u.deepMerge(this.config, options)
+			// 否则正常使用mergeConfig中的url和params进行拼接
+			mergeConfig.url = this.mixinParam(options.url, options.params)
+		}
 
-        // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题
-        if (mergeConfig.url === uni.$u.page()) return
+		// 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题
+		if (mergeConfig.url === uni.$u.page()) return
 
-        if (params.intercept) {
-            this.config.intercept = params.intercept
-        }
-        // params参数也带给拦截器
-        mergeConfig.params = params
-        // 合并内外部参数
-        mergeConfig = uni.$u.deepMerge(this.config, mergeConfig)
-        // 判断用户是否定义了拦截器
-        if (typeof uni.$u.routeIntercept === 'function') {
-            // 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
-            const isNext = await new Promise((resolve, reject) => {
-                uni.$u.routeIntercept(mergeConfig, resolve)
-            })
-            // 如果isNext为true,则执行路由跳转
-            isNext && this.openPage(mergeConfig)
-        } else {
-            this.openPage(mergeConfig)
-        }
-    }
+		if (params.intercept) {
+			this.config.intercept = params.intercept
+		}
+		// params参数也带给拦截器
+		mergeConfig.params = params
+		// 合并内外部参数
+		mergeConfig = uni.$u.deepMerge(this.config, mergeConfig)
+		// 判断用户是否定义了拦截器
+		if (typeof uni.$u.routeIntercept === 'function') {
+			// 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
+			const isNext = await new Promise((resolve, reject) => {
+				uni.$u.routeIntercept(mergeConfig, resolve)
+			})
+			// 如果isNext为true,则执行路由跳转
+			isNext && this.openPage(mergeConfig)
+		} else {
+			this.openPage(mergeConfig)
+		}
+	}
 
-    // 执行路由跳转
-    openPage(config) {
-        // 解构参数
-        const {
-            url,
-            type,
-            delta,
-            animationType,
-            animationDuration
-        } = config
-        if (config.type == 'navigateTo' || config.type == 'to') {
-            uni.navigateTo({
-                url,
-                animationType,
-                animationDuration
-            })
-        }
-        if (config.type == 'redirectTo' || config.type == 'redirect') {
-            uni.redirectTo({
-                url
-            })
-        }
-        if (config.type == 'switchTab' || config.type == 'tab') {
-            uni.switchTab({
-                url
-            })
-        }
-        if (config.type == 'reLaunch' || config.type == 'launch') {
-            uni.reLaunch({
-                url
-            })
-        }
-        if (config.type == 'navigateBack' || config.type == 'back') {
-            uni.navigateBack({
-                delta
-            })
-        }
-    }
+	// 执行路由跳转
+	openPage(config) {
+		// 解构参数
+		const {
+			url,
+			type,
+			delta,
+			animationType,
+			animationDuration
+		} = config
+		if (config.type == 'navigateTo' || config.type == 'to') {
+			uni.navigateTo({
+				url,
+				animationType,
+				animationDuration
+			})
+		}
+		if (config.type == 'redirectTo' || config.type == 'redirect') {
+			uni.redirectTo({
+				url
+			})
+		}
+		if (config.type == 'switchTab' || config.type == 'tab') {
+			uni.switchTab({
+				url
+			})
+		}
+		if (config.type == 'reLaunch' || config.type == 'launch') {
+			uni.reLaunch({
+				url
+			})
+		}
+		if (config.type == 'navigateBack' || config.type == 'back') {
+			uni.navigateBack({
+				delta
+			})
+		}
+	}
 }
 
 export default (new Router()).route

+ 7 - 9
uni_modules/uview-ui/package.json

@@ -2,7 +2,7 @@
 	"id": "uview-ui",
 	"name": "uview-ui",
 	"displayName": "uView2.0重磅发布,利剑出鞘,一统江湖",
-	"version": "2.0.33",
+	"version": "2.0.38",
 	"description": "uView UI已完美兼容nvue,全面的组件和便捷的工具会让您信手拈来,如鱼得水",
 	"keywords": [
         "uview",
@@ -17,12 +17,8 @@
 	"engines": {
 		"HBuilderX": "^3.1.0"
 	},
-	"dcloudext": {
-		"category": [
-			"前端组件",
-			"通用组件"
-		],
-		"sale": {
+    "dcloudext": {
+        "sale": {
 			"regular": {
 				"price": "0.00"
 			},
@@ -38,7 +34,8 @@
 			"data": "无",
 			"permissions": "无"
 		},
-		"npmurl": "https://www.npmjs.com/package/uview-ui"
+        "npmurl": "https://www.npmjs.com/package/uview-ui",
+        "type": "component-vue"
 	},
 	"uni_modules": {
 		"dependencies": [],
@@ -46,7 +43,8 @@
 		"platforms": {
 			"cloud": {
 				"tcb": "y",
-				"aliyun": "y"
+                "aliyun": "y",
+                "alipay": "n"
 			},
 			"client": {
 				"Vue": {

+ 15 - 0
utils/tools.js

@@ -0,0 +1,15 @@
+export function formatSeconds(seconds, type) {
+	let hours = Math.floor(seconds / 3600);
+	let minutes = Math.floor((seconds - (hours * 3600)) / 60);
+	let secs = seconds - (hours * 3600 + minutes * 60);
+
+	hours = hours == 0 ? '' : hours.toString().padStart(2, '0');
+	minutes = hours == 0 && minutes == 0 ? '' : minutes.toString().padStart(2, '0');
+	secs = hours == 0 && minutes == 0&&secs == 0 ? '' : secs.toString().padStart(2, '0');
+	// 汉字1,:隔开0
+	if(type == 1) {
+		return `${hours&&hours+'时'}${minutes&&minutes+'分'}${secs&&secs+'秒'}`
+	} else {
+		return `${hours&&hours+':'}${minutes&&minutes+':'}${secs}`
+	}
+};