xiaoxin 2 gadi atpakaļ
vecāks
revīzija
73956668ff
100 mainītis faili ar 9459 papildinājumiem un 40 dzēšanām
  1. 29 1
      pages.json
  2. 392 0
      pages/affirmOrder/affirmOrder.vue
  3. 16 2
      pages/common/common.vue
  4. 548 10
      pages/detail/detail.vue
  5. 218 0
      pages/detailInfo/detailInfo.vue
  6. 7 7
      pages/index/index.vue
  7. 48 9
      pages/my/my.vue
  8. 398 0
      pages/orderDetail/orderDetail.vue
  9. 7 1
      pages/orderManage/orderManage.vue
  10. 227 0
      pages/pay/pay.vue
  11. BIN
      static/index/add.png
  12. BIN
      static/index/address.png
  13. BIN
      static/index/air.png
  14. BIN
      static/index/bed.png
  15. BIN
      static/index/close.png
  16. BIN
      static/index/info.png
  17. BIN
      static/index/left.png
  18. BIN
      static/index/lock.png
  19. BIN
      static/index/minus.png
  20. BIN
      static/index/people.png
  21. BIN
      static/index/phone2.png
  22. BIN
      static/index/place.png
  23. BIN
      static/index/right2.png
  24. BIN
      static/index/smoke.png
  25. BIN
      static/index/time.png
  26. BIN
      static/index/wifi.png
  27. BIN
      static/index/window.png
  28. BIN
      static/index/wxPay.png
  29. BIN
      static/my/map.png
  30. BIN
      static/my/phone.png
  31. BIN
      static/my/right2.png
  32. 68 0
      uni_modules/uni-popup/changelog.md
  33. 45 0
      uni_modules/uni-popup/components/uni-popup-dialog/keypress.js
  34. 275 0
      uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue
  35. 143 0
      uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue
  36. 187 0
      uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue
  37. 7 0
      uni_modules/uni-popup/components/uni-popup/i18n/en.json
  38. 8 0
      uni_modules/uni-popup/components/uni-popup/i18n/index.js
  39. 7 0
      uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json
  40. 7 0
      uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json
  41. 45 0
      uni_modules/uni-popup/components/uni-popup/keypress.js
  42. 26 0
      uni_modules/uni-popup/components/uni-popup/popup.js
  43. 473 0
      uni_modules/uni-popup/components/uni-popup/uni-popup.vue
  44. 87 0
      uni_modules/uni-popup/package.json
  45. 17 0
      uni_modules/uni-popup/readme.md
  46. 22 0
      uni_modules/uni-transition/changelog.md
  47. 131 0
      uni_modules/uni-transition/components/uni-transition/createAnimation.js
  48. 286 0
      uni_modules/uni-transition/components/uni-transition/uni-transition.vue
  49. 84 0
      uni_modules/uni-transition/package.json
  50. 11 0
      uni_modules/uni-transition/readme.md
  51. 15 0
      uni_modules/uv-button/changelog.md
  52. 46 0
      uni_modules/uv-button/components/uv-button/nvue.scss
  53. 158 0
      uni_modules/uv-button/components/uv-button/props.js
  54. 510 0
      uni_modules/uv-button/components/uv-button/uv-button.vue
  55. 94 0
      uni_modules/uv-button/components/uv-button/vue.scss
  56. 89 0
      uni_modules/uv-button/package.json
  57. 19 0
      uni_modules/uv-button/readme.md
  58. 14 0
      uni_modules/uv-calendar/changelog.md
  59. 546 0
      uni_modules/uv-calendar/components/uv-calendar/calendar.js
  60. 104 0
      uni_modules/uv-calendar/components/uv-calendar/header.vue
  61. 616 0
      uni_modules/uv-calendar/components/uv-calendar/month.vue
  62. 145 0
      uni_modules/uv-calendar/components/uv-calendar/props.js
  63. 390 0
      uni_modules/uv-calendar/components/uv-calendar/uv-calendar.vue
  64. 89 0
      uni_modules/uv-calendar/package.json
  65. 11 0
      uni_modules/uv-calendar/readme.md
  66. 20 0
      uni_modules/uv-datetime-picker/changelog.md
  67. 120 0
      uni_modules/uv-datetime-picker/components/uv-datetime-picker/props.js
  68. 356 0
      uni_modules/uv-datetime-picker/components/uv-datetime-picker/uv-datetime-picker.vue
  69. 88 0
      uni_modules/uv-datetime-picker/package.json
  70. 19 0
      uni_modules/uv-datetime-picker/readme.md
  71. 2 0
      uni_modules/uv-icon/changelog.md
  72. 5 5
      uni_modules/uv-icon/components/uv-icon/icons.js
  73. 2 2
      uni_modules/uv-icon/package.json
  74. 7 3
      uni_modules/uv-icon/readme.md
  75. 7 0
      uni_modules/uv-loading-icon/changelog.md
  76. 60 0
      uni_modules/uv-loading-icon/components/uv-loading-icon/props.js
  77. 346 0
      uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue
  78. 87 0
      uni_modules/uv-loading-icon/package.json
  79. 11 0
      uni_modules/uv-loading-icon/readme.md
  80. 9 0
      uni_modules/uv-overlay/changelog.md
  81. 25 0
      uni_modules/uv-overlay/components/uv-overlay/props.js
  82. 85 0
      uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue
  83. 88 0
      uni_modules/uv-overlay/package.json
  84. 11 0
      uni_modules/uv-overlay/readme.md
  85. 17 0
      uni_modules/uv-picker/changelog.md
  86. 90 0
      uni_modules/uv-picker/components/uv-picker/props.js
  87. 312 0
      uni_modules/uv-picker/components/uv-picker/uv-picker.vue
  88. 39 0
      uni_modules/uv-picker/components/uv-toolbar/props.js
  89. 109 0
      uni_modules/uv-picker/components/uv-toolbar/uv-toolbar.vue
  90. 89 0
      uni_modules/uv-picker/package.json
  91. 11 0
      uni_modules/uv-picker/readme.md
  92. 9 0
      uni_modules/uv-popup/changelog.md
  93. 45 0
      uni_modules/uv-popup/components/uv-popup/keypress.js
  94. 80 0
      uni_modules/uv-popup/components/uv-popup/props.js
  95. 527 0
      uni_modules/uv-popup/components/uv-popup/uv-popup.vue
  96. 92 0
      uni_modules/uv-popup/package.json
  97. 11 0
      uni_modules/uv-popup/readme.md
  98. 7 0
      uni_modules/uv-status-bar/changelog.md
  99. 8 0
      uni_modules/uv-status-bar/components/uv-status-bar/props.js
  100. 0 0
      uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue

+ 29 - 1
pages.json

@@ -9,7 +9,8 @@
 			"path": "pages/my/my",
 			"style": {
 				"navigationBarTitleText": "我的",
-				"enablePullDownRefresh": false
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom"
 			}
 		}, {
 			"path": "pages/search/search",
@@ -53,6 +54,33 @@
 				"navigationBarTitleText": "民宿详情",
 				"enablePullDownRefresh": false
 			}
+		}, {
+			"path": "pages/detailInfo/detailInfo",
+			"style": {
+				"navigationBarTitleText": "设施详情",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom"
+			}
+		}, {
+			"path": "pages/pay/pay",
+			"style": {
+				"navigationBarTitleText": "支付订单",
+				"enablePullDownRefresh": false
+			}
+		}, {
+			"path": "pages/affirmOrder/affirmOrder",
+			"style": {
+				"navigationBarTitleText": "民宿名称",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom"
+			}
+		}, {
+			"path": "pages/orderDetail/orderDetail",
+			"style": {
+				"navigationBarTitleText": "订单详情",
+				"enablePullDownRefresh": false,
+				"navigationStyle": "custom"
+			}
 		}
 	],
 	"tabBar": {

+ 392 - 0
pages/affirmOrder/affirmOrder.vue

@@ -0,0 +1,392 @@
+<template>
+	<view class="container">
+		<!-- 导航栏图片区域 -->
+		<view class="header">
+			<img src="../../static/my/headerImg.png" />
+
+			<!-- 标题区域 -->
+			<view class="header_title">民宿名称</view>
+			<!-- 返回图标区域 -->
+			<img class="header_icon" src="../../static/index/left.png" @click="handleBack" />
+		</view>
+		<!-- 房间信息区域 -->
+		<view class="info">
+			<view class="info_time">
+				{{ info.startTimeMonth }}月{{ info.startTimeMonth }}日
+				<text class="gap">星期{{ info.startTimeWeek }}</text>
+				<view class="time_line"></view>
+				<view class="time_num">{{ info.nightNum }}晚</view>
+				<view class="time_line"></view>
+				<view class="gap">{{ info.endTimeMonth }}月{{ info.endTimeDay }}日</view>
+				<text>星期{{ info.endTimeWeek }}</text>
+			</view>
+			<view class="info_msg">大床房</view>
+			<view class="info_type">
+				<view class="type_item">包吃住型</view>
+				<view class="type_item">包吃住型</view>
+				<view class="type_item">包吃住型</view>
+			</view>
+			<view class="info_tag">
+				<view class="tag_item">16-20㎡</view>
+				<view class="tag_item">双人床</view>
+				<view class="tag_item">窗户位于走廊/窗户较小</view>
+			</view>
+		</view>
+
+		<!-- 入住信息区域 -->
+		<view class="msg">
+			<view class="msg_title">入住信息</view>
+			<view class="msg_box">
+				<view class="box_key">房间数量</view>
+				<view class="box_value">{{ roomCount }}间</view>
+				<view class="box_icon">
+					<img class="img" src="../../static/index/add.png" @click="handleAdd" />
+					<img class="img2" src="../../static/index/minus.png" @click="handleMinus" />
+				</view>
+			</view>
+			<view class="msg_box">
+				<view class="box_key">住客姓名</view>
+				<view class="box_value">
+					<input type="text" placeholder="请输入住客姓名" v-model="clientName" />
+				</view>
+				<view class="box_icon" @click="handleSelectClient">
+					<img class="img2" src="../../static/index/people.png" />
+				</view>
+			</view>
+			<view class="msg_box">
+				<view class="box_key">联系电话</view>
+				<view class="box_value">
+					<input type="number" maxlength="11" placeholder="请输入联系电话" v-model="clientPhone" />
+				</view>
+			</view>
+			<view class="msg_box" @click="handleSelectTime">
+				<view class="box_key">预计到店</view>
+				<view class="box_value" :class="{ color: !arriveTime }">{{ arriveTime ? arriveTime : '请选择预计到店时间' }}</view>
+				<view class="box_icon">
+					<img class="img3" src="../../static/index/right2.png" />
+				</view>
+			</view>
+
+			<uv-datetime-picker
+				ref="datetimePicker"
+				v-model="timeValue"
+				mode="datetime"
+				:closeOnClickOverlay="false"
+				confirmColor="#096562"
+				:formatter="formatter"
+				@confirm="confirm"
+			></uv-datetime-picker>
+		</view>
+
+		<view class="foot">
+			<view class="foot_left">
+				<text>¥</text>
+				{{ info.price }}
+			</view>
+			<view class="foot_right" @click="goPagePay">提交订单</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			info: {},
+			// 预定房间数量
+			roomCount: 1,
+			// 住客姓名
+			clientName: '张三',
+			// 联系电话
+			clientPhone: '13659865896',
+			// 预计到店时间
+			arriveTime: '',
+			timeValue: Number(new Date())
+		}
+	},
+	onLoad(options) {
+		this.info = JSON.parse(options.info)
+		// console.log(this.info)
+		uni.$on('change', this.change)
+	},
+	methods: {
+		change(e) {
+			this.clientName = e.name
+		},
+		handleSelectClient() {
+			uni.navigateTo({
+				url: '/pages/common/common?type=1'
+			})
+		},
+		confirm(e) {
+			// console.log(e.value)
+			let date = new Date(e.value)
+			let month = date.getMonth() + 1
+			let day = date.getDate()
+			let H = date.getHours()
+			let M = date.getMinutes()
+			this.arriveTime = `${month}月${day}日${H}:${M}分之前`
+		},
+		formatter(type, value) {
+			if (type === 'year') {
+				return `${value}年`
+			}
+			if (type === 'month') {
+				return `${value}月`
+			}
+			if (type === 'day') {
+				return `${value}日`
+			}
+			return value
+		},
+		handleSelectTime() {
+			this.$refs.datetimePicker.open()
+		},
+		handleAdd() {
+			this.roomCount++
+		},
+		handleMinus() {
+			if (this.roomCount > 1) {
+				this.roomCount--
+			} else {
+				uni.showToast({
+					title: '至少需要预定1间',
+					icon: 'none'
+				})
+			}
+		},
+		// 提交订单按钮回调
+		goPagePay() {
+			let info = JSON.stringify(this.info)
+			uni.navigateTo({
+				url: `/pages/pay/pay?info=${info}`
+			})
+		},
+		handleBack() {
+			uni.navigateBack(1)
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+	position: relative;
+	display: flex;
+	flex-direction: column;
+	min-height: 100vh;
+	background-color: #ebeced;
+
+	.header {
+		position: relative;
+		height: 125rpx;
+		overflow: hidden;
+
+		img {
+			width: 100%;
+		}
+
+		.header_title {
+			position: absolute;
+			top: 65rpx;
+			left: 308rpx;
+			color: #fff;
+			font-size: 28rpx;
+		}
+
+		.header_icon {
+			position: absolute;
+			top: 56rpx;
+			left: 5rpx;
+			width: 47rpx;
+			height: 47rpx;
+		}
+	}
+
+	.info {
+		display: flex;
+		flex-direction: column;
+		box-sizing: border-box;
+		padding: 0 30rpx;
+		margin: 0 auto;
+		margin-top: 18rpx;
+		width: 710rpx;
+		border-radius: 15rpx;
+		background-color: #fff;
+
+		.info_time {
+			display: flex;
+			align-items: center;
+			margin-top: 20rpx;
+			font-size: 32rpx;
+			font-weight: bold;
+
+			.time_line {
+				width: 17rpx;
+				height: 1rpx;
+				background-color: #096562;
+			}
+
+			.time_num {
+				box-sizing: border-box;
+				padding: 0 15rpx;
+				height: 46rpx;
+				line-height: 46rpx;
+				font-size: 24rpx;
+				font-weight: 400;
+				border-radius: 66rpx;
+				border: 1rpx solid #096562;
+				background-color: #f0f2f5;
+			}
+
+			.gap {
+				margin: 0 10rpx;
+			}
+
+			text {
+				font-size: 24rpx;
+				font-weight: 400;
+			}
+		}
+
+		.info_msg {
+			margin-top: 15rpx;
+			font-size: 28rpx;
+			font-weight: bold;
+		}
+		.info_type {
+			display: flex;
+			flex-wrap: wrap;
+			margin-top: 15rpx;
+
+			.type_item {
+				box-sizing: border-box;
+				padding: 0 15rpx;
+				margin-right: 20rpx;
+				height: 41rpx;
+				line-height: 41rpx;
+				font-size: 24rpx;
+				color: #fff;
+				border-radius: 34rpx;
+				background-color: #096562;
+			}
+		}
+
+		.info_tag {
+			display: flex;
+			flex-wrap: wrap;
+			margin: 18rpx 0 30rpx;
+			color: #808080;
+			font-size: 24rpx;
+
+			.tag_item {
+				margin-right: 20rpx;
+			}
+		}
+	}
+
+	.msg {
+		box-sizing: border-box;
+		padding-left: 26rpx;
+		margin: auto;
+		margin-top: 20rpx;
+		width: 710rpx;
+		border-radius: 15rpx;
+		background-color: #fff;
+
+		.msg_title {
+			height: 87rpx;
+			line-height: 87rpx;
+			font-size: 28rpx;
+			font-weight: bold;
+			border-bottom: 1rpx solid #e6e6e6;
+		}
+
+		.msg_box {
+			display: flex;
+			align-items: center;
+			height: 97rpx;
+			font-size: 28rpx;
+			border-bottom: 1rpx solid #e6e6e6;
+
+			.box_key {
+				width: 120rpx;
+				color: #808080;
+			}
+
+			.box_value {
+				margin-left: 30rpx;
+				flex: 1;
+
+				input {
+					box-sizing: border-box;
+					padding-right: 30rpx;
+					width: 100%;
+				}
+			}
+
+			.color {
+				color: #808080;
+			}
+
+			.box_icon {
+				display: flex;
+				align-items: center;
+
+				.img {
+					width: 47rpx;
+					height: 47rpx;
+				}
+
+				.img2 {
+					margin: 0 30rpx;
+					width: 37rpx;
+					height: 37rpx;
+				}
+
+				.img3 {
+					margin: 0 30rpx;
+					width: 40rpx;
+					height: 40rpx;
+				}
+			}
+		}
+	}
+
+	.foot {
+		position: absolute;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		padding: 0 20rpx;
+		display: flex;
+		justify-content: space-between;
+		align-items: center;
+		height: 126rpx;
+		background-color: #fff;
+
+		.foot_left {
+			margin-top: -10rpx;
+			font-size: 40rpx;
+			color: #ff5733;
+
+			text {
+				font-size: 24rpx;
+			}
+		}
+
+		.foot_right {
+			margin-top: -10rpx;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			width: 238rpx;
+			height: 80rpx;
+			color: #fff;
+			font-size: 32rpx;
+			border-radius: 64rpx;
+			background-color: #096562;
+		}
+	}
+}
+</style>

+ 16 - 2
pages/common/common.vue

@@ -19,7 +19,7 @@
 						</view>
 					</template>
 
-					<view class="list_item">
+					<view class="list_item" @click="handleClick(item)">
 						<view class="item_box">
 							<view class="box_key">姓名</view>
 							<view class="box_value">{{ item.name }}</view>
@@ -77,10 +77,24 @@ export default {
 					identity: '360730199603164895',
 					phone: '13677985689'
 				}
-			]
+			],
+			type: ''
+		}
+	},
+	onLoad(options) {
+		if (options.type) {
+			this.type = options.type
 		}
 	},
 	methods: {
+		handleClick(item) {
+			if (this.type) {
+				uni.$emit('change', {
+					name: item.name
+				})
+				uni.navigateBack(1)
+			}
+		},
 		// 右侧选项内容编辑按钮回调
 		handleClickEdit(item) {
 			// console.log(item)

+ 548 - 10
pages/detail/detail.vue

@@ -1,8 +1,8 @@
 <template>
-	<view class="container">
+	<view class="container" :style="'overflow:' + (showPage ? 'hidden' : 'visible')">
 		<!-- 顶部民宿图片区域 -->
 		<view class="banner">
-			<img class="img" src="../../static/index/banner.png" />
+			<img class="img" :src="info.imgUrl" />
 
 			<view class="slogan">
 				<img src="../../static/index/slogan.png" />
@@ -11,11 +11,11 @@
 
 		<!-- 民宿详细信息区域 -->
 		<view class="detail">
-			<view class="detail_name">民宿名称</view>
+			<view class="detail_name">{{ info.hotelName }}</view>
 
 			<view class="detail_info">
 				<view class="info_left">2021年装修|2023年开业</view>
-				<view class="info_right">
+				<view class="info_right" @click="goPageDetailInfo">
 					设施/详情
 					<img src="../../static/index/right.png" />
 				</view>
@@ -48,13 +48,129 @@
 				</view>
 			</view>
 
-			<view class="distance">距离我520km</view>
+			<view class="distance">距离我{{ info.distance }}km</view>
 		</view>
 
 		<!-- 房型信息区域 -->
 		<view class="body">
-			<view class="body_header">123</view>
-			<view class="body_content">456</view>
+			<!-- 选择日期区域 -->
+			<view class="body_header" @click="handleOpen">
+				<view class="header_start">
+					<view class="header_top">周{{ startTimeWeek }}入住</view>
+					<view class="header_bottom">{{ startTimeMonth }}月{{ startTimeDay }}日</view>
+				</view>
+				<view class="header_night">
+					<view class="night_line"></view>
+					<view class="night_box">{{ nightNum }}晚</view>
+					<view class="night_line"></view>
+				</view>
+				<view class="header_end">
+					<view class="header_top">周{{ endTimeWeek }}离店</view>
+					<view class="header_bottom">{{ endTimeMonth }}月{{ endTimeDay }}日</view>
+				</view>
+			</view>
+
+			<!-- 日历组件 -->
+			<uv-calendar
+				ref="calendar"
+				showLunar
+				color="#096562"
+				mode="range"
+				:defaultDate="defaultDateMultiple"
+				startText="住店"
+				endText="离店"
+				confirmDisabledText="请选择离店日期"
+				@confirm="handleConfirm"
+			></uv-calendar>
+
+			<!-- 房型列表区域 -->
+			<view class="body_content">
+				<!-- 每一个房型盒子区域 -->
+				<view class="body_box" v-for="item in list" :key="item.id" @click="handleLookDetail(item)">
+					<view class="box_left">
+						<img :src="item.imgUrl" />
+					</view>
+
+					<view class="box_center">
+						<view class="center_top">{{ item.name }}</view>
+						<view class="center_center">
+							<view class="center_item">大床</view>
+							<view class="center_item">无早</view>
+						</view>
+						<view class="center_bottom">
+							<img class="img" src="../../static/index/wifi.png" />
+							<img class="img" src="../../static/index/air.png" />
+							<img class="img" src="../../static/index/smoke.png" />
+							<img class="img" src="../../static/index/lock.png" />
+						</view>
+					</view>
+
+					<view class="box_right">
+						<view class="right_price">
+							<text>¥</text>
+							{{ item.price }}
+						</view>
+						<view class="right_btn" :class="{ inactive: !item.active }" @click.stop="goPageAffOrder(item)">订</view>
+					</view>
+				</view>
+
+				<!-- 点击房型弹窗区域 -->
+				<uv-popup ref="popup" bgColor="none" :safeAreaInsetBottom="false" :closeOnClickOverlay="false">
+					<view class="body_pop">
+						<!-- 轮播图区域 -->
+						<swiper indicator-dots circular indicator-color="#FFFFFF" indicator-active-color="#096562" class="pop_swiper">
+							<swiper-item class="pop_swiper_item">
+								<img class="img" src="../../static/index/banner.png" />
+							</swiper-item>
+							<swiper-item class="pop_swiper_item">
+								<img class="img" src="../../static/index/banner.png" />
+							</swiper-item>
+						</swiper>
+						<!-- 轮播图关闭图标区域 -->
+						<img class="pop_icon" src="../../static/index/close.png" @click="handleClosePop" />
+						<view class="pop_body">
+							<view class="body_title">{{ roomInfo.name }}</view>
+							<view class="body_tags">
+								<view class="tags_item">
+									<img class="img" src="../../static/index/bed.png" />
+									1张大床房
+								</view>
+								<view class="tags_item">
+									<img class="img" src="../../static/index/wifi.png" />
+									wifi免费
+								</view>
+								<view class="tags_item">
+									<img class="img" src="../../static/index/place.png" />
+									15-20㎡
+								</view>
+								<view class="tags_item">
+									<img class="img" src="../../static/index/window.png" />
+									窗户位于走廊/窗户较小
+								</view>
+							</view>
+							<view class="body_title2">费用明细</view>
+							<view class="body_detail">
+								<view>¥{{ roomInfo.price }}</view>
+								<view>
+									每间每晚
+									<text>¥{{ roomInfo.price }}</text>
+								</view>
+							</view>
+							<view class="body_bottom">
+								<view class="bottom_left">
+									<img class="img" src="../../static/index/phone2.png" />
+									联系商家
+								</view>
+								<view class="bottom_right">
+									<text>¥</text>
+									{{ roomInfo.price }}
+									<view class="btn" @click="goPageAffOrder(roomInfo)">预定</view>
+								</view>
+							</view>
+						</view>
+					</view>
+				</uv-popup>
+			</view>
 		</view>
 	</view>
 </template>
@@ -62,10 +178,149 @@
 <script>
 export default {
 	data() {
-		return {}
+		return {
+			// 滚动穿透控制
+			showPage: false,
+			// 房型数据数组
+			list: [
+				{
+					id: 1,
+					imgUrl: '../../static/index/banner.png',
+					name: '01户型',
+					price: '323.00',
+					active: true
+				},
+				{
+					id: 2,
+					imgUrl: '../../static/index/banner.png',
+					name: '02户型',
+					price: '283.00',
+					active: false
+				},
+				{
+					id: 3,
+					imgUrl: '../../static/index/banner.png',
+					name: '03户型',
+					price: '223.00',
+					active: true
+				}
+			],
+			// 住几晚
+			nightNum: 1,
+			// 入住时间-月
+			startTimeMonth: '',
+			// 入住时间-日
+			startTimeDay: '',
+			// 入住时间-星期
+			startTimeWeek: '',
+			// 离店时间-月
+			endTimeMonth: '',
+			// 离店时间-日
+			endTimeDay: '',
+			// 离店时间-星期
+			endTimeWeek: '',
+			// 日历默认选择日期
+			defaultDateMultiple: [],
+			// 酒店信息
+			info: {},
+			// 房间信息
+			roomInfo: {}
+		}
 	},
 	onLoad(options) {
 		// console.log(JSON.parse(options.info))
+		this.info = JSON.parse(options.info)
+		this.getTimes()
+	},
+	methods: {
+		// 点击弹窗关闭图标回调
+		handleClosePop() {
+			this.showPage = false
+			this.$refs.popup.close()
+		},
+		// 点击每一个户型回调
+		handleLookDetail(item) {
+			// console.log(item)
+			this.roomInfo = item
+			this.showPage = true
+			this.$refs.popup.open('bottom')
+		},
+		// 点击设施详情回调
+		goPageDetailInfo() {
+			uni.navigateTo({
+				url: '/pages/detailInfo/detailInfo'
+			})
+		},
+		// 点击 订 按钮回调
+		goPageAffOrder(item) {
+			if (item.active) {
+				this.$refs.popup.close()
+				this.showPage = false
+				let info = JSON.stringify({
+					nightNum: this.nightNum,
+					startTimeDay: this.startTimeDay,
+					startTimeMonth: this.startTimeMonth,
+					startTimeWeek: this.startTimeWeek,
+					endTimeDay: this.endTimeDay,
+					endTimeMonth: this.endTimeMonth,
+					endTimeWeek: this.endTimeWeek,
+					price: item.price
+				})
+				uni.navigateTo({
+					url: `/pages/affirmOrder/affirmOrder?info=${info}`
+				})
+			} else {
+				uni.showToast({
+					title: '该房间已售罄',
+					icon: 'none'
+				})
+			}
+		},
+		// 点击日期区域回调
+		handleOpen() {
+			this.$refs.calendar.open()
+		},
+		// 选择日历确定按钮回调
+		handleConfirm(e) {
+			// console.log(e)
+			this.startTimeWeek = this.getWeek(e[0])
+			this.endTimeWeek = this.getWeek(e[e.length - 1])
+			let temStart = e[0].split('-')
+			let temEnd = e[e.length - 1].split('-')
+
+			this.startTimeMonth = temStart[1]
+			this.startTimeDay = temStart[2]
+			this.endTimeMonth = temEnd[1]
+			this.endTimeDay = temEnd[2]
+			this.nightNum = e.length - 1
+		},
+		// 获取今明两天的日期,星期
+		getTimes() {
+			let weekList = ['日', '一', '二', '三', '四', '五', '六']
+			//今天的日期
+			let today = new Date()
+
+			this.startTimeWeek = weekList[today.getDay()]
+			this.startTimeMonth = (today.getMonth() + 1).toString().padStart(2, 0)
+			this.startTimeDay = today.getDate().toString().padStart(2, 0)
+
+			//明天的日期
+			let tomorrow = new Date()
+			tomorrow.setTime(tomorrow.getTime() + 24 * 60 * 60 * 1000)
+			this.endTimeWeek = weekList[tomorrow.getDay()]
+			this.endTimeMonth = (tomorrow.getMonth() + 1).toString().padStart(2, 0)
+			this.endTimeDay = tomorrow.getDate().toString().padStart(2, 0)
+			// 日历默认选择的日期
+			this.defaultDateMultiple = [`${today.getFullYear()}-${this.startTimeMonth}-${this.startTimeDay}`, `${tomorrow.getFullYear()}-${this.endTimeMonth}-${this.endTimeDay}`]
+		},
+
+		// 传入参数获取当前是星期几
+		getWeek(time) {
+			let date = new Date(time)
+			let week = date.getDay()
+			let weekList = ['日', '一', '二', '三', '四', '五', '六']
+			return weekList[week]
+		}
 	}
 }
 </script>
@@ -209,17 +464,300 @@ export default {
 		position: absolute;
 		top: 539rpx;
 		left: 20rpx;
+		padding-bottom: 30rpx;
 		width: 710rpx;
 		border-radius: 10rpx;
 		background-color: #fff;
 
 		.body_header {
+			display: flex;
+			justify-content: space-around;
+			box-sizing: border-box;
+			padding: 0 30rpx;
 			height: 150rpx;
-			border-radius: 10rpx 10rpx 0 0;
-			background-color: skyblue;
+
+			.header_start {
+				display: flex;
+				flex-direction: column;
+				justify-content: center;
+				align-items: center;
+				width: 213rpx;
+				height: 150rpx;
+
+				.header_top {
+					margin-bottom: 10rpx;
+					color: #999999;
+					font-size: 24rpx;
+				}
+
+				.header_bottom {
+					font-size: 34rpx;
+					font-weight: bold;
+				}
+			}
+
+			.header_night {
+				display: flex;
+				justify-content: center;
+				align-items: center;
+
+				.night_line {
+					width: 17rpx;
+					height: 1rpx;
+					background-color: #296de3;
+				}
+
+				.night_box {
+					display: flex;
+					align-items: center;
+					justify-content: center;
+					width: 81rpx;
+					height: 46rpx;
+					font-size: 24rpx;
+					border-radius: 66rpx;
+					border: 1rpx solid #296de3;
+				}
+			}
+
+			.header_end {
+				display: flex;
+				flex-direction: column;
+				justify-content: center;
+				align-items: center;
+				width: 213rpx;
+				height: 150rpx;
+
+				.header_top {
+					margin-bottom: 10rpx;
+					color: #999999;
+					font-size: 24rpx;
+				}
+
+				.header_bottom {
+					font-size: 34rpx;
+					font-weight: bold;
+				}
+			}
 		}
 
 		.body_content {
+			.body_box {
+				box-sizing: border-box;
+				padding: 0 30rpx;
+				margin-bottom: 30rpx;
+				display: flex;
+				height: 193rpx;
+				border-bottom: 1rpx solid #cccccc;
+
+				.box_left {
+					width: 213rpx;
+					height: 161rpx;
+					border-radius: 5rpx;
+
+					img {
+						width: 100%;
+						height: 100%;
+					}
+				}
+
+				.box_center {
+					margin-left: 16rpx;
+
+					.center_top {
+						font-size: 34rpx;
+						font-weight: bold;
+					}
+
+					.center_center {
+						display: flex;
+						flex-wrap: wrap;
+						margin-top: 10rpx;
+						color: #999999;
+						font-size: 24rpx;
+
+						.center_item {
+							margin-right: 15rpx;
+						}
+					}
+					.center_bottom {
+						margin-top: 25rpx;
+
+						.img {
+							margin-right: 15rpx;
+							width: 30rpx;
+							height: 30rpx;
+						}
+					}
+				}
+
+				.box_right {
+					display: flex;
+					flex-direction: column;
+					align-items: flex-end;
+					margin-left: auto;
+					margin-top: 20rpx;
+
+					.right_price {
+						color: #ff5733;
+						font-size: 42rpx;
+						text {
+							font-size: 24rpx;
+						}
+					}
+
+					.right_btn {
+						display: flex;
+						justify-content: center;
+						align-items: center;
+						margin-top: 10rpx;
+						width: 100rpx;
+						height: 66rpx;
+						color: #fff;
+						font-size: 36rpx;
+						border-radius: 11rpx;
+						background-color: #096562;
+					}
+
+					.inactive {
+						background-color: #cccccc;
+					}
+				}
+			}
+
+			.body_pop {
+				position: relative;
+				height: 955rpx;
+				border-radius: 22rpx 22rpx 0 0;
+				background-color: #fff;
+				overflow-y: auto;
+
+				.pop_swiper {
+					height: 422rpx;
+					border-radius: 22rpx 22rpx 0 0;
+
+					.pop_swiper_item {
+						width: 100%;
+						height: 100%;
+
+						.img {
+							width: 100%;
+							height: 100%;
+							border-radius: 22rpx 22rpx 0 0;
+						}
+					}
+				}
+
+				.pop_icon {
+					position: absolute;
+					top: 20rpx;
+					right: 30rpx;
+					width: 58rpx;
+					height: 58rpx;
+				}
+
+				.pop_body {
+					box-sizing: border-box;
+					padding: 0 20rpx 30rpx;
+
+					.body_title {
+						margin-top: 30rpx;
+						font-size: 32rpx;
+						font-weight: bold;
+					}
+
+					.body_tags {
+						display: flex;
+						flex-wrap: wrap;
+						margin-top: 20rpx;
+
+						.tags_item {
+							display: flex;
+							align-items: center;
+							margin-right: 76rpx;
+							margin-bottom: 20rpx;
+							color: #383838;
+							font-size: 24rpx;
+
+							.img {
+								margin-right: 5rpx;
+								width: 28rpx;
+								height: 28rpx;
+							}
+						}
+					}
+
+					.body_title2 {
+						margin-top: 20rpx;
+						font-size: 28rpx;
+					}
+
+					.body_detail {
+						display: flex;
+						flex-direction: column;
+						justify-content: space-between;
+						align-items: flex-end;
+						box-sizing: border-box;
+						padding: 20rpx 30rpx;
+						margin-top: 18rpx;
+						width: 710rpx;
+						height: 215rpx;
+						font-size: 28rpx;
+						border-radius: 7rpx;
+						background-color: #f2f2f2;
+
+						text {
+							color: #ff5733;
+						}
+					}
+
+					.body_bottom {
+						display: flex;
+						justify-content: space-between;
+						align-items: center;
+						margin-top: 250rpx;
+						height: 80rpx;
+
+						.bottom_left {
+							display: flex;
+							align-items: center;
+							color: #333333;
+							font-size: 24rpx;
+
+							.img {
+								margin-right: 10rpx;
+								width: 48rpx;
+								height: 48rpx;
+							}
+						}
+
+						.bottom_right {
+							display: flex;
+							align-items: center;
+							color: #ff5733;
+							font-size: 40rpx;
+							font-weight: bold;
+
+							text {
+								font-size: 24rpx;
+							}
+
+							.btn {
+								display: flex;
+								justify-content: center;
+								align-items: center;
+								margin-left: 30rpx;
+								width: 238rpx;
+								height: 80rpx;
+								color: #fff;
+								font-size: 32rpx;
+								font-weight: 400;
+								border-radius: 64rpx;
+								background-color: #096562;
+							}
+						}
+					}
+				}
+			}
 		}
 	}
 }

Failā izmaiņas netiks attēlotas, jo tās ir par lielu
+ 218 - 0
pages/detailInfo/detailInfo.vue


+ 7 - 7
pages/index/index.vue

@@ -52,12 +52,12 @@ export default {
 					imgUrl: '../../static/search/img.png',
 					hotelName: '双溪镇抱朴小院',
 					type: '舒适型',
-					distance: '3.2',
+					distance: '7.2',
 					price: 180
 				},
 				{
 					id: 2,
-					imgUrl: '../../static/search/img.png',
+					imgUrl: '../../static/index/banner.png',
 					hotelName: '幸福乡宿',
 					type: '舒适型',
 					distance: '3.2',
@@ -65,10 +65,10 @@ export default {
 				},
 				{
 					id: 3,
-					imgUrl: '../../static/search/img.png',
+					imgUrl: '../../static/index/banner.png',
 					hotelName: '健康乡宿',
 					type: '舒适型',
-					distance: '3.2',
+					distance: '8.6',
 					price: 150
 				},
 				{
@@ -76,15 +76,15 @@ export default {
 					imgUrl: '../../static/search/img.png',
 					hotelName: '开心乡宿',
 					type: '舒适型',
-					distance: '3.2',
+					distance: '6.2',
 					price: 180
 				},
 				{
 					id: 5,
-					imgUrl: '../../static/search/img.png',
+					imgUrl: '../../static/index/banner.png',
 					hotelName: '快乐乡宿',
 					type: '舒适型',
-					distance: '3.2',
+					distance: '3.9',
 					price: 280
 				}
 			]

+ 48 - 9
pages/my/my.vue

@@ -3,6 +3,8 @@
 		<!-- 顶部用户信息区域 -->
 		<view class="header">
 			<img src="../../static/my/headerImg.png" />
+			<!-- 页面标题 -->
+			<view class="title">我的</view>
 			<!-- 头像区域 -->
 			<img class="img" src="../../static/my/portrait.png" />
 
@@ -11,10 +13,14 @@
 			<!-- 用户id区域 -->
 			<view class="number">ID:1925689</view>
 			<!-- 是否实名认证区域 -->
-			<view class="real">
+			<view class="real" v-if="real">
 				<img src="../../static/my/true.png" />
 				已实名认证
 			</view>
+			<view class="real2" v-else @click="goPageCommon">
+				去认证
+				<img src="../../static/my/right2.png" />
+			</view>
 		</view>
 
 		<!-- 内容区域 -->
@@ -43,7 +49,10 @@
 <script>
 export default {
 	data() {
-		return {}
+		return {
+			// 是否实名认证
+			real: false
+		}
 	},
 	methods: {
 		// 点击订单管理按钮回调
@@ -76,7 +85,7 @@ export default {
 
 	.header {
 		position: relative;
-		height: 300rpx;
+		height: 480rpx;
 		color: #fff;
 		overflow: hidden;
 
@@ -84,9 +93,17 @@ export default {
 			width: 100%;
 		}
 
+		.title {
+			position: absolute;
+			top: 65rpx;
+			left: 342rpx;
+			font-size: 28rpx;
+			color: #fff;
+		}
+
 		.img {
 			position: absolute;
-			top: 120rpx;
+			top: 190rpx;
 			left: 32rpx;
 			width: 140rpx;
 			height: 140rpx;
@@ -94,7 +111,7 @@ export default {
 
 		.name {
 			position: absolute;
-			top: 118rpx;
+			top: 178rpx;
 			left: 202rpx;
 			font-size: 40rpx;
 			font-weight: bold;
@@ -102,7 +119,7 @@ export default {
 
 		.number {
 			position: absolute;
-			top: 176rpx;
+			top: 240rpx;
 			left: 202rpx;
 			font-size: 24rpx;
 			opacity: 0.5;
@@ -110,7 +127,7 @@ export default {
 
 		.real {
 			position: absolute;
-			top: 221rpx;
+			top: 285rpx;
 			left: 202rpx;
 			display: flex;
 			align-items: center;
@@ -128,15 +145,37 @@ export default {
 				height: 24rpx;
 			}
 		}
+
+		.real2 {
+			position: absolute;
+			top: 285rpx;
+			left: 202rpx;
+			display: flex;
+			justify-content: center;
+			align-items: center;
+			box-sizing: border-box;
+			padding-left: 13rpx;
+			width: 179rpx;
+			height: 42rpx;
+			font-size: 24rpx;
+			border-radius: 113rpx;
+			background-color: rgba(255, 255, 255, 0.2);
+
+			img {
+				margin-left: 12rpx;
+				width: 24rpx;
+				height: 24rpx;
+			}
+		}
 	}
 
 	.body {
 		position: absolute;
-		top: 290rpx;
+		top: 360rpx;
 		box-sizing: border-box;
 		padding: 0 30rpx;
 		width: 100%;
-		height: calc(100vh - 290rpx);
+		height: calc(100vh - 360rpx);
 		border-radius: 20rpx 20rpx 0 0;
 		background-color: #fff;
 

+ 398 - 0
pages/orderDetail/orderDetail.vue

@@ -0,0 +1,398 @@
+<template>
+	<view class="container">
+		<!-- 顶部订单状态信息区域 -->
+		<view class="header">
+			<!-- 背景图片区域 -->
+			<img src="../../static/my/headerImg.png" />
+			<!-- 标题区域 -->
+			<view class="header_title">订单详情</view>
+			<!-- 返回图标区域 -->
+			<img class="header_icon" src="../../static/index/left.png" @click="handleBack" />
+			<view class="header_type">订单超时</view>
+			<view class="header_info">订单已超过可支付时间,请重新下单</view>
+			<view class="header_btn">
+				<view class="btn_box">再次预定</view>
+				<view class="btn_box">删除订单</view>
+			</view>
+		</view>
+
+		<!-- 主体内容区域 -->
+		<view class="body">
+			<!-- 费用信息区域 -->
+			<view class="body_box">
+				<view class="box_title">费用信息</view>
+				<view class="box_container">
+					<view class="container_key">在线支付</view>
+					<view class="container_value price">¥280</view>
+				</view>
+				<view class="box_container">
+					<view class="container_key">发票报销</view>
+					<view class="container_value">如需发票,请先与酒店确认</view>
+				</view>
+			</view>
+
+			<!-- 民宿信息区域 -->
+			<view class="body_box">
+				<view class="box_info">
+					<img src="../../static/index/banner.png" />
+					<view class="info_msg">
+						<view class="msg_name">民宿名称</view>
+						<view class="msg_address">详细地址详细地址详细地址详细地址详细地址详细地址详细详细地址详细地址详细地址详</view>
+					</view>
+				</view>
+				<view class="box_btn">
+					<view class="btn_item">
+						<img class="img" src="../../static/my/map.png" />
+						地图/导航
+					</view>
+					<view class="btn_item">
+						<img class="img2" src="../../static/my/phone.png" />
+						联系商家
+					</view>
+				</view>
+			</view>
+
+			<!-- 房型信息和用户信息区域 -->
+			<view class="body_box2">
+				<view class="info_time">
+					7月26日
+					<text class="gap">今天</text>
+					<view class="time_line"></view>
+					<view class="time_num">1晚</view>
+					<view class="time_line"></view>
+					<view class="gap">7月26日</view>
+					<text>明天</text>
+				</view>
+				<view class="info_msg">大床房</view>
+				<view class="info_type">
+					<view class="type_item">包吃住型</view>
+					<view class="type_item">包吃住型</view>
+					<view class="type_item">包吃住型</view>
+				</view>
+				<view class="info_tag">
+					<view class="tag_item">16-20㎡</view>
+					<view class="tag_item">双人床</view>
+					<view class="tag_item">窗户位于走廊/窗户较小</view>
+				</view>
+				<view class="info_box">
+					<view class="box_key">住客姓名</view>
+					<view class="box_value">张三</view>
+				</view>
+				<view class="info_box">
+					<view class="box_key">联系电话</view>
+					<view class="box_value">13677985689</view>
+				</view>
+				<view class="info_box">
+					<view class="box_key">预计到店</view>
+					<view class="box_value">7月26日14:00之前</view>
+				</view>
+			</view>
+
+			<!-- 订单信息区域 -->
+			<view class="body_box">
+				<view class="box_title">订单信息</view>
+				<view class="box_container">
+					<view class="container_key">订单号</view>
+					<view class="container_value">2626262626260660606</view>
+					<view class="container_copy">复制</view>
+				</view>
+				<view class="box_container">
+					<view class="container_key">下单时间</view>
+					<view class="container_value">2023-06-27 15:15:15</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {}
+	},
+	methods: {
+		handleBack() {
+			uni.navigateBack(1)
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+	position: relative;
+	height: 100vh;
+	background-color: #f2f2f2;
+
+	.header {
+		height: 480rpx;
+		color: #fff;
+
+		img {
+			width: 100%;
+		}
+
+		.header_title {
+			position: absolute;
+			top: 65rpx;
+			left: 308rpx;
+			color: #fff;
+			font-size: 28rpx;
+		}
+
+		.header_icon {
+			position: absolute;
+			top: 56rpx;
+			left: 10rpx;
+			width: 47rpx;
+			height: 47rpx;
+		}
+
+		.header_type {
+			position: absolute;
+			top: 144rpx;
+			left: 30rpx;
+			font-size: 40rpx;
+		}
+
+		.header_info {
+			position: absolute;
+			top: 215rpx;
+			left: 30rpx;
+			font-size: 24rpx;
+		}
+
+		.header_btn {
+			position: absolute;
+			top: 280rpx;
+			left: 30rpx;
+			right: 30rpx;
+			display: flex;
+			justify-content: space-between;
+
+			.btn_box {
+				display: flex;
+				justify-content: center;
+				align-items: center;
+				width: 335rpx;
+				height: 72rpx;
+				border-radius: 10rpx;
+				color: #096562;
+				font-size: 28rpx;
+				background-color: #fff;
+			}
+		}
+	}
+
+	.body {
+		position: absolute;
+		top: 390rpx;
+		left: 0;
+		right: 0;
+		box-sizing: border-box;
+		padding: 20rpx;
+		height: calc(100vh - 390rpx);
+		border-radius: 20rpx 20rpx 0 0;
+		background-color: #ebeced;
+		overflow-y: auto;
+
+		.body_box {
+			box-sizing: border-box;
+			padding: 0 30rpx;
+			margin-bottom: 20rpx;
+			display: flex;
+			flex-direction: column;
+			height: 240rpx;
+			border-radius: 14rpx;
+			background-color: #fff;
+
+			.box_title {
+				line-height: 90rpx;
+				font-size: 28rpx;
+				font-weight: bold;
+				border-bottom: 1rpx solid #e5e5e5;
+			}
+
+			.box_container {
+				display: flex;
+				margin-top: 25rpx;
+				font-size: 28rpx;
+
+				.container_key {
+					width: 160rpx;
+					color: #808080;
+				}
+
+				.container_value {
+				}
+
+				.container_copy {
+					margin-left: auto;
+					color: #096562;
+				}
+
+				.price {
+					color: #ff5733;
+				}
+			}
+
+			.box_info {
+				display: flex;
+				margin-top: 20rpx;
+				height: 130rpx;
+
+				img {
+					width: 100rpx;
+					height: 100rpx;
+					border-radius: 10rpx;
+				}
+
+				.info_msg {
+					display: flex;
+					flex-direction: column;
+					justify-content: space-around;
+					margin-top: -10rpx;
+					margin-left: 18rpx;
+					width: 540rpx;
+
+					.msg_name {
+						font-size: 28rpx;
+						font-weight: bold;
+					}
+
+					.msg_address {
+						color: #808080;
+						font-size: 24rpx;
+						display: -webkit-box;
+						-webkit-box-orient: vertical;
+						-webkit-line-clamp: 2;
+						overflow: hidden;
+					}
+				}
+			}
+
+			.box_btn {
+				display: flex;
+				justify-content: space-around;
+				align-items: center;
+				height: 110rpx;
+
+				.btn_item {
+					display: flex;
+					align-items: center;
+					width: 200rpx;
+
+					.img {
+						margin-right: 14rpx;
+						width: 38rpx;
+						height: 38rpx;
+					}
+
+					.img2 {
+						width: 66rpx;
+						height: 66rpx;
+					}
+				}
+			}
+		}
+
+		.body_box2 {
+			display: flex;
+			flex-direction: column;
+			box-sizing: border-box;
+			padding: 0 30rpx;
+			margin-bottom: 20rpx;
+			height: 478rpx;
+			border-radius: 15rpx;
+			background-color: #fff;
+
+			.info_time {
+				display: flex;
+				align-items: center;
+				margin-top: 20rpx;
+				font-size: 32rpx;
+				font-weight: bold;
+
+				.time_line {
+					width: 17rpx;
+					height: 1rpx;
+					background-color: #096562;
+				}
+
+				.time_num {
+					box-sizing: border-box;
+					padding: 0 15rpx;
+					height: 46rpx;
+					line-height: 46rpx;
+					font-size: 24rpx;
+					font-weight: 400;
+					border-radius: 66rpx;
+					border: 1rpx solid #096562;
+					background-color: #f0f2f5;
+				}
+
+				.gap {
+					margin: 0 10rpx;
+				}
+
+				text {
+					font-size: 24rpx;
+					font-weight: 400;
+				}
+			}
+
+			.info_msg {
+				margin-top: 15rpx;
+				font-size: 28rpx;
+				font-weight: bold;
+			}
+			.info_type {
+				display: flex;
+				flex-wrap: wrap;
+				margin-top: 15rpx;
+
+				.type_item {
+					box-sizing: border-box;
+					padding: 0 15rpx;
+					margin-right: 20rpx;
+					height: 41rpx;
+					line-height: 41rpx;
+					font-size: 24rpx;
+					color: #fff;
+					border-radius: 34rpx;
+					background-color: #096562;
+				}
+			}
+
+			.info_tag {
+				display: flex;
+				flex-wrap: wrap;
+				margin: 18rpx 0 0;
+				box-sizing: border-box;
+				padding-bottom: 30rpx;
+				color: #808080;
+				font-size: 24rpx;
+				border-bottom: 1rpx solid #e5e5e5;
+
+				.tag_item {
+					margin-right: 20rpx;
+				}
+			}
+
+			.info_box {
+				display: flex;
+				margin-top: 23rpx;
+				font-size: 28rpx;
+
+				.box_key {
+					width: 160rpx;
+					color: #808080;
+				}
+
+				.box_value {
+				}
+			}
+		}
+	}
+}
+</style>

+ 7 - 1
pages/orderManage/orderManage.vue

@@ -9,7 +9,7 @@
 						<img class="img" src="../../static/my/delete.png" />
 					</view>
 				</template>
-				<view class="order_box">
+				<view class="order_box" @click="goPageOrderDetail">
 					<!-- 标题区域 -->
 					<view class="box_header">
 						<img class="img" src="../../static/my/hotel.png" />
@@ -76,6 +76,12 @@ export default {
 		}
 	},
 	methods: {
+		// 点击每一个订单回调
+		goPageOrderDetail() {
+			uni.navigateTo({
+				url: '/pages/orderDetail/orderDetail'
+			})
+		},
 		// 右侧选项内容删除按钮回调
 		handleDelete(item) {
 			console.log(item.name)

+ 227 - 0
pages/pay/pay.vue

@@ -0,0 +1,227 @@
+<template>
+	<view class="container">
+		<view class="countDown">交易剩余时间15:00</view>
+		<view class="price">
+			<text>¥</text>
+			{{ info.price }}
+		</view>
+
+		<view class="title">住房信息</view>
+		<view class="info">
+			<view class="info_time">
+				{{ info.startTimeMonth }}月{{ info.startTimeDay }}日
+				<text class="gap">星期{{ info.startTimeWeek }}</text>
+				<view class="time_line"></view>
+				<view class="time_num">{{ info.nightNum }}晚</view>
+				<view class="time_line"></view>
+				<view class="gap">{{ info.endTimeMonth }}月{{ info.endTimeDay }}日</view>
+				<text>星期{{ info.endTimeWeek }}</text>
+			</view>
+			<view class="info_msg">大床房</view>
+			<view class="info_type">
+				<view class="type_item">包吃住型</view>
+				<view class="type_item">包吃住型</view>
+				<view class="type_item">包吃住型</view>
+			</view>
+			<view class="info_tag">
+				<view class="tag_item">16-20㎡</view>
+				<view class="tag_item">双人床</view>
+				<view class="tag_item">窗户位于走廊/窗户较小</view>
+			</view>
+		</view>
+
+		<view class="title">支付方式</view>
+		<view class="way">
+			<view class="way_item" @click="handleChange">
+				<img src="../../static/index/wxPay.png" />
+				<view class="way_text">微信支付</view>
+				<radio class="way_radio" :checked="isChecked" />
+			</view>
+		</view>
+
+		<!-- 提交订单区域 -->
+		<view class="btn">提交订单</view>
+	</view>
+</template>
+
+<script>
+export default {
+	data() {
+		return {
+			isChecked: true,
+			info: {}
+		}
+	},
+	onLoad(options) {
+		this.info = JSON.parse(options.info)
+		console.log(this.info)
+	},
+	methods: {
+		// 点击支付方式回调
+		handleChange() {
+			this.isChecked = !this.isChecked
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.container {
+	position: relative;
+	box-sizing: border-box;
+	padding: 0 20rpx 160rpx;
+	min-height: 100vh;
+	background-color: #f2f3f5;
+
+	.countDown {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		height: 70rpx;
+		color: #808080;
+		font-size: 24rpx;
+	}
+
+	.price {
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		height: 65rpx;
+		font-size: 50rpx;
+		font-weight: bold;
+
+		text {
+			font-size: 28rpx;
+		}
+	}
+
+	.title {
+		margin-top: 14rpx;
+		color: #808080;
+		font-size: 24rpx;
+	}
+
+	.info {
+		display: flex;
+		flex-direction: column;
+		box-sizing: border-box;
+		padding: 0 30rpx;
+		margin-top: 18rpx;
+		width: 100%;
+		border-radius: 15rpx;
+		background-color: #fff;
+
+		.info_time {
+			display: flex;
+			align-items: center;
+			margin-top: 20rpx;
+			font-size: 32rpx;
+			font-weight: bold;
+
+			.time_line {
+				width: 17rpx;
+				height: 1rpx;
+				background-color: #096562;
+			}
+
+			.time_num {
+				box-sizing: border-box;
+				padding: 0 15rpx;
+				height: 46rpx;
+				line-height: 46rpx;
+				font-size: 24rpx;
+				font-weight: 400;
+				border-radius: 66rpx;
+				border: 1rpx solid #096562;
+				background-color: #f0f2f5;
+			}
+
+			.gap {
+				margin: 0 10rpx;
+			}
+
+			text {
+				font-size: 24rpx;
+				font-weight: 400;
+			}
+		}
+
+		.info_msg {
+			margin-top: 15rpx;
+			font-size: 28rpx;
+			font-weight: bold;
+		}
+		.info_type {
+			display: flex;
+			flex-wrap: wrap;
+			margin-top: 15rpx;
+
+			.type_item {
+				box-sizing: border-box;
+				padding: 0 15rpx;
+				margin-right: 20rpx;
+				height: 41rpx;
+				line-height: 41rpx;
+				font-size: 24rpx;
+				color: #fff;
+				border-radius: 34rpx;
+				background-color: #096562;
+			}
+		}
+
+		.info_tag {
+			display: flex;
+			flex-wrap: wrap;
+			margin: 18rpx 0 30rpx;
+			color: #808080;
+			font-size: 24rpx;
+
+			.tag_item {
+				margin-right: 20rpx;
+			}
+		}
+	}
+
+	.way {
+		.way_item {
+			display: flex;
+			align-items: center;
+			box-sizing: border-box;
+			padding: 0 30rpx;
+			margin-top: 18rpx;
+			height: 100rpx;
+			font-size: 28rpx;
+			border-radius: 15rpx;
+			background-color: #fff;
+
+			img {
+				width: 40rpx;
+				height: 40rpx;
+			}
+
+			.way_text {
+				margin-left: 18rpx;
+			}
+
+			.way_radio {
+				margin-left: auto;
+				transform: scale(0.9);
+			}
+		}
+	}
+
+	.btn {
+		position: fixed;
+		bottom: 40rpx;
+		display: flex;
+		justify-content: center;
+		align-items: center;
+		width: 710rpx;
+		height: 96rpx;
+		color: #fff;
+		font-size: 32rpx;
+		border-radius: 64rpx;
+		background-color: #096562;
+	}
+}
+</style>

BIN
static/index/add.png


BIN
static/index/address.png


BIN
static/index/air.png


BIN
static/index/bed.png


BIN
static/index/close.png


BIN
static/index/info.png


BIN
static/index/left.png


BIN
static/index/lock.png


BIN
static/index/minus.png


BIN
static/index/people.png


BIN
static/index/phone2.png


BIN
static/index/place.png


BIN
static/index/right2.png


BIN
static/index/smoke.png


BIN
static/index/time.png


BIN
static/index/wifi.png


BIN
static/index/window.png


BIN
static/index/wxPay.png


BIN
static/my/map.png


BIN
static/my/phone.png


BIN
static/my/right2.png


+ 68 - 0
uni_modules/uni-popup/changelog.md

@@ -0,0 +1,68 @@
+## 1.8.3(2023-04-17)
+- 修复 uni-popup 重复打开时的 bug
+## 1.8.2(2023-02-02)
+- uni-popup-dialog 组件新增 inputType 属性
+## 1.8.1(2022-12-01)
+- 修复 nvue 下 v-show 报错
+## 1.8.0(2022-11-29)
+- 优化 主题样式
+## 1.7.9(2022-04-02)
+- 修复 弹出层内部无法滚动的bug
+## 1.7.8(2022-03-28)
+- 修复 小程序中高度错误的bug
+## 1.7.7(2022-03-17)
+- 修复 快速调用open出现问题的Bug
+## 1.7.6(2022-02-14)
+- 修复 safeArea 属性不能设置为false的bug
+## 1.7.5(2022-01-19)
+- 修复 isMaskClick 失效的bug
+## 1.7.4(2022-01-19)
+- 新增 cancelText \ confirmText 属性 ,可自定义文本
+- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色
+- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题
+## 1.7.3(2022-01-13)
+- 修复 设置 safeArea 属性不生效的bug
+## 1.7.2(2021-11-26)
+- 优化 组件示例
+## 1.7.1(2021-11-26)
+- 修复 vuedoc 文字错误
+## 1.7.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup)
+## 1.6.2(2021-08-24)
+- 新增 支持国际化
+## 1.6.1(2021-07-30)
+- 优化 vue3下事件警告的问题
+## 1.6.0(2021-07-13)
+- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.5.0(2021-06-23)
+- 新增 mask-click 遮罩层点击事件
+## 1.4.5(2021-06-22)
+- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
+## 1.4.4(2021-06-18)
+- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug
+## 1.4.3(2021-06-08)
+- 修复 错误的 watch 字段
+- 修复 safeArea 属性不生效的问题
+- 修复 点击内容,再点击遮罩无法关闭的Bug
+## 1.4.2(2021-05-12)
+- 新增 组件示例地址
+## 1.4.1(2021-04-29)
+- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题
+## 1.4.0 (2021-04-29)
+- 新增 type 属性的 left\right 值,支持左右弹出
+- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗
+- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色
+- 新增 safeArea 属性,是否适配底部安全区
+- 修复 App\h5\微信小程序底部安全区占位不对的Bug
+- 修复 App 端弹出等待的Bug
+- 优化 提升低配设备性能,优化动画卡顿问题
+- 优化 更简单的组件自定义方式
+## 1.2.9(2021-02-05)
+- 优化 组件引用关系,通过uni_modules引用组件
+## 1.2.8(2021-02-05)
+- 调整为uni_modules目录规范
+## 1.2.7(2021-02-05)
+- 调整为uni_modules目录规范
+- 新增 支持 PC 端
+- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端

+ 45 - 0
uni_modules/uni-popup/components/uni-popup-dialog/keypress.js

@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+  name: 'Keypress',
+  props: {
+    disable: {
+      type: Boolean,
+      default: false
+    }
+  },
+  mounted () {
+    const keyNames = {
+      esc: ['Esc', 'Escape'],
+      tab: 'Tab',
+      enter: 'Enter',
+      space: [' ', 'Spacebar'],
+      up: ['Up', 'ArrowUp'],
+      left: ['Left', 'ArrowLeft'],
+      right: ['Right', 'ArrowRight'],
+      down: ['Down', 'ArrowDown'],
+      delete: ['Backspace', 'Delete', 'Del']
+    }
+    const listener = ($event) => {
+      if (this.disable) {
+        return
+      }
+      const keyName = Object.keys(keyNames).find(key => {
+        const keyName = $event.key
+        const value = keyNames[key]
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
+      })
+      if (keyName) {
+        // 避免和其他按键事件冲突
+        setTimeout(() => {
+          this.$emit(keyName, {})
+        }, 0)
+      }
+    }
+    document.addEventListener('keyup', listener)
+    this.$once('hook:beforeDestroy', () => {
+      document.removeEventListener('keyup', listener)
+    })
+  },
+	render: () => {}
+}
+// #endif

+ 275 - 0
uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue

@@ -0,0 +1,275 @@
+<template>
+	<view class="uni-popup-dialog">
+		<view class="uni-dialog-title">
+			<text class="uni-dialog-title-text" :class="['uni-popup__'+dialogType]">{{titleText}}</text>
+		</view>
+		<view v-if="mode === 'base'" class="uni-dialog-content">
+			<slot>
+				<text class="uni-dialog-content-text">{{content}}</text>
+			</slot>
+		</view>
+		<view v-else class="uni-dialog-content">
+			<slot>
+				<input class="uni-dialog-input" v-model="val" :type="inputType" :placeholder="placeholderText" :focus="focus" >
+			</slot>
+		</view>
+		<view class="uni-dialog-button-group">
+			<view class="uni-dialog-button" @click="closeDialog">
+				<text class="uni-dialog-button-text">{{closeText}}</text>
+			</view>
+			<view class="uni-dialog-button uni-border-left" @click="onOk">
+				<text class="uni-dialog-button-text uni-button-color">{{okText}}</text>
+			</view>
+		</view>
+
+	</view>
+</template>
+
+<script>
+	import popup from '../uni-popup/popup.js'
+	import {
+	initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from '../uni-popup/i18n/index.js'
+	const {	t } = initVueI18n(messages)
+	/**
+	 * PopUp 弹出层-对话框样式
+	 * @description 弹出层-对话框样式
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} value input 模式下的默认值
+	 * @property {String} placeholder input 模式下输入提示
+	 * @property {String} type = [success|warning|info|error] 主题样式
+	 *  @value success 成功
+	 * 	@value warning 提示
+	 * 	@value info 消息
+	 * 	@value error 错误
+	 * @property {String} mode = [base|input] 模式、
+	 * 	@value base 基础对话框
+	 * 	@value input 可输入对话框
+	 * @property {String} content 对话框内容
+	 * @property {Boolean} beforeClose 是否拦截取消事件
+	 * @event {Function} confirm 点击确认按钮触发
+	 * @event {Function} close 点击取消按钮触发
+	 */
+
+	export default {
+		name: "uniPopupDialog",
+		mixins: [popup],
+		emits:['confirm','close'],
+		props: {
+			inputType:{
+				type: String,
+				default: 'text'
+			},
+			value: {
+				type: [String, Number],
+				default: ''
+			},
+			placeholder: {
+				type: [String, Number],
+				default: ''
+			},
+			type: {
+				type: String,
+				default: 'error'
+			},
+			mode: {
+				type: String,
+				default: 'base'
+			},
+			title: {
+				type: String,
+				default: ''
+			},
+			content: {
+				type: String,
+				default: ''
+			},
+			beforeClose: {
+				type: Boolean,
+				default: false
+			},
+			cancelText:{
+				type: String,
+				default: ''
+			},
+			confirmText:{
+				type: String,
+				default: ''
+			}
+		},
+		data() {
+			return {
+				dialogType: 'error',
+				focus: false,
+				val: ""
+			}
+		},
+		computed: {
+			okText() {
+				return this.confirmText || t("uni-popup.ok")
+			},
+			closeText() {
+				return this.cancelText || t("uni-popup.cancel")
+			},
+			placeholderText() {
+				return this.placeholder || t("uni-popup.placeholder")
+			},
+			titleText() {
+				return this.title || t("uni-popup.title")
+			}
+		},
+		watch: {
+			type(val) {
+				this.dialogType = val
+			},
+			mode(val) {
+				if (val === 'input') {
+					this.dialogType = 'info'
+				}
+			},
+			value(val) {
+				this.val = val
+			}
+		},
+		created() {
+			// 对话框遮罩不可点击
+			this.popup.disableMask()
+			// this.popup.closeMask()
+			if (this.mode === 'input') {
+				this.dialogType = 'info'
+				this.val = this.value
+			} else {
+				this.dialogType = this.type
+			}
+		},
+		mounted() {
+			this.focus = true
+		},
+		methods: {
+			/**
+			 * 点击确认按钮
+			 */
+			onOk() {
+				if (this.mode === 'input'){
+					this.$emit('confirm', this.val)
+				}else{
+					this.$emit('confirm')
+				}
+				if(this.beforeClose) return
+				this.popup.close()
+			},
+			/**
+			 * 点击取消按钮
+			 */
+			closeDialog() {
+				this.$emit('close')
+				if(this.beforeClose) return
+				this.popup.close()
+			},
+			close(){
+				this.popup.close()
+			}
+		}
+	}
+</script>
+
+<style lang="scss" >
+	.uni-popup-dialog {
+		width: 300px;
+		border-radius: 11px;
+		background-color: #fff;
+	}
+
+	.uni-dialog-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		padding-top: 25px;
+	}
+
+	.uni-dialog-title-text {
+		font-size: 16px;
+		font-weight: 500;
+	}
+
+	.uni-dialog-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		padding: 20px;
+	}
+
+	.uni-dialog-content-text {
+		font-size: 14px;
+		color: #6C6C6C;
+	}
+
+	.uni-dialog-button-group {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		border-top-color: #f5f5f5;
+		border-top-style: solid;
+		border-top-width: 1px;
+	}
+
+	.uni-dialog-button {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+
+		flex: 1;
+		flex-direction: row;
+		justify-content: center;
+		align-items: center;
+		height: 45px;
+	}
+
+	.uni-border-left {
+		border-left-color: #f0f0f0;
+		border-left-style: solid;
+		border-left-width: 1px;
+	}
+
+	.uni-dialog-button-text {
+		font-size: 16px;
+		color: #333;
+	}
+
+	.uni-button-color {
+		color: #007aff;
+	}
+
+	.uni-dialog-input {
+		flex: 1;
+		font-size: 14px;
+		border: 1px #eee solid;
+		height: 40px;
+		padding: 0 10px;
+		border-radius: 5px;
+		color: #555;
+	}
+
+	.uni-popup__success {
+		color: #4cd964;
+	}
+
+	.uni-popup__warn {
+		color: #f0ad4e;
+	}
+
+	.uni-popup__error {
+		color: #dd524d;
+	}
+
+	.uni-popup__info {
+		color: #909399;
+	}
+</style>

+ 143 - 0
uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue

@@ -0,0 +1,143 @@
+<template>
+	<view class="uni-popup-message">
+		<view class="uni-popup-message__box fixforpc-width" :class="'uni-popup__'+type">
+			<slot>
+				<text class="uni-popup-message-text" :class="'uni-popup__'+type+'-text'">{{message}}</text>
+			</slot>
+		</view>
+	</view>
+</template>
+
+<script>
+	import popup from '../uni-popup/popup.js'
+	/**
+	 * PopUp 弹出层-消息提示
+	 * @description 弹出层-消息提示
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [success|warning|info|error] 主题样式
+	 *  @value success 成功
+	 * 	@value warning 提示
+	 * 	@value info 消息
+	 * 	@value error 错误
+	 * @property {String} message 消息提示文字
+	 * @property {String} duration 显示时间,设置为 0 则不会自动关闭
+	 */
+
+	export default {
+		name: 'uniPopupMessage',
+		mixins:[popup],
+		props: {
+			/**
+			 * 主题 success/warning/info/error	  默认 success
+			 */
+			type: {
+				type: String,
+				default: 'success'
+			},
+			/**
+			 * 消息文字
+			 */
+			message: {
+				type: String,
+				default: ''
+			},
+			/**
+			 * 显示时间,设置为 0 则不会自动关闭
+			 */
+			duration: {
+				type: Number,
+				default: 3000
+			},
+			maskShow:{
+				type:Boolean,
+				default:false
+			}
+		},
+		data() {
+			return {}
+		},
+		created() {
+			this.popup.maskShow = this.maskShow
+			this.popup.messageChild = this
+		},
+		methods: {
+			timerClose(){
+				if(this.duration === 0) return
+				clearTimeout(this.timer) 
+				this.timer = setTimeout(()=>{
+					this.popup.close()
+				},this.duration)
+			}
+		}
+	}
+</script>
+<style lang="scss" >
+	.uni-popup-message {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+	}
+
+	.uni-popup-message__box {
+		background-color: #e1f3d8;
+		padding: 10px 15px;
+		border-color: #eee;
+		border-style: solid;
+		border-width: 1px;
+		flex: 1;
+	}
+
+	@media screen and (min-width: 500px) {
+		.fixforpc-width {
+			margin-top: 20px;
+			border-radius: 4px;
+			flex: none;
+			min-width: 380px;
+			/* #ifndef APP-NVUE */
+			max-width: 50%;
+			/* #endif */
+			/* #ifdef APP-NVUE */
+			max-width: 500px;
+			/* #endif */
+		}
+	}
+
+	.uni-popup-message-text {
+		font-size: 14px;
+		padding: 0;
+	}
+
+	.uni-popup__success {
+		background-color: #e1f3d8;
+	}
+
+	.uni-popup__success-text {
+		color: #67C23A;
+	}
+
+	.uni-popup__warn {
+		background-color: #faecd8;
+	}
+
+	.uni-popup__warn-text {
+		color: #E6A23C;
+	}
+
+	.uni-popup__error {
+		background-color: #fde2e2;
+	}
+
+	.uni-popup__error-text {
+		color: #F56C6C;
+	}
+
+	.uni-popup__info {
+		background-color: #F2F6FC;
+	}
+
+	.uni-popup__info-text {
+		color: #909399;
+	}
+</style>

+ 187 - 0
uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue

@@ -0,0 +1,187 @@
+<template>
+	<view class="uni-popup-share">
+		<view class="uni-share-title"><text class="uni-share-title-text">{{shareTitleText}}</text></view>
+		<view class="uni-share-content">
+			<view class="uni-share-content-box">
+				<view class="uni-share-content-item" v-for="(item,index) in bottomData" :key="index" @click.stop="select(item,index)">
+					<image class="uni-share-image" :src="item.icon" mode="aspectFill"></image>
+					<text class="uni-share-text">{{item.text}}</text>
+				</view>
+
+			</view>
+		</view>
+		<view class="uni-share-button-box">
+			<button class="uni-share-button" @click="close">{{cancelText}}</button>
+		</view>
+	</view>
+</template>
+
+<script>
+	import popup from '../uni-popup/popup.js'
+	import {
+	initVueI18n
+	} from '@dcloudio/uni-i18n'
+	import messages from '../uni-popup/i18n/index.js'
+	const {	t	} = initVueI18n(messages)
+	export default {
+		name: 'UniPopupShare',
+		mixins:[popup],
+		emits:['select'],
+		props: {
+			title: {
+				type: String,
+				default: ''
+			},
+			beforeClose: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				bottomData: [{
+						text: '微信',
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/c2b17470-50be-11eb-b680-7980c8a877b8.png',
+						name: 'wx'
+					},
+					{
+						text: '支付宝',
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/d684ae40-50be-11eb-8ff1-d5dcf8779628.png',
+						name: 'wx'
+					},
+					{
+						text: 'QQ',
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/e7a79520-50be-11eb-b997-9918a5dda011.png',
+						name: 'qq'
+					},
+					{
+						text: '新浪',
+						icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/0dacdbe0-50bf-11eb-8ff1-d5dcf8779628.png',
+						name: 'sina'
+					},
+					// {
+					// 	text: '百度',
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/1ec6e920-50bf-11eb-8a36-ebb87efcf8c0.png',
+					// 	name: 'copy'
+					// },
+					// {
+					// 	text: '其他',
+					// 	icon: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/2e0fdfe0-50bf-11eb-b997-9918a5dda011.png',
+					// 	name: 'more'
+					// }
+				]
+			}
+		},
+		created() {},
+		computed: {
+			cancelText() {
+				return t("uni-popup.cancel")
+			},
+		shareTitleText() {
+				return this.title || t("uni-popup.shareTitle")
+			}
+		},
+		methods: {
+			/**
+			 * 选择内容
+			 */
+			select(item, index) {
+				this.$emit('select', {
+					item,
+					index
+				})
+				this.close()
+
+			},
+			/**
+			 * 关闭窗口
+			 */
+			close() {
+				if(this.beforeClose) return
+				this.popup.close()
+			}
+		}
+	}
+</script>
+<style lang="scss" >
+	.uni-popup-share {
+		background-color: #fff;
+		border-top-left-radius: 11px;
+		border-top-right-radius: 11px;
+	}
+	.uni-share-title {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		height: 40px;
+	}
+	.uni-share-title-text {
+		font-size: 14px;
+		color: #666;
+	}
+	.uni-share-content {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: center;
+		padding-top: 10px;
+	}
+
+	.uni-share-content-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		flex-wrap: wrap;
+		width: 360px;
+	}
+
+	.uni-share-content-item {
+		width: 90px;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		justify-content: center;
+		padding: 10px 0;
+		align-items: center;
+	}
+
+	.uni-share-content-item:active {
+		background-color: #f5f5f5;
+	}
+
+	.uni-share-image {
+		width: 30px;
+		height: 30px;
+	}
+
+	.uni-share-text {
+		margin-top: 10px;
+		font-size: 14px;
+		color: #3B4144;
+	}
+
+	.uni-share-button-box {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		padding: 10px 15px;
+	}
+
+	.uni-share-button {
+		flex: 1;
+		border-radius: 50px;
+		color: #666;
+		font-size: 16px;
+	}
+
+	.uni-share-button::after {
+		border-radius: 50px;
+	}
+</style>

+ 7 - 0
uni_modules/uni-popup/components/uni-popup/i18n/en.json

@@ -0,0 +1,7 @@
+{
+	"uni-popup.cancel": "cancel",
+	"uni-popup.ok": "ok",
+	"uni-popup.placeholder": "pleace enter",
+	"uni-popup.title": "Hint",
+	"uni-popup.shareTitle": "Share to"
+}

+ 8 - 0
uni_modules/uni-popup/components/uni-popup/i18n/index.js

@@ -0,0 +1,8 @@
+import en from './en.json'
+import zhHans from './zh-Hans.json'
+import zhHant from './zh-Hant.json'
+export default {
+	en,
+	'zh-Hans': zhHans,
+	'zh-Hant': zhHant
+}

+ 7 - 0
uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json

@@ -0,0 +1,7 @@
+{
+	"uni-popup.cancel": "取消",
+	"uni-popup.ok": "确定",
+	"uni-popup.placeholder": "请输入",
+		"uni-popup.title": "提示",
+		"uni-popup.shareTitle": "分享到"
+}

+ 7 - 0
uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json

@@ -0,0 +1,7 @@
+{
+	"uni-popup.cancel": "取消",
+	"uni-popup.ok": "確定",
+	"uni-popup.placeholder": "請輸入",
+	"uni-popup.title": "提示",
+	"uni-popup.shareTitle": "分享到"
+}

+ 45 - 0
uni_modules/uni-popup/components/uni-popup/keypress.js

@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+  name: 'Keypress',
+  props: {
+    disable: {
+      type: Boolean,
+      default: false
+    }
+  },
+  mounted () {
+    const keyNames = {
+      esc: ['Esc', 'Escape'],
+      tab: 'Tab',
+      enter: 'Enter',
+      space: [' ', 'Spacebar'],
+      up: ['Up', 'ArrowUp'],
+      left: ['Left', 'ArrowLeft'],
+      right: ['Right', 'ArrowRight'],
+      down: ['Down', 'ArrowDown'],
+      delete: ['Backspace', 'Delete', 'Del']
+    }
+    const listener = ($event) => {
+      if (this.disable) {
+        return
+      }
+      const keyName = Object.keys(keyNames).find(key => {
+        const keyName = $event.key
+        const value = keyNames[key]
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
+      })
+      if (keyName) {
+        // 避免和其他按键事件冲突
+        setTimeout(() => {
+          this.$emit(keyName, {})
+        }, 0)
+      }
+    }
+    document.addEventListener('keyup', listener)
+    // this.$once('hook:beforeDestroy', () => {
+    //   document.removeEventListener('keyup', listener)
+    // })
+  },
+	render: () => {}
+}
+// #endif

+ 26 - 0
uni_modules/uni-popup/components/uni-popup/popup.js

@@ -0,0 +1,26 @@
+
+export default {
+	data() {
+		return {
+			
+		}
+	},
+	created(){
+		this.popup = this.getParent()
+	},
+	methods:{
+		/**
+		 * 获取父元素实例
+		 */
+		getParent(name = 'uniPopup') {
+			let parent = this.$parent;
+			let parentName = parent.$options.name;
+			while (parentName !== name) {
+				parent = parent.$parent;
+				if (!parent) return false
+				parentName = parent.$options.name;
+			}
+			return parent;
+		},
+	}
+}

+ 473 - 0
uni_modules/uni-popup/components/uni-popup/uni-popup.vue

@@ -0,0 +1,473 @@
+<template>
+	<view v-if="showPopup" class="uni-popup" :class="[popupstyle, isDesktop ? 'fixforpc-z-index' : '']">
+		<view @touchstart="touchstart">
+			<uni-transition key="1" v-if="maskShow" name="mask" mode-class="fade" :styles="maskClass"
+				:duration="duration" :show="showTrans" @click="onTap" />
+			<uni-transition key="2" :mode-class="ani" name="content" :styles="transClass" :duration="duration"
+				:show="showTrans" @click="onTap">
+				<view class="uni-popup__wrapper" :style="{ backgroundColor: bg }" :class="[popupstyle]" @click="clear">
+					<slot />
+				</view>
+			</uni-transition>
+		</view>
+		<!-- #ifdef H5 -->
+		<keypress v-if="maskShow" @esc="onTap" />
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	// #ifdef H5
+	import keypress from './keypress.js'
+	// #endif
+
+	/**
+	 * PopUp 弹出层
+	 * @description 弹出层组件,为了解决遮罩弹层的问题
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=329
+	 * @property {String} type = [top|center|bottom|left|right|message|dialog|share] 弹出方式
+	 * 	@value top 顶部弹出
+	 * 	@value center 中间弹出
+	 * 	@value bottom 底部弹出
+	 * 	@value left		左侧弹出
+	 * 	@value right  右侧弹出
+	 * 	@value message 消息提示
+	 * 	@value dialog 对话框
+	 * 	@value share 底部分享示例
+	 * @property {Boolean} animation = [true|false] 是否开启动画
+	 * @property {Boolean} maskClick = [true|false] 蒙版点击是否关闭弹窗(废弃)
+	 * @property {Boolean} isMaskClick = [true|false] 蒙版点击是否关闭弹窗
+	 * @property {String}  backgroundColor 主窗口背景色
+	 * @property {String}  maskBackgroundColor 蒙版颜色
+	 * @property {Boolean} safeArea		   是否适配底部安全区
+	 * @event {Function} change 打开关闭弹窗触发,e={show: false}
+	 * @event {Function} maskClick 点击遮罩触发
+	 */
+
+	export default {
+		name: 'uniPopup',
+		components: {
+			// #ifdef H5
+			keypress
+			// #endif
+		},
+		emits: ['change', 'maskClick'],
+		props: {
+			// 开启动画
+			animation: {
+				type: Boolean,
+				default: true
+			},
+			// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+			// message: 消息提示 ; dialog : 对话框
+			type: {
+				type: String,
+				default: 'center'
+			},
+			// maskClick
+			isMaskClick: {
+				type: Boolean,
+				default: null
+			},
+			// TODO 2 个版本后废弃属性 ,使用 isMaskClick
+			maskClick: {
+				type: Boolean,
+				default: null
+			},
+			backgroundColor: {
+				type: String,
+				default: 'none'
+			},
+			safeArea: {
+				type: Boolean,
+				default: true
+			},
+			maskBackgroundColor: {
+				type: String,
+				default: 'rgba(0, 0, 0, 0.4)'
+			},
+		},
+
+		watch: {
+			/**
+			 * 监听type类型
+			 */
+			type: {
+				handler: function(type) {
+					if (!this.config[type]) return
+					this[this.config[type]](true)
+				},
+				immediate: true
+			},
+			isDesktop: {
+				handler: function(newVal) {
+					if (!this.config[newVal]) return
+					this[this.config[this.type]](true)
+				},
+				immediate: true
+			},
+			/**
+			 * 监听遮罩是否可点击
+			 * @param {Object} val
+			 */
+			maskClick: {
+				handler: function(val) {
+					this.mkclick = val
+				},
+				immediate: true
+			},
+			isMaskClick: {
+				handler: function(val) {
+					this.mkclick = val
+				},
+				immediate: true
+			},
+			// H5 下禁止底部滚动
+			showPopup(show) {
+				// #ifdef H5
+				// fix by mehaotian 处理 h5 滚动穿透的问题
+				document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
+				// #endif
+			}
+		},
+		data() {
+			return {
+				duration: 300,
+				ani: [],
+				showPopup: false,
+				showTrans: false,
+				popupWidth: 0,
+				popupHeight: 0,
+				config: {
+					top: 'top',
+					bottom: 'bottom',
+					center: 'center',
+					left: 'left',
+					right: 'right',
+					message: 'top',
+					dialog: 'center',
+					share: 'bottom'
+				},
+				maskClass: {
+					position: 'fixed',
+					bottom: 0,
+					top: 0,
+					left: 0,
+					right: 0,
+					backgroundColor: 'rgba(0, 0, 0, 0.4)'
+				},
+				transClass: {
+					position: 'fixed',
+					left: 0,
+					right: 0
+				},
+				maskShow: true,
+				mkclick: true,
+				popupstyle: this.isDesktop ? 'fixforpc-top' : 'top'
+			}
+		},
+		computed: {
+			isDesktop() {
+				return this.popupWidth >= 500 && this.popupHeight >= 500
+			},
+			bg() {
+				if (this.backgroundColor === '' || this.backgroundColor === 'none') {
+					return 'transparent'
+				}
+				return this.backgroundColor
+			}
+		},
+		mounted() {
+			const fixSize = () => {
+				const {
+					windowWidth,
+					windowHeight,
+					windowTop,
+					safeArea,
+					screenHeight,
+					safeAreaInsets
+				} = uni.getSystemInfoSync()
+				this.popupWidth = windowWidth
+				this.popupHeight = windowHeight + (windowTop || 0)
+				// TODO fix by mehaotian 是否适配底部安全区 ,目前微信ios 、和 app ios 计算有差异,需要框架修复
+				if (safeArea && this.safeArea) {
+					// #ifdef MP-WEIXIN
+					this.safeAreaInsets = screenHeight - safeArea.bottom
+					// #endif
+					// #ifndef MP-WEIXIN
+					this.safeAreaInsets = safeAreaInsets.bottom
+					// #endif
+				} else {
+					this.safeAreaInsets = 0
+				}
+			}
+			fixSize()
+			// #ifdef H5
+			// window.addEventListener('resize', fixSize)
+			// this.$once('hook:beforeDestroy', () => {
+			// 	window.removeEventListener('resize', fixSize)
+			// })
+			// #endif
+		},
+		// #ifndef VUE3
+		// TODO vue2
+		destroyed() {
+			this.setH5Visible()
+		},
+		// #endif
+		// #ifdef VUE3
+		// TODO vue3
+		unmounted() {
+			this.setH5Visible()
+		},
+		// #endif
+		created() {
+			// this.mkclick =  this.isMaskClick || this.maskClick
+			if (this.isMaskClick === null && this.maskClick === null) {
+				this.mkclick = true
+			} else {
+				this.mkclick = this.isMaskClick !== null ? this.isMaskClick : this.maskClick
+			}
+			if (this.animation) {
+				this.duration = 300
+			} else {
+				this.duration = 0
+			}
+			// TODO 处理 message 组件生命周期异常的问题
+			this.messageChild = null
+			// TODO 解决头条冒泡的问题
+			this.clearPropagation = false
+			this.maskClass.backgroundColor = this.maskBackgroundColor
+		},
+		methods: {
+			setH5Visible() {
+				// #ifdef H5
+				// fix by mehaotian 处理 h5 滚动穿透的问题
+				document.getElementsByTagName('body')[0].style.overflow = 'visible'
+				// #endif
+			},
+			/**
+			 * 公用方法,不显示遮罩层
+			 */
+			closeMask() {
+				this.maskShow = false
+			},
+			/**
+			 * 公用方法,遮罩层禁止点击
+			 */
+			disableMask() {
+				this.mkclick = false
+			},
+			// TODO nvue 取消冒泡
+			clear(e) {
+				// #ifndef APP-NVUE
+				e.stopPropagation()
+				// #endif
+				this.clearPropagation = true
+			},
+
+			open(direction) {
+				// fix by mehaotian 处理快速打开关闭的情况
+				if (this.showPopup) {
+					return
+				}
+				let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
+				if (!(direction && innerType.indexOf(direction) !== -1)) {
+					direction = this.type
+				}
+				if (!this.config[direction]) {
+					console.error('缺少类型:', direction)
+					return
+				}
+				this[this.config[direction]]()
+				this.$emit('change', {
+					show: true,
+					type: direction
+				})
+			},
+			close(type) {
+				this.showTrans = false
+				this.$emit('change', {
+					show: false,
+					type: this.type
+				})
+				clearTimeout(this.timer)
+				// // 自定义关闭事件
+				// this.customOpen && this.customClose()
+				this.timer = setTimeout(() => {
+					this.showPopup = false
+				}, 300)
+			},
+			// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
+			touchstart() {
+				this.clearPropagation = false
+			},
+
+			onTap() {
+				if (this.clearPropagation) {
+					// fix by mehaotian 兼容 nvue
+					this.clearPropagation = false
+					return
+				}
+				this.$emit('maskClick')
+				if (!this.mkclick) return
+				this.close()
+			},
+			/**
+			 * 顶部弹出样式处理
+			 */
+			top(type) {
+				this.popupstyle = this.isDesktop ? 'fixforpc-top' : 'top'
+				this.ani = ['slide-top']
+				this.transClass = {
+					position: 'fixed',
+					left: 0,
+					right: 0,
+					backgroundColor: this.bg
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+				this.$nextTick(() => {
+					if (this.messageChild && this.type === 'message') {
+						this.messageChild.timerClose()
+					}
+				})
+			},
+			/**
+			 * 底部弹出样式处理
+			 */
+			bottom(type) {
+				this.popupstyle = 'bottom'
+				this.ani = ['slide-bottom']
+				this.transClass = {
+					position: 'fixed',
+					left: 0,
+					right: 0,
+					bottom: 0,
+					paddingBottom: this.safeAreaInsets + 'px',
+					backgroundColor: this.bg
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			/**
+			 * 中间弹出样式处理
+			 */
+			center(type) {
+				this.popupstyle = 'center'
+				this.ani = ['zoom-out', 'fade']
+				this.transClass = {
+					position: 'fixed',
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column',
+					/* #endif */
+					bottom: 0,
+					left: 0,
+					right: 0,
+					top: 0,
+					justifyContent: 'center',
+					alignItems: 'center'
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			left(type) {
+				this.popupstyle = 'left'
+				this.ani = ['slide-left']
+				this.transClass = {
+					position: 'fixed',
+					left: 0,
+					bottom: 0,
+					top: 0,
+					backgroundColor: this.bg,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column'
+					/* #endif */
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			right(type) {
+				this.popupstyle = 'right'
+				this.ani = ['slide-right']
+				this.transClass = {
+					position: 'fixed',
+					bottom: 0,
+					right: 0,
+					top: 0,
+					backgroundColor: this.bg,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column'
+					/* #endif */
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			}
+		}
+	}
+</script>
+<style lang="scss">
+	.uni-popup {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+
+		/* #endif */
+		&.top,
+		&.left,
+		&.right {
+			/* #ifdef H5 */
+			top: var(--window-top);
+			/* #endif */
+			/* #ifndef H5 */
+			top: 0;
+			/* #endif */
+		}
+
+		.uni-popup__wrapper {
+			/* #ifndef APP-NVUE */
+			display: block;
+			/* #endif */
+			position: relative;
+
+			/* iphonex 等安全区设置,底部安全区适配 */
+			/* #ifndef APP-NVUE */
+			// padding-bottom: constant(safe-area-inset-bottom);
+			// padding-bottom: env(safe-area-inset-bottom);
+			/* #endif */
+			&.left,
+			&.right {
+				/* #ifdef H5 */
+				padding-top: var(--window-top);
+				/* #endif */
+				/* #ifndef H5 */
+				padding-top: 0;
+				/* #endif */
+				flex: 1;
+			}
+		}
+	}
+
+	.fixforpc-z-index {
+		/* #ifndef APP-NVUE */
+		z-index: 999;
+		/* #endif */
+	}
+
+	.fixforpc-top {
+		top: 0;
+	}
+</style>

+ 87 - 0
uni_modules/uni-popup/package.json

@@ -0,0 +1,87 @@
+{
+	"id": "uni-popup",
+	"displayName": "uni-popup 弹出层",
+	"version": "1.8.3",
+	"description": " Popup 组件,提供常用的弹层",
+	"keywords": [
+        "uni-ui",
+        "弹出层",
+        "弹窗",
+        "popup",
+        "弹框"
+    ],
+	"repository": "https://github.com/dcloudio/uni-ui",
+	"engines": {
+		"HBuilderX": ""
+	},
+	"directories": {
+		"example": "../../temps/example_temps"
+	},
+    "dcloudext": {
+        "sale": {
+			"regular": {
+				"price": "0.00"
+			},
+			"sourcecode": {
+				"price": "0.00"
+			}
+		},
+		"contact": {
+			"qq": ""
+		},
+		"declaration": {
+			"ads": "无",
+			"data": "无",
+			"permissions": "无"
+		},
+        "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+        "type": "component-vue"
+	},
+	"uni_modules": {
+		"dependencies": [
+			"uni-scss",
+			"uni-transition"
+		],
+		"encrypt": [],
+		"platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+                },
+                "Vue": {
+                    "vue2": "y",
+                    "vue3": "y"
+                }
+			}
+		}
+	}
+}

+ 17 - 0
uni_modules/uni-popup/readme.md

@@ -0,0 +1,17 @@
+
+
+## Popup 弹出层
+> **组件名:uni-popup**
+> 代码块: `uPopup`
+> 关联组件:`uni-transition`
+
+
+弹出层组件,在应用中弹出一个消息提示窗口、提示框等
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 
+
+
+
+
+

+ 22 - 0
uni_modules/uni-transition/changelog.md

@@ -0,0 +1,22 @@
+## 1.3.2(2023-05-04)
+- 修复 NVUE 平台报错的问题
+## 1.3.1(2021-11-23)
+- 修复 init 方法初始化问题
+## 1.3.0(2021-11-19)
+- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource)
+- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition)
+## 1.2.1(2021-09-27)
+- 修复 init 方法不生效的 Bug
+## 1.2.0(2021-07-30)
+- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834)
+## 1.1.1(2021-05-12)
+- 新增 示例地址
+- 修复 示例项目缺少组件的 Bug
+## 1.1.0(2021-04-22)
+- 新增 通过方法自定义动画
+- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式
+- 优化 动画触发逻辑,使动画更流畅
+- 优化 支持单独的动画类型
+- 优化 文档示例
+## 1.0.2(2021-02-05)
+- 调整为 uni_modules 目录规范

+ 131 - 0
uni_modules/uni-transition/components/uni-transition/createAnimation.js

@@ -0,0 +1,131 @@
+// const defaultOption = {
+// 	duration: 300,
+// 	timingFunction: 'linear',
+// 	delay: 0,
+// 	transformOrigin: '50% 50% 0'
+// }
+// #ifdef APP-NVUE
+const nvueAnimation = uni.requireNativePlugin('animation')
+// #endif
+class MPAnimation {
+	constructor(options, _this) {
+		this.options = options
+		// 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误
+		this.animation = uni.createAnimation({
+			...options
+		})
+		this.currentStepAnimates = {}
+		this.next = 0
+		this.$ = _this
+
+	}
+
+	_nvuePushAnimates(type, args) {
+		let aniObj = this.currentStepAnimates[this.next]
+		let styles = {}
+		if (!aniObj) {
+			styles = {
+				styles: {},
+				config: {}
+			}
+		} else {
+			styles = aniObj
+		}
+		if (animateTypes1.includes(type)) {
+			if (!styles.styles.transform) {
+				styles.styles.transform = ''
+			}
+			let unit = ''
+			if(type === 'rotate'){
+				unit = 'deg'
+			}
+			styles.styles.transform += `${type}(${args+unit}) `
+		} else {
+			styles.styles[type] = `${args}`
+		}
+		this.currentStepAnimates[this.next] = styles
+	}
+	_animateRun(styles = {}, config = {}) {
+		let ref = this.$.$refs['ani'].ref
+		if (!ref) return
+		return new Promise((resolve, reject) => {
+			nvueAnimation.transition(ref, {
+				styles,
+				...config
+			}, res => {
+				resolve()
+			})
+		})
+	}
+
+	_nvueNextAnimate(animates, step = 0, fn) {
+		let obj = animates[step]
+		if (obj) {
+			let {
+				styles,
+				config
+			} = obj
+			this._animateRun(styles, config).then(() => {
+				step += 1
+				this._nvueNextAnimate(animates, step, fn)
+			})
+		} else {
+			this.currentStepAnimates = {}
+			typeof fn === 'function' && fn()
+			this.isEnd = true
+		}
+	}
+
+	step(config = {}) {
+		// #ifndef APP-NVUE
+		this.animation.step(config)
+		// #endif
+		// #ifdef APP-NVUE
+		this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config)
+		this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin
+		this.next++
+		// #endif
+		return this
+	}
+
+	run(fn) {
+		// #ifndef APP-NVUE
+		this.$.animationData = this.animation.export()
+		this.$.timer = setTimeout(() => {
+			typeof fn === 'function' && fn()
+		}, this.$.durationTime)
+		// #endif
+		// #ifdef APP-NVUE
+		this.isEnd = false
+		let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref
+		if(!ref) return
+		this._nvueNextAnimate(this.currentStepAnimates, 0, fn)
+		this.next = 0
+		// #endif
+	}
+}
+
+
+const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d',
+	'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY',
+	'translateZ'
+]
+const animateTypes2 = ['opacity', 'backgroundColor']
+const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom']
+animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => {
+	MPAnimation.prototype[type] = function(...args) {
+		// #ifndef APP-NVUE
+		this.animation[type](...args)
+		// #endif
+		// #ifdef APP-NVUE
+		this._nvuePushAnimates(type, args)
+		// #endif
+		return this
+	}
+})
+
+export function createAnimation(option, _this) {
+	if(!_this) return
+	clearTimeout(_this.timer)
+	return new MPAnimation(option, _this)
+}

+ 286 - 0
uni_modules/uni-transition/components/uni-transition/uni-transition.vue

@@ -0,0 +1,286 @@
+<template>
+  <!-- #ifndef APP-NVUE -->
+  <view v-show="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
+  <!-- #endif -->
+  <!-- #ifdef APP-NVUE -->
+  <view v-if="isShow" ref="ani" :animation="animationData" :class="customClass" :style="transformStyles" @click="onClick"><slot></slot></view>
+  <!-- #endif -->
+</template>
+
+<script>
+import { createAnimation } from './createAnimation'
+
+/**
+ * Transition 过渡动画
+ * @description 简单过渡动画组件
+ * @tutorial https://ext.dcloud.net.cn/plugin?id=985
+ * @property {Boolean} show = [false|true] 控制组件显示或隐藏
+ * @property {Array|String} modeClass = [fade|slide-top|slide-right|slide-bottom|slide-left|zoom-in|zoom-out] 过渡动画类型
+ *  @value fade 渐隐渐出过渡
+ *  @value slide-top 由上至下过渡
+ *  @value slide-right 由右至左过渡
+ *  @value slide-bottom 由下至上过渡
+ *  @value slide-left 由左至右过渡
+ *  @value zoom-in 由小到大过渡
+ *  @value zoom-out 由大到小过渡
+ * @property {Number} duration 过渡动画持续时间
+ * @property {Object} styles 组件样式,同 css 样式,注意带’-‘连接符的属性需要使用小驼峰写法如:`backgroundColor:red`
+ */
+export default {
+	name: 'uniTransition',
+	emits:['click','change'],
+	props: {
+		show: {
+			type: Boolean,
+			default: false
+		},
+		modeClass: {
+			type: [Array, String],
+			default() {
+				return 'fade'
+			}
+		},
+		duration: {
+			type: Number,
+			default: 300
+		},
+		styles: {
+			type: Object,
+			default() {
+				return {}
+			}
+		},
+		customClass:{
+			type: String,
+			default: ''
+		},
+		onceRender:{
+			type:Boolean,
+			default:false
+		},
+	},
+	data() {
+		return {
+			isShow: false,
+			transform: '',
+			opacity: 1,
+			animationData: {},
+			durationTime: 300,
+			config: {}
+		}
+	},
+	watch: {
+		show: {
+			handler(newVal) {
+				if (newVal) {
+					this.open()
+				} else {
+					// 避免上来就执行 close,导致动画错乱
+					if (this.isShow) {
+						this.close()
+					}
+				}
+			},
+			immediate: true
+		}
+	},
+	computed: {
+		// 生成样式数据
+		stylesObject() {
+			let styles = {
+				...this.styles,
+				'transition-duration': this.duration / 1000 + 's'
+			}
+			let transform = ''
+			for (let i in styles) {
+				let line = this.toLine(i)
+				transform += line + ':' + styles[i] + ';'
+			}
+			return transform
+		},
+		// 初始化动画条件
+		transformStyles() {
+			return 'transform:' + this.transform + ';' + 'opacity:' + this.opacity + ';' + this.stylesObject
+		}
+	},
+	created() {
+		// 动画默认配置
+		this.config = {
+			duration: this.duration,
+			timingFunction: 'ease',
+			transformOrigin: '50% 50%',
+			delay: 0
+		}
+		this.durationTime = this.duration
+	},
+	methods: {
+		/**
+		 *  ref 触发 初始化动画
+		 */
+		init(obj = {}) {
+			if (obj.duration) {
+				this.durationTime = obj.duration
+			}
+			this.animation = createAnimation(Object.assign(this.config, obj),this)
+		},
+		/**
+		 * 点击组件触发回调
+		 */
+		onClick() {
+			this.$emit('click', {
+				detail: this.isShow
+			})
+		},
+		/**
+		 * ref 触发 动画分组
+		 * @param {Object} obj
+		 */
+		step(obj, config = {}) {
+			if (!this.animation) return
+			for (let i in obj) {
+				try {
+					if(typeof obj[i] === 'object'){
+						this.animation[i](...obj[i])
+					}else{
+						this.animation[i](obj[i])
+					}
+				} catch (e) {
+					console.error(`方法 ${i} 不存在`)
+				}
+			}
+			this.animation.step(config)
+			return this
+		},
+		/**
+		 *  ref 触发 执行动画
+		 */
+		run(fn) {
+			if (!this.animation) return
+			this.animation.run(fn)
+		},
+		// 开始过度动画
+		open() {
+			clearTimeout(this.timer)
+			this.transform = ''
+			this.isShow = true
+			let { opacity, transform } = this.styleInit(false)
+			if (typeof opacity !== 'undefined') {
+				this.opacity = opacity
+			}
+			this.transform = transform
+			// 确保动态样式已经生效后,执行动画,如果不加 nextTick ,会导致 wx 动画执行异常
+			this.$nextTick(() => {
+				// TODO 定时器保证动画完全执行,目前有些问题,后面会取消定时器
+				this.timer = setTimeout(() => {
+					this.animation = createAnimation(this.config, this)
+					this.tranfromInit(false).step()
+					this.animation.run()
+					this.$emit('change', {
+						detail: this.isShow
+					})
+				}, 20)
+			})
+		},
+		// 关闭过度动画
+		close(type) {
+			if (!this.animation) return
+			this.tranfromInit(true)
+				.step()
+				.run(() => {
+					this.isShow = false
+					this.animationData = null
+					this.animation = null
+					let { opacity, transform } = this.styleInit(false)
+					this.opacity = opacity || 1
+					this.transform = transform
+					this.$emit('change', {
+						detail: this.isShow
+					})
+				})
+		},
+		// 处理动画开始前的默认样式
+		styleInit(type) {
+			let styles = {
+				transform: ''
+			}
+			let buildStyle = (type, mode) => {
+				if (mode === 'fade') {
+					styles.opacity = this.animationType(type)[mode]
+				} else {
+					styles.transform += this.animationType(type)[mode] + ' '
+				}
+			}
+			if (typeof this.modeClass === 'string') {
+				buildStyle(type, this.modeClass)
+			} else {
+				this.modeClass.forEach(mode => {
+					buildStyle(type, mode)
+				})
+			}
+			return styles
+		},
+		// 处理内置组合动画
+		tranfromInit(type) {
+			let buildTranfrom = (type, mode) => {
+				let aniNum = null
+				if (mode === 'fade') {
+					aniNum = type ? 0 : 1
+				} else {
+					aniNum = type ? '-100%' : '0'
+					if (mode === 'zoom-in') {
+						aniNum = type ? 0.8 : 1
+					}
+					if (mode === 'zoom-out') {
+						aniNum = type ? 1.2 : 1
+					}
+					if (mode === 'slide-right') {
+						aniNum = type ? '100%' : '0'
+					}
+					if (mode === 'slide-bottom') {
+						aniNum = type ? '100%' : '0'
+					}
+				}
+				this.animation[this.animationMode()[mode]](aniNum)
+			}
+			if (typeof this.modeClass === 'string') {
+				buildTranfrom(type, this.modeClass)
+			} else {
+				this.modeClass.forEach(mode => {
+					buildTranfrom(type, mode)
+				})
+			}
+
+			return this.animation
+		},
+		animationType(type) {
+			return {
+				fade: type ? 1 : 0,
+				'slide-top': `translateY(${type ? '0' : '-100%'})`,
+				'slide-right': `translateX(${type ? '0' : '100%'})`,
+				'slide-bottom': `translateY(${type ? '0' : '100%'})`,
+				'slide-left': `translateX(${type ? '0' : '-100%'})`,
+				'zoom-in': `scaleX(${type ? 1 : 0.8}) scaleY(${type ? 1 : 0.8})`,
+				'zoom-out': `scaleX(${type ? 1 : 1.2}) scaleY(${type ? 1 : 1.2})`
+			}
+		},
+		// 内置动画类型与实际动画对应字典
+		animationMode() {
+			return {
+				fade: 'opacity',
+				'slide-top': 'translateY',
+				'slide-right': 'translateX',
+				'slide-bottom': 'translateY',
+				'slide-left': 'translateX',
+				'zoom-in': 'scale',
+				'zoom-out': 'scale'
+			}
+		},
+		// 驼峰转中横线
+		toLine(name) {
+			return name.replace(/([A-Z])/g, '-$1').toLowerCase()
+		}
+	}
+}
+</script>
+
+<style></style>

+ 84 - 0
uni_modules/uni-transition/package.json

@@ -0,0 +1,84 @@
+{
+  "id": "uni-transition",
+  "displayName": "uni-transition 过渡动画",
+  "version": "1.3.2",
+  "description": "元素的简单过渡动画",
+  "keywords": [
+    "uni-ui",
+    "uniui",
+    "动画",
+    "过渡",
+    "过渡动画"
+],
+  "repository": "https://github.com/dcloudio/uni-ui",
+  "engines": {
+    "HBuilderX": ""
+  },
+  "directories": {
+    "example": "../../temps/example_temps"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui",
+    "type": "component-vue"
+  },
+  "uni_modules": {
+    "dependencies": ["uni-scss"],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "u",
+          "联盟": "u"
+        },
+        "Vue": {
+            "vue2": "y",
+            "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 11 - 0
uni_modules/uni-transition/readme.md

@@ -0,0 +1,11 @@
+
+
+## Transition 过渡动画
+> **组件名:uni-transition**
+> 代码块: `uTransition`
+
+
+元素过渡动画
+
+### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition)
+#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 

+ 15 - 0
uni_modules/uv-button/changelog.md

@@ -0,0 +1,15 @@
+## 1.0.6(2023-07-25)
+1. 增加customTextStyle属性,方便自定义文字样式
+## 1.0.5(2023-07-20)
+1. 解决微信小程序动态设置hover-class点击态不消失的BUG
+## 1.0.4(2023-06-29)
+1. 修改上次更新出现nvue报错异常
+## 1.0.3(2023-06-28)
+ 修复:设置open-type="chooseAvatar"等值不生效的BUG
+## 1.0.2(2023-06-01)
+1. 修复按钮点击触发两次的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-button 按钮

+ 46 - 0
uni_modules/uv-button/components/uv-button/nvue.scss

@@ -0,0 +1,46 @@
+$uv-button-active-opacity:0.75 !default;
+$uv-button-loading-text-margin-left:4px !default;
+$uv-button-text-color: #FFFFFF !default;
+$uv-button-text-plain-error-color:$uv-error !default;
+$uv-button-text-plain-warning-color:$uv-warning !default;
+$uv-button-text-plain-success-color:$uv-success !default;
+$uv-button-text-plain-info-color:$uv-info !default;
+$uv-button-text-plain-primary-color:$uv-primary !default;
+.uv-button {
+	&--active {
+		opacity: $uv-button-active-opacity;
+	}
+	
+	&--active--plain {
+		background-color: rgb(217, 217, 217);
+	}
+	
+	&__loading-text {
+		margin-left:$uv-button-loading-text-margin-left;
+	}
+	
+	&__text,
+	&__loading-text {
+		color:$uv-button-text-color;
+	}
+	
+	&__text--plain--error {
+		color:$uv-button-text-plain-error-color;
+	}
+	
+	&__text--plain--warning {
+		color:$uv-button-text-plain-warning-color;
+	}
+	
+	&__text--plain--success{
+		color:$uv-button-text-plain-success-color;
+	}
+	
+	&__text--plain--info {
+		color:$uv-button-text-plain-info-color;
+	}
+	
+	&__text--plain--primary {
+		color:$uv-button-text-plain-primary-color;
+	}
+}

+ 158 - 0
uni_modules/uv-button/components/uv-button/props.js

@@ -0,0 +1,158 @@
+export default {
+	props: {
+		// 是否细边框
+		hairline: {
+			type: Boolean,
+			default: true
+		},
+		// 按钮的预置样式,info,primary,error,warning,success
+		type: {
+			type: String,
+			default: 'info'
+		},
+		// 按钮尺寸,large,normal,small,mini
+		size: {
+			type: String,
+			default: 'normal'
+		},
+		// 按钮形状,circle(两边为半圆),square(带圆角)
+		shape: {
+			type: String,
+			default: 'square'
+		},
+		// 按钮是否镂空
+		plain: {
+			type: Boolean,
+			default: false
+		},
+		// 是否禁止状态
+		disabled: {
+			type: Boolean,
+			default: false
+		},
+		// 是否加载中
+		loading: {
+			type: Boolean,
+			default: false
+		},
+		// 加载中提示文字
+		loadingText: {
+			type: [String, Number],
+			default: ''
+		},
+		// 加载状态图标类型
+		loadingMode: {
+			type: String,
+			default: 'spinner'
+		},
+		// 加载图标大小
+		loadingSize: {
+			type: [String, Number],
+			default: 14
+		},
+		// 开放能力,具体请看uniapp稳定关于button组件部分说明
+		// https://uniapp.dcloud.io/component/button
+		openType: {
+			type: String,
+			default: ''
+		},
+		// 用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+		// 取值为submit(提交表单),reset(重置表单)
+		formType: {
+			type: String,
+			default: ''
+		},
+		// 打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效
+		// 只微信小程序、QQ小程序有效
+		appParameter: {
+			type: String,
+			default: ''
+		},
+		// 指定是否阻止本节点的祖先节点出现点击态,微信小程序有效
+		hoverStopPropagation: {
+			type: Boolean,
+			default: true
+		},
+		// 指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文。只微信小程序有效
+		lang: {
+			type: String,
+			default: 'en'
+		},
+		// 会话来源,open-type="contact"时有效。只微信小程序有效
+		sessionFrom: {
+			type: String,
+			default: ''
+		},
+		// 会话内消息卡片标题,open-type="contact"时有效
+		// 默认当前标题,只微信小程序有效
+		sendMessageTitle: {
+			type: String,
+			default: ''
+		},
+		// 会话内消息卡片点击跳转小程序路径,open-type="contact"时有效
+		// 默认当前分享路径,只微信小程序有效
+		sendMessagePath: {
+			type: String,
+			default: ''
+		},
+		// 会话内消息卡片图片,open-type="contact"时有效
+		// 默认当前页面截图,只微信小程序有效
+		sendMessageImg: {
+			type: String,
+			default: ''
+		},
+		// 是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,
+		// 用户点击后可以快速发送小程序消息,open-type="contact"时有效
+		showMessageCard: {
+			type: Boolean,
+			default: true
+		},
+		// 额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+		dataName: {
+			type: String,
+			default: ''
+		},
+		// 节流,一定时间内只能触发一次
+		throttleTime: {
+			type: [String, Number],
+			default: 0
+		},
+		// 按住后多久出现点击态,单位毫秒
+		hoverStartTime: {
+			type: [String, Number],
+			default: 0
+		},
+		// 手指松开后点击态保留时间,单位毫秒
+		hoverStayTime: {
+			type: [String, Number],
+			default: 200
+		},
+		// 按钮文字,之所以通过props传入,是因为slot传入的话
+		// nvue中无法控制文字的样式
+		text: {
+			type: [String, Number],
+			default: ''
+		},
+		// 按钮图标
+		icon: {
+			type: String,
+			default: ''
+		},
+		// 按钮图标颜色
+		iconColor: {
+			type: String,
+			default: '#000000'
+		},
+		// 按钮颜色,支持传入linear-gradient渐变色
+		color: {
+			type: String,
+			default: ''
+		},
+		// 自定义按钮文本样式
+		customTextStyle: {
+			type: [Object,String],
+			default: ()=>{}
+		},
+		...uni.$uv?.props?.button
+	}
+}

+ 510 - 0
uni_modules/uv-button/components/uv-button/uv-button.vue

@@ -0,0 +1,510 @@
+<template>
+	<view class="uv-button-wrapper">
+    <!-- #ifndef APP-NVUE -->
+		<!-- #ifdef MP -->
+		<!-- 为了解决微信小程序动态设置hover-class点击态不消失的BUG -->
+		<view class="uv-button-wrapper--dis" v-if="disabled || loading"></view>
+		<button
+		  :hover-start-time="Number(hoverStartTime)"
+		  :hover-stay-time="Number(hoverStayTime)"
+		  :form-type="formType"
+		  :open-type="openType"
+		  :app-parameter="appParameter"
+		  :hover-stop-propagation="hoverStopPropagation"
+		  :send-message-title="sendMessageTitle"
+		  :send-message-path="sendMessagePath"
+		  :lang="lang"
+		  :data-name="dataName"
+		  :session-from="sessionFrom"
+		  :send-message-img="sendMessageImg"
+		  :show-message-card="showMessageCard"
+		  @getphonenumber="onGetPhoneNumber"
+		  @getuserinfo="onGetUserInfo"
+		  @error="onError"
+		  @opensetting="onOpenSetting"
+		  @launchapp="onLaunchApp"
+			@contact="onContact"
+			@chooseavatar="onChooseavatar"
+			@addgroupapp="onAddgroupapp"
+			@chooseaddress="onChooseaddress"
+			@subscribe="onSubscribe"
+			@login="onLogin"
+			@im="onIm"
+		  hover-class="uv-button--active"
+		  class="uv-button uv-reset-button"
+		  :style="[baseColor, $uv.addStyle(customStyle)]"
+		  @tap="clickHandler"
+		  :class="bemClass"
+		>
+		<!-- #endif -->
+    <!-- #ifndef MP -->
+    <button
+      :hover-start-time="Number(hoverStartTime)"
+      :hover-stay-time="Number(hoverStayTime)"
+      :form-type="formType"
+      :open-type="openType"
+      :app-parameter="appParameter"
+      :hover-stop-propagation="hoverStopPropagation"
+      :send-message-title="sendMessageTitle"
+      :send-message-path="sendMessagePath"
+      :lang="lang"
+      :data-name="dataName"
+      :session-from="sessionFrom"
+      :send-message-img="sendMessageImg"
+      :show-message-card="showMessageCard"
+      :hover-class="!disabled && !loading ? 'uv-button--active' : ''"
+      class="uv-button uv-reset-button"
+      :style="[baseColor, $uv.addStyle(customStyle)]"
+      @tap="clickHandler"
+      :class="bemClass"
+    >
+    <!-- #endif -->
+      <template v-if="loading">
+        <uv-loading-icon
+          :mode="loadingMode"
+          :size="loadingSize * 1.15"
+          :color="loadingColor"
+        ></uv-loading-icon>
+          <text
+            class="uv-button__loading-text"
+            :style="[
+							{ fontSize: textSize + 'px' },
+							$uv.addStyle(customTextStyle)
+						]"
+          >{{ loadingText || text }}</text>
+      </template>
+      <template v-else>
+        <uv-icon
+          v-if="icon"
+          :name="icon"
+          :color="iconColorCom"
+          :size="textSize * 1.35"
+          :customStyle="{ marginRight: '2px' }"
+        ></uv-icon>
+        <slot>
+          <text
+            class="uv-button__text"
+            :style="[
+							{ fontSize: textSize + 'px' },
+							$uv.addStyle(customTextStyle)
+						]"
+            >{{ text }}</text>
+        </slot>
+      </template>
+    </button>
+    <!-- #endif -->
+    <!-- #ifdef APP-NVUE -->
+    <view
+      :hover-start-time="Number(hoverStartTime)"
+      :hover-stay-time="Number(hoverStayTime)"
+      class="uv-button"
+      :hover-class="
+        !disabled && !loading && !color && (plain || type === 'info')
+          ? 'uv-button--active--plain'
+          : !disabled && !loading && !plain
+          ? 'uv-button--active'
+          : ''
+      "
+      @tap="clickHandler"
+      :class="bemClass"
+      :style="[baseColor, $uv.addStyle(customStyle)]"
+    >
+      <template v-if="loading">
+        <uv-loading-icon
+          :mode="loadingMode"
+          :size="loadingSize * 1.15"
+          :color="loadingColor"
+        ></uv-loading-icon>
+        <text
+          class="uv-button__loading-text"
+          :style="[nvueTextStyle,$uv.addStyle(customTextStyle)]"
+          :class="[plain && `uv-button__text--plain--${type}`]"
+          >{{ loadingText || text }}</text>
+      </template>
+      <template v-else>
+        <uv-icon
+          v-if="icon"
+          :name="icon"
+          :color="iconColorCom"
+          :size="textSize * 1.35"
+        ></uv-icon>
+        <text
+          class="uv-button__text"
+          :style="[
+            {
+              marginLeft: icon ? '2px' : 0,
+            },
+            nvueTextStyle,
+						$uv.addStyle(customTextStyle)
+          ]"
+          :class="[plain && `uv-button__text--plain--${type}`]"
+          >{{ text }}</text>
+      </template>
+    </view>
+    <!-- #endif -->
+	</view>
+</template>
+
+<script>
+import throttle from '@/uni_modules/uv-ui-tools/libs/function/throttle.js';
+import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+import button from '@/uni_modules/uv-ui-tools/libs/mixin/button.js'
+import openType from '@/uni_modules/uv-ui-tools/libs/mixin/openType.js'
+import props from "./props.js";
+/**
+ * button 按钮
+ * @description Button 按钮
+ * @tutorial https://www.uvui.cn/components/button.html
+ * @property {Boolean}			hairline				是否显示按钮的细边框 (默认 true )
+ * @property {String}			type					按钮的预置样式,info,primary,error,warning,success (默认 'info' )
+ * @property {String}			size					按钮尺寸,large,normal,mini (默认 normal)
+ * @property {String}			shape					按钮形状,circle(两边为半圆),square(带圆角) (默认 'square' )
+ * @property {Boolean}			plain					按钮是否镂空,背景色透明 (默认 false)
+ * @property {Boolean}			disabled				是否禁用 (默认 false)
+ * @property {Boolean}			loading					按钮名称前是否带 loading 图标(App-nvue 平台,在 ios 上为雪花,Android上为圆圈) (默认 false)
+ * @property {String | Number}	loadingText				加载中提示文字
+ * @property {String}			loadingMode				加载状态图标类型 (默认 'spinner' )
+ * @property {String | Number}	loadingSize				加载图标大小 (默认 15 )
+ * @property {String}			openType				开放能力,具体请看uniapp稳定关于button组件部分说明
+ * @property {String}			formType				用于 <form> 组件,点击分别会触发 <form> 组件的 submit/reset 事件
+ * @property {String}			appParameter			打开 APP 时,向 APP 传递的参数,open-type=launchApp时有效 (注:只微信小程序、QQ小程序有效)
+ * @property {Boolean}			hoverStopPropagation	指定是否阻止本节点的祖先节点出现点击态,微信小程序有效(默认 true )
+ * @property {String}			lang					指定返回用户信息的语言,zh_CN 简体中文,zh_TW 繁体中文,en 英文(默认 en )
+ * @property {String}			sessionFrom				会话来源,openType="contact"时有效
+ * @property {String}			sendMessageTitle		会话内消息卡片标题,openType="contact"时有效
+ * @property {String}			sendMessagePath			会话内消息卡片点击跳转小程序路径,openType="contact"时有效
+ * @property {String}			sendMessageImg			会话内消息卡片图片,openType="contact"时有效
+ * @property {Boolean}			showMessageCard			是否显示会话内消息卡片,设置此参数为 true,用户进入客服会话会在右下角显示"可能要发送的小程序"提示,用户点击后可以快速发送小程序消息,openType="contact"时有效(默认false)
+ * @property {String}			dataName				额外传参参数,用于小程序的data-xxx属性,通过target.dataset.name获取
+ * @property {String | Number}	throttleTime			节流,一定时间内只能触发一次 (默认 0 )
+ * @property {String | Number}	hoverStartTime			按住后多久出现点击态,单位毫秒 (默认 0 )
+ * @property {String | Number}	hoverStayTime			手指松开后点击态保留时间,单位毫秒 (默认 200 )
+ * @property {String | Number}	text					按钮文字,之所以通过props传入,是因为slot传入的话(注:nvue中无法控制文字的样式)
+ * @property {String}			icon					按钮图标
+ * @property {String}			iconColor				按钮图标颜色
+ * @property {String}			color					按钮颜色,支持传入linear-gradient渐变色
+ * @property {Object}			customStyle				定义需要用到的外部样式
+ * @event {Function}	click			非禁止并且非加载中,才能点击
+ * @event {Function}	getphonenumber	open-type="getPhoneNumber"时有效
+ * @event {Function}	getuserinfo		用户点击该按钮时,会返回获取到的用户信息,从返回参数的detail中获取到的值同uni.getUserInfo
+ * @event {Function}	error			当使用开放能力时,发生错误的回调
+ * @event {Function}	opensetting		在打开授权设置页并关闭后回调
+ * @event {Function}	launchapp		打开 APP 成功的回调
+ * @example <uv-button>月落</uv-button>
+ */
+export default {
+		name: "uv-button",
+		// #ifdef MP
+		mixins: [mpMixin, mixin, button, openType, props],
+		// #endif
+		// #ifndef MP
+		mixins: [mpMixin, mixin, props],
+		// #endif
+		emits: ['click'],
+		data() {
+			return {};
+		},
+		computed: {
+			// 生成bem风格的类名
+			bemClass() {
+				// this.bem为一个computed变量,在mixin中
+				if (!this.color) {
+					return this.bem("button",
+						["type", "shape", "size"],
+						["disabled", "plain", "hairline"]);
+				} else {
+					// 由于nvue的原因,在有color参数时,不需要传入type,否则会生成type相关的类型,影响最终的样式
+					return this.bem("button",
+						["shape", "size"],
+						["disabled", "plain", "hairline"]);
+				}
+			},
+			loadingColor() {
+				if (this.plain) {
+					// 如果有设置color值,则用color值,否则使用type主题颜色
+					return this.color ? this.color : '#3c9cff';
+				}
+				if (this.type === "info") {
+					return "#c9c9c9";
+				}
+				return "rgb(200, 200, 200)";
+			},
+			iconColorCom() {
+				// 如果是镂空状态,设置了color就用color值,否则使用主题颜色,
+				// uv-icon的color能接受一个主题颜色的值
+				if (this.iconColor) return this.iconColor;
+				if (this.plain) {
+					return this.color ? this.color : this.type;
+				} else {
+					return this.type === "info" ? "#000000" : "#ffffff";
+				}
+			},
+			baseColor() {
+				let style = {};
+				if (this.color) {
+					// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+					style.color = this.plain ? this.color : "white";
+					if (!this.plain) {
+						// 非镂空,背景色使用自定义的颜色
+						style["background-color"] = this.color;
+					}
+					if (this.color.indexOf("gradient") !== -1) {
+						// 如果自定义的颜色为渐变色,不显示边框,以及通过backgroundImage设置渐变色
+						// weex文档说明可以写borderWidth的形式,为什么这里需要分开写?
+						// 因为weex是阿里巴巴为了部门业绩考核而做的你懂的东西,所以需要这么写才有效
+						style.borderTopWidth = 0;
+						style.borderRightWidth = 0;
+						style.borderBottomWidth = 0;
+						style.borderLeftWidth = 0;
+						if (!this.plain) {
+							style.backgroundImage = this.color;
+						}
+					} else {
+						// 非渐变色,则设置边框相关的属性
+						style.borderColor = this.color;
+						style.borderWidth = "1px";
+						style.borderStyle = "solid";
+					}
+				}
+				return style;
+			},
+			// nvue版本按钮的字体不会继承父组件的颜色,需要对每一个text组件进行单独的设置
+			nvueTextStyle() {
+				let style = {};
+				// 针对自定义了color颜色的情况,镂空状态下,就是用自定义的颜色
+				if (this.type === "info") {
+					style.color = "#323233";
+				}
+				if (this.color) {
+					style.color = this.plain ? this.color : "white";
+				}
+				style.fontSize = this.textSize + "px";
+				return style;
+			},
+			// 字体大小
+			textSize() {
+				let fontSize = 14,
+					{ size } = this;
+				if (size === "large") fontSize = 16;
+				if (size === "normal") fontSize = 14;
+				if (size === "small") fontSize = 12;
+				if (size === "mini") fontSize = 10;
+				return fontSize;
+			},
+		},
+		methods: {
+			clickHandler() {
+				// 非禁止并且非加载中,才能点击
+				if (!this.disabled && !this.loading) {
+					// 进行节流控制,每this.throttle毫秒内,只在开始处执行
+					throttle(() => {
+						this.$emit("click");
+					}, this.throttleTime);
+				}
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+$show-reset-button: 1;
+@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+
+/* #ifndef APP-NVUE */
+@import "./vue.scss";
+/* #endif */
+
+/* #ifdef APP-NVUE */
+@import "./nvue.scss";
+/* #endif */
+
+$uv-button-uv-button-height: 40px !default;
+$uv-button-text-font-size: 15px !default;
+$uv-button-loading-text-font-size: 15px !default;
+$uv-button-loading-text-margin-left: 4px !default;
+$uv-button-large-width: 100% !default;
+$uv-button-large-height: 50px !default;
+$uv-button-normal-padding: 0 12px !default;
+$uv-button-large-padding: 0 15px !default;
+$uv-button-normal-font-size: 14px !default;
+$uv-button-small-min-width: 60px !default;
+$uv-button-small-height: 30px !default;
+$uv-button-small-padding: 0px 8px !default;
+$uv-button-mini-padding: 0px 8px !default;
+$uv-button-small-font-size: 12px !default;
+$uv-button-mini-height: 22px !default;
+$uv-button-mini-font-size: 10px !default;
+$uv-button-mini-min-width: 50px !default;
+$uv-button-disabled-opacity: 0.5 !default;
+$uv-button-info-color: #323233 !default;
+$uv-button-info-background-color: #fff !default;
+$uv-button-info-border-color: #ebedf0 !default;
+$uv-button-info-border-width: 1px !default;
+$uv-button-info-border-style: solid !default;
+$uv-button-success-color: #fff !default;
+$uv-button-success-background-color: $uv-success !default;
+$uv-button-success-border-color: $uv-button-success-background-color !default;
+$uv-button-success-border-width: 1px !default;
+$uv-button-success-border-style: solid !default;
+$uv-button-primary-color: #fff !default;
+$uv-button-primary-background-color: $uv-primary !default;
+$uv-button-primary-border-color: $uv-button-primary-background-color !default;
+$uv-button-primary-border-width: 1px !default;
+$uv-button-primary-border-style: solid !default;
+$uv-button-error-color: #fff !default;
+$uv-button-error-background-color: $uv-error !default;
+$uv-button-error-border-color: $uv-button-error-background-color !default;
+$uv-button-error-border-width: 1px !default;
+$uv-button-error-border-style: solid !default;
+$uv-button-warning-color: #fff !default;
+$uv-button-warning-background-color: $uv-warning !default;
+$uv-button-warning-border-color: $uv-button-warning-background-color !default;
+$uv-button-warning-border-width: 1px !default;
+$uv-button-warning-border-style: solid !default;
+$uv-button-block-width: 100% !default;
+$uv-button-circle-border-top-right-radius: 100px !default;
+$uv-button-circle-border-top-left-radius: 100px !default;
+$uv-button-circle-border-bottom-left-radius: 100px !default;
+$uv-button-circle-border-bottom-right-radius: 100px !default;
+$uv-button-square-border-top-right-radius: 3px !default;
+$uv-button-square-border-top-left-radius: 3px !default;
+$uv-button-square-border-bottom-left-radius: 3px !default;
+$uv-button-square-border-bottom-right-radius: 3px !default;
+$uv-button-icon-min-width: 1em !default;
+$uv-button-plain-background-color: #fff !default;
+$uv-button-hairline-border-width: 0.5px !default;
+
+.uv-button {
+    height: $uv-button-uv-button-height;
+    position: relative;
+    align-items: center;
+    justify-content: center;
+    @include flex;
+    /* #ifndef APP-NVUE */
+    box-sizing: border-box;
+    /* #endif */
+    flex-direction: row;
+
+    &__text {
+        font-size: $uv-button-text-font-size;
+    }
+
+    &__loading-text {
+        font-size: $uv-button-loading-text-font-size;
+        margin-left: $uv-button-loading-text-margin-left;
+    }
+
+    &--large {
+        /* #ifndef APP-NVUE */
+        width: $uv-button-large-width;
+        /* #endif */
+        height: $uv-button-large-height;
+        padding: $uv-button-large-padding;
+    }
+
+    &--normal {
+        padding: $uv-button-normal-padding;
+        font-size: $uv-button-normal-font-size;
+    }
+
+    &--small {
+        /* #ifndef APP-NVUE */
+        min-width: $uv-button-small-min-width;
+        /* #endif */
+        height: $uv-button-small-height;
+        padding: $uv-button-small-padding;
+        font-size: $uv-button-small-font-size;
+    }
+
+    &--mini {
+        height: $uv-button-mini-height;
+        font-size: $uv-button-mini-font-size;
+        /* #ifndef APP-NVUE */
+        min-width: $uv-button-mini-min-width;
+        /* #endif */
+        padding: $uv-button-mini-padding;
+    }
+
+    &--disabled {
+        opacity: $uv-button-disabled-opacity;
+    }
+
+    &--info {
+        color: $uv-button-info-color;
+        background-color: $uv-button-info-background-color;
+        border-color: $uv-button-info-border-color;
+        border-width: $uv-button-info-border-width;
+        border-style: $uv-button-info-border-style;
+    }
+
+    &--success {
+        color: $uv-button-success-color;
+        background-color: $uv-button-success-background-color;
+        border-color: $uv-button-success-border-color;
+        border-width: $uv-button-success-border-width;
+        border-style: $uv-button-success-border-style;
+    }
+
+    &--primary {
+        color: $uv-button-primary-color;
+        background-color: $uv-button-primary-background-color;
+        border-color: $uv-button-primary-border-color;
+        border-width: $uv-button-primary-border-width;
+        border-style: $uv-button-primary-border-style;
+    }
+
+    &--error {
+        color: $uv-button-error-color;
+        background-color: $uv-button-error-background-color;
+        border-color: $uv-button-error-border-color;
+        border-width: $uv-button-error-border-width;
+        border-style: $uv-button-error-border-style;
+    }
+
+    &--warning {
+        color: $uv-button-warning-color;
+        background-color: $uv-button-warning-background-color;
+        border-color: $uv-button-warning-border-color;
+        border-width: $uv-button-warning-border-width;
+        border-style: $uv-button-warning-border-style;
+    }
+
+    &--block {
+        @include flex;
+        width: $uv-button-block-width;
+    }
+
+    &--circle {
+        border-top-right-radius: $uv-button-circle-border-top-right-radius;
+        border-top-left-radius: $uv-button-circle-border-top-left-radius;
+        border-bottom-left-radius: $uv-button-circle-border-bottom-left-radius;
+        border-bottom-right-radius: $uv-button-circle-border-bottom-right-radius;
+    }
+
+    &--square {
+        border-bottom-left-radius: $uv-button-square-border-top-right-radius;
+        border-bottom-right-radius: $uv-button-square-border-top-left-radius;
+        border-top-left-radius: $uv-button-square-border-bottom-left-radius;
+        border-top-right-radius: $uv-button-square-border-bottom-right-radius;
+    }
+
+    &__icon {
+        /* #ifndef APP-NVUE */
+        min-width: $uv-button-icon-min-width;
+        line-height: inherit !important;
+        vertical-align: top;
+        /* #endif */
+    }
+
+    &--plain {
+        background-color: $uv-button-plain-background-color;
+    }
+
+    &--hairline {
+        border-width: $uv-button-hairline-border-width !important;
+    }
+}
+</style>

+ 94 - 0
uni_modules/uv-button/components/uv-button/vue.scss

@@ -0,0 +1,94 @@
+@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+// nvue下hover-class无效
+$uv-button-before-top:50% !default;
+$uv-button-before-left:50% !default;
+$uv-button-before-width:100% !default;
+$uv-button-before-height:100% !default;
+$uv-button-before-transform:translate(-50%, -50%) !default;
+$uv-button-before-opacity:0 !default;
+$uv-button-before-background-color:#000 !default;
+$uv-button-before-border-color:#000 !default;
+$uv-button-active-before-opacity:.15 !default;
+$uv-button-icon-margin-left:4px !default;
+$uv-button-plain-uv-button-info-color:$uv-info;
+$uv-button-plain-uv-button-success-color:$uv-success;
+$uv-button-plain-uv-button-error-color:$uv-error;
+$uv-button-plain-uv-button-warning-color:$uv-error;
+
+.uv-button-wrapper {
+	position: relative;
+	width: 100%;
+	&--dis {
+		position: absolute;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		z-index: 9;
+	}
+}
+
+.uv-button {
+	width: 100%;
+	
+	&__text {
+		white-space: nowrap;
+		line-height: 1;
+	}
+	
+	&:before {
+		position: absolute;
+		top:$uv-button-before-top;
+		left:$uv-button-before-left;
+		width:$uv-button-before-width;
+		height:$uv-button-before-height;
+		border: inherit;
+		border-radius: inherit;
+		transform:$uv-button-before-transform;
+		opacity:$uv-button-before-opacity;
+		content: " ";
+		background-color:$uv-button-before-background-color;
+		border-color:$uv-button-before-border-color;
+	}
+	
+	&--active {
+		&:before {
+			opacity: .15
+		}
+	}
+	
+	&__icon+&__text:not(:empty),
+	&__loading-text {
+		margin-left:$uv-button-icon-margin-left;
+	}
+	
+	&--plain {
+		&.uv-button--primary {
+			color: $uv-primary;
+		}
+	}
+	
+	&--plain {
+		&.uv-button--info {
+			color:$uv-button-plain-uv-button-info-color;
+		}
+	}
+	
+	&--plain {
+		&.uv-button--success {
+			color:$uv-button-plain-uv-button-success-color;
+		}
+	}
+	
+	&--plain {
+		&.uv-button--error {
+			color:$uv-button-plain-uv-button-error-color;
+		}
+	}
+	
+	&--plain {
+		&.uv-button--warning {
+			color:$uv-button-plain-uv-button-warning-color;
+		}
+	}
+}

+ 89 - 0
uni_modules/uv-button/package.json

@@ -0,0 +1,89 @@
+{
+	"id": "uv-button",
+	"displayName": "uv-button 按钮 全面兼容vue3+2、app、h5、小程序等多端",
+	"version": "1.0.6",
+	"description": "按钮组件内部实现以uni-app的button组件为基础,进行二次封装,灵活配置,功能齐全,兼容全端。",
+	"keywords": [
+        "uv-button",
+        "uvui",
+        "uv-ui",
+        "button",
+        "按钮"
+    ],
+	"repository": "",
+	"engines": {
+		"HBuilderX": "^3.1.0"
+	},
+	"dcloudext": {
+		"type": "component-vue",
+		"sale": {
+			"regular": {
+				"price": "0.00"
+			},
+			"sourcecode": {
+				"price": "0.00"
+			}
+		},
+		"contact": {
+			"qq": ""
+		},
+		"declaration": {
+			"ads": "无",
+			"data": "插件不采集任何数据",
+			"permissions": "无"
+		},
+		"npmurl": ""
+	},
+	"uni_modules": {
+		"dependencies": [
+			"uv-ui-tools",
+			"uv-loading-icon",
+			"uv-icon"
+		],
+		"encrypt": [],
+		"platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+	}
+}

+ 19 - 0
uni_modules/uv-button/readme.md

@@ -0,0 +1,19 @@
+## Button 按钮
+
+> **组件名:uv-button**
+
+该组件内部实现以`uni-app`的`button`组件为基础,进行二次封装,灵活配置,功能齐全,兼容全端。灵活配置,内置状态设置,开箱即用。
+
+# <a href="https://www.uvui.cn/components/button.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 14 - 0
uni_modules/uv-calendar/changelog.md

@@ -0,0 +1,14 @@
+## 1.0.5(2023-07-02)
+uv-calendar  由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/calendar.html
+## 1.0.4(2023-06-15)
+1. formatter格式化中增加topInfo参数
+## 1.0.3(2023-06-08)
+1. 增加点击日期change回调
+2. 优化
+## 1.0.2(2023-06-05)
+1. 修改多个时间选择的时候存在反选的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-calendar 日历

+ 546 - 0
uni_modules/uv-calendar/components/uv-calendar/calendar.js

@@ -0,0 +1,546 @@
+/**
+* @1900-2100区间内的公历、农历互转
+* @charset UTF-8
+* @github  https://github.com/jjonline/calendar.js
+* @Author  Jea杨(JJonline@JJonline.Cn)
+* @Time    2014-7-21
+* @Time    2016-8-13 Fixed 2033hex、Attribution Annals
+* @Time    2016-9-25 Fixed lunar LeapMonth Param Bug
+* @Time    2017-7-24 Fixed use getTerm Func Param Error.use solar year,NOT lunar year
+* @Version 1.0.3
+* @公历转农历:calendar.solar2lunar(1987,11,01); //[you can ignore params of prefix 0]
+* @农历转公历:calendar.lunar2solar(1987,09,10); //[you can ignore params of prefix 0]
+*/
+/* eslint-disable */
+var calendar = {
+
+    /**
+        * 农历1900-2100的润大小信息表
+        * @Array Of Property
+        * @return Hex
+        */
+    lunarInfo: [0x04bd8, 0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950, 0x16554, 0x056a0, 0x09ad0, 0x055d2, // 1900-1909
+        0x04ae0, 0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540, 0x0d6a0, 0x0ada2, 0x095b0, 0x14977, // 1910-1919
+        0x04970, 0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54, 0x02b60, 0x09570, 0x052f2, 0x04970, // 1920-1929
+        0x06566, 0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60, 0x186e3, 0x092e0, 0x1c8d7, 0x0c950, // 1930-1939
+        0x0d4a0, 0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0, 0x092d0, 0x0d2b2, 0x0a950, 0x0b557, // 1940-1949
+        0x06ca0, 0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573, 0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, // 1950-1959
+        0x0aea6, 0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260, 0x0f263, 0x0d950, 0x05b57, 0x056a0, // 1960-1969
+        0x096d0, 0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250, 0x0d558, 0x0b540, 0x0b6a0, 0x195a6, // 1970-1979
+        0x095b0, 0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50, 0x06d40, 0x0af46, 0x0ab60, 0x09570, // 1980-1989
+        0x04af5, 0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58, 0x05ac0, 0x0ab60, 0x096d5, 0x092e0, // 1990-1999
+        0x0c960, 0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0, 0x0abb7, 0x025d0, 0x092d0, 0x0cab5, // 2000-2009
+        0x0a950, 0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0, 0x0a5b0, 0x15176, 0x052b0, 0x0a930, // 2010-2019
+        0x07954, 0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6, 0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, // 2020-2029
+        0x05aa0, 0x076a3, 0x096d0, 0x04afb, 0x04ad0, 0x0a4d0, 0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, // 2030-2039
+        0x0b5a0, 0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0, 0x0aa50, 0x1b255, 0x06d20, 0x0ada0, // 2040-2049
+        /** Add By JJonline@JJonline.Cn**/
+        0x14b63, 0x09370, 0x049f8, 0x04970, 0x064b0, 0x168a6, 0x0ea50, 0x06b20, 0x1a6c4, 0x0aae0, // 2050-2059
+        0x0a2e0, 0x0d2e3, 0x0c960, 0x0d557, 0x0d4a0, 0x0da50, 0x05d55, 0x056a0, 0x0a6d0, 0x055d4, // 2060-2069
+        0x052d0, 0x0a9b8, 0x0a950, 0x0b4a0, 0x0b6a6, 0x0ad50, 0x055a0, 0x0aba4, 0x0a5b0, 0x052b0, // 2070-2079
+        0x0b273, 0x06930, 0x07337, 0x06aa0, 0x0ad50, 0x14b55, 0x04b60, 0x0a570, 0x054e4, 0x0d160, // 2080-2089
+        0x0e968, 0x0d520, 0x0daa0, 0x16aa6, 0x056d0, 0x04ae0, 0x0a9d4, 0x0a2d0, 0x0d150, 0x0f252, // 2090-2099
+        0x0d520], // 2100
+
+    /**
+        * 公历每个月份的天数普通表
+        * @Array Of Property
+        * @return Number
+        */
+    solarMonth: [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31],
+
+    /**
+        * 天干地支之天干速查表
+        * @Array Of Property trans["甲","乙","丙","丁","戊","己","庚","辛","壬","癸"]
+        * @return Cn string
+        */
+    Gan: ['\u7532', '\u4e59', '\u4e19', '\u4e01', '\u620a', '\u5df1', '\u5e9a', '\u8f9b', '\u58ec', '\u7678'],
+
+    /**
+        * 天干地支之地支速查表
+        * @Array Of Property
+        * @trans["子","丑","寅","卯","辰","巳","午","未","申","酉","戌","亥"]
+        * @return Cn string
+        */
+    Zhi: ['\u5b50', '\u4e11', '\u5bc5', '\u536f', '\u8fb0', '\u5df3', '\u5348', '\u672a', '\u7533', '\u9149', '\u620c', '\u4ea5'],
+
+    /**
+        * 天干地支之地支速查表<=>生肖
+        * @Array Of Property
+        * @trans["鼠","牛","虎","兔","龙","蛇","马","羊","猴","鸡","狗","猪"]
+        * @return Cn string
+        */
+    Animals: ['\u9f20', '\u725b', '\u864e', '\u5154', '\u9f99', '\u86c7', '\u9a6c', '\u7f8a', '\u7334', '\u9e21', '\u72d7', '\u732a'],
+
+    /**
+        * 24节气速查表
+        * @Array Of Property
+        * @trans["小寒","大寒","立春","雨水","惊蛰","春分","清明","谷雨","立夏","小满","芒种","夏至","小暑","大暑","立秋","处暑","白露","秋分","寒露","霜降","立冬","小雪","大雪","冬至"]
+        * @return Cn string
+        */
+    solarTerm: ['\u5c0f\u5bd2', '\u5927\u5bd2', '\u7acb\u6625', '\u96e8\u6c34', '\u60ca\u86f0', '\u6625\u5206', '\u6e05\u660e', '\u8c37\u96e8', '\u7acb\u590f', '\u5c0f\u6ee1', '\u8292\u79cd', '\u590f\u81f3', '\u5c0f\u6691', '\u5927\u6691', '\u7acb\u79cb', '\u5904\u6691', '\u767d\u9732', '\u79cb\u5206', '\u5bd2\u9732', '\u971c\u964d', '\u7acb\u51ac', '\u5c0f\u96ea', '\u5927\u96ea', '\u51ac\u81f3'],
+
+    /**
+        * 1900-2100各年的24节气日期速查表
+        * @Array Of Property
+        * @return 0x string For splice
+        */
+    sTermInfo: ['9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f',
+        '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+        '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f', 'b027097bd097c36b0b6fc9274c91aa',
+        '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd0b06bdb0722c965ce1cfcc920f',
+        'b027097bd097c36b0b6fc9274c91aa', '9778397bd19801ec9210c965cc920e', '97b6b97bd19801ec95f8c965cc920f',
+        '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd197c36c9210c9274c91aa',
+        '97b6b97bd19801ec95f8c965cc920e', '97bd09801d98082c95f8e1cfcc920f', '97bd097bd097c36b0b6fc9210c8dc2',
+        '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec95f8c965cc920e', '97bcf97c3598082c95f8e1cfcc920f',
+        '97bd097bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+        '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+        '97b6b97bd19801ec9210c965cc920e', '97bcf97c3598082c95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722',
+        '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f',
+        '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+        '97bcf97c359801ec95f8c965cc920f', '97bd097bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+        '97b6b97bd19801ec9210c965cc920e', '97bcf97c359801ec95f8c965cc920f', '97bd097bd07f595b0b6fc920fb0722',
+        '9778397bd097c36b0b6fc9210c8dc2', '9778397bd19801ec9210c9274c920e', '97b6b97bd19801ec95f8c965cc920f',
+        '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+        '97b6b97bd19801ec95f8c965cc920f', '97bd07f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+        '9778397bd097c36c9210c9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bd07f1487f595b0b0bc920fb0722',
+        '7f0e397bd097c36b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+        '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+        '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+        '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e', '97bcf7f1487f531b0b0bb0b6fb0722',
+        '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b97bd19801ec9210c965cc920e',
+        '97bcf7f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+        '97b6b97bd19801ec9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+        '9778397bd097c36b0b6fc9210c91aa', '97b6b97bd197c36c9210c9274c920e', '97bcf7f0e47f531b0b0bb0b6fb0722',
+        '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '9778397bd097c36c9210c9274c920e',
+        '97b6b7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c36b0b6fc9210c8dc2',
+        '9778397bd097c36b0b70c9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+        '7f0e397bd097c35b0b6fc9210c8dc2', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+        '7f0e27f1487f595b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+        '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+        '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+        '7f0e397bd097c35b0b6fc920fb0722', '9778397bd097c36b0b6fc9274c91aa', '97b6b7f0e47f531b0723b0b6fb0721',
+        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9274c91aa',
+        '97b6b7f0e47f531b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+        '9778397bd097c36b0b6fc9210c91aa', '97b6b7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+        '7f0e397bd07f595b0b0bc920fb0722', '9778397bd097c36b0b6fc9210c8dc2', '977837f0e37f149b0723b0787b0721',
+        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f5307f595b0b0bc920fb0722', '7f0e397bd097c35b0b6fc9210c8dc2',
+        '977837f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e37f1487f595b0b0bb0b6fb0722',
+        '7f0e397bd097c35b0b6fc9210c8dc2', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722', '977837f0e37f14998082b0787b06bd',
+        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd097c35b0b6fc920fb0722',
+        '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+        '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14998082b0787b06bd',
+        '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0b0bb0b6fb0722', '7f0e397bd07f595b0b0bc920fb0722',
+        '977837f0e37f14998082b0723b06bd', '7f07e7f0e37f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+        '7f0e397bd07f595b0b0bc920fb0722', '977837f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b0721',
+        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f595b0b0bb0b6fb0722', '7f0e37f0e37f14898082b0723b02d5',
+        '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e37f1487f531b0b0bb0b6fb0722',
+        '7f0e37f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+        '7f0e37f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e37f14898082b072297c35',
+        '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722',
+        '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f149b0723b0787b0721',
+        '7f0e27f1487f531b0b0bb0b6fb0722', '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14998082b0723b06bd',
+        '7f07e7f0e47f149b0723b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722', '7f0e37f0e366aa89801eb072297c35',
+        '7ec967f0e37f14998082b0723b06bd', '7f07e7f0e37f14998083b0787b0721', '7f0e27f0e47f531b0723b0b6fb0722',
+        '7f0e37f0e366aa89801eb072297c35', '7ec967f0e37f14898082b0723b02d5', '7f07e7f0e37f14998082b0787b0721',
+        '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66aa89801e9808297c35', '665f67f0e37f14898082b0723b02d5',
+        '7ec967f0e37f14998082b0787b0721', '7f07e7f0e47f531b0723b0b6fb0722', '7f0e36665b66a449801e9808297c35',
+        '665f67f0e37f14898082b0723b02d5', '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721',
+        '7f0e36665b66a449801e9808297c35', '665f67f0e37f14898082b072297c35', '7ec967f0e37f14998082b0787b06bd',
+        '7f07e7f0e47f531b0723b0b6fb0721', '7f0e26665b66a449801e9808297c35', '665f67f0e37f1489801eb072297c35',
+        '7ec967f0e37f14998082b0787b06bd', '7f07e7f0e47f531b0723b0b6fb0721', '7f0e27f1487f531b0b0bb0b6fb0722'],
+
+    /**
+        * 数字转中文速查表
+        * @Array Of Property
+        * @trans ['日','一','二','三','四','五','六','七','八','九','十']
+        * @return Cn string
+        */
+    nStr1: ['\u65e5', '\u4e00', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341'],
+
+    /**
+        * 日期转农历称呼速查表
+        * @Array Of Property
+        * @trans ['初','十','廿','卅']
+        * @return Cn string
+        */
+    nStr2: ['\u521d', '\u5341', '\u5eff', '\u5345'],
+
+    /**
+        * 月份转农历称呼速查表
+        * @Array Of Property
+        * @trans ['正','一','二','三','四','五','六','七','八','九','十','冬','腊']
+        * @return Cn string
+        */
+    nStr3: ['\u6b63', '\u4e8c', '\u4e09', '\u56db', '\u4e94', '\u516d', '\u4e03', '\u516b', '\u4e5d', '\u5341', '\u51ac', '\u814a'],
+
+    /**
+        * 返回农历y年一整年的总天数
+        * @param lunar Year
+        * @return Number
+        * @eg:var count = calendar.lYearDays(1987) ;//count=387
+        */
+    lYearDays: function (y) {
+        var i; var sum = 348
+        for (i = 0x8000; i > 0x8; i >>= 1) { sum += (this.lunarInfo[y - 1900] & i) ? 1 : 0 }
+        return (sum + this.leapDays(y))
+    },
+
+    /**
+        * 返回农历y年闰月是哪个月;若y年没有闰月 则返回0
+        * @param lunar Year
+        * @return Number (0-12)
+        * @eg:var leapMonth = calendar.leapMonth(1987) ;//leapMonth=6
+        */
+    leapMonth: function (y) { // 闰字编码 \u95f0
+        return (this.lunarInfo[y - 1900] & 0xf)
+    },
+
+    /**
+        * 返回农历y年闰月的天数 若该年没有闰月则返回0
+        * @param lunar Year
+        * @return Number (0、29、30)
+        * @eg:var leapMonthDay = calendar.leapDays(1987) ;//leapMonthDay=29
+        */
+    leapDays: function (y) {
+        if (this.leapMonth(y)) {
+            return ((this.lunarInfo[y - 1900] & 0x10000) ? 30 : 29)
+        }
+        return (0)
+    },
+
+    /**
+        * 返回农历y年m月(非闰月)的总天数,计算m为闰月时的天数请使用leapDays方法
+        * @param lunar Year
+        * @return Number (-1、29、30)
+        * @eg:var MonthDay = calendar.monthDays(1987,9) ;//MonthDay=29
+        */
+    monthDays: function (y, m) {
+        if (m > 12 || m < 1) { return -1 }// 月份参数从1至12,参数错误返回-1
+        return ((this.lunarInfo[y - 1900] & (0x10000 >> m)) ? 30 : 29)
+    },
+
+    /**
+        * 返回公历(!)y年m月的天数
+        * @param solar Year
+        * @return Number (-1、28、29、30、31)
+        * @eg:var solarMonthDay = calendar.leapDays(1987) ;//solarMonthDay=30
+        */
+    solarDays: function (y, m) {
+        if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+        var ms = m - 1
+        if (ms == 1) { // 2月份的闰平规律测算后确认返回28或29
+            return (((y % 4 == 0) && (y % 100 != 0) || (y % 400 == 0)) ? 29 : 28)
+        } else {
+            return (this.solarMonth[ms])
+        }
+    },
+
+    /**
+       * 农历年份转换为干支纪年
+       * @param  lYear 农历年的年份数
+       * @return Cn string
+       */
+    toGanZhiYear: function (lYear) {
+        var ganKey = (lYear - 3) % 10
+        var zhiKey = (lYear - 3) % 12
+        if (ganKey == 0) ganKey = 10// 如果余数为0则为最后一个天干
+        if (zhiKey == 0) zhiKey = 12// 如果余数为0则为最后一个地支
+        return this.Gan[ganKey - 1] + this.Zhi[zhiKey - 1]
+    },
+
+    /**
+       * 公历月、日判断所属星座
+       * @param  cMonth [description]
+       * @param  cDay [description]
+       * @return Cn string
+       */
+    toAstro: function (cMonth, cDay) {
+        var s = '\u9b54\u7faf\u6c34\u74f6\u53cc\u9c7c\u767d\u7f8a\u91d1\u725b\u53cc\u5b50\u5de8\u87f9\u72ee\u5b50\u5904\u5973\u5929\u79e4\u5929\u874e\u5c04\u624b\u9b54\u7faf'
+        var arr = [20, 19, 21, 21, 21, 22, 23, 23, 23, 23, 22, 22]
+        return s.substr(cMonth * 2 - (cDay < arr[cMonth - 1] ? 2 : 0), 2) + '\u5ea7'// 座
+    },
+
+    /**
+        * 传入offset偏移量返回干支
+        * @param offset 相对甲子的偏移量
+        * @return Cn string
+        */
+    toGanZhi: function (offset) {
+        return this.Gan[offset % 10] + this.Zhi[offset % 12]
+    },
+
+    /**
+        * 传入公历(!)y年获得该年第n个节气的公历日期
+        * @param y公历年(1900-2100);n二十四节气中的第几个节气(1~24);从n=1(小寒)算起
+        * @return day Number
+        * @eg:var _24 = calendar.getTerm(1987,3) ;//_24=4;意即1987年2月4日立春
+        */
+    getTerm: function (y, n) {
+        if (y < 1900 || y > 2100) { return -1 }
+        if (n < 1 || n > 24) { return -1 }
+        var _table = this.sTermInfo[y - 1900]
+        var _info = [
+            parseInt('0x' + _table.substr(0, 5)).toString(),
+            parseInt('0x' + _table.substr(5, 5)).toString(),
+            parseInt('0x' + _table.substr(10, 5)).toString(),
+            parseInt('0x' + _table.substr(15, 5)).toString(),
+            parseInt('0x' + _table.substr(20, 5)).toString(),
+            parseInt('0x' + _table.substr(25, 5)).toString()
+        ]
+        var _calday = [
+            _info[0].substr(0, 1),
+            _info[0].substr(1, 2),
+            _info[0].substr(3, 1),
+            _info[0].substr(4, 2),
+
+            _info[1].substr(0, 1),
+            _info[1].substr(1, 2),
+            _info[1].substr(3, 1),
+            _info[1].substr(4, 2),
+
+            _info[2].substr(0, 1),
+            _info[2].substr(1, 2),
+            _info[2].substr(3, 1),
+            _info[2].substr(4, 2),
+
+            _info[3].substr(0, 1),
+            _info[3].substr(1, 2),
+            _info[3].substr(3, 1),
+            _info[3].substr(4, 2),
+
+            _info[4].substr(0, 1),
+            _info[4].substr(1, 2),
+            _info[4].substr(3, 1),
+            _info[4].substr(4, 2),
+
+            _info[5].substr(0, 1),
+            _info[5].substr(1, 2),
+            _info[5].substr(3, 1),
+            _info[5].substr(4, 2)
+        ]
+        return parseInt(_calday[n - 1])
+    },
+
+    /**
+        * 传入农历数字月份返回汉语通俗表示法
+        * @param lunar month
+        * @return Cn string
+        * @eg:var cnMonth = calendar.toChinaMonth(12) ;//cnMonth='腊月'
+        */
+    toChinaMonth: function (m) { // 月 => \u6708
+        if (m > 12 || m < 1) { return -1 } // 若参数错误 返回-1
+        var s = this.nStr3[m - 1]
+        s += '\u6708'// 加上月字
+        return s
+    },
+
+    /**
+        * 传入农历日期数字返回汉字表示法
+        * @param lunar day
+        * @return Cn string
+        * @eg:var cnDay = calendar.toChinaDay(21) ;//cnMonth='廿一'
+        */
+    toChinaDay: function (d) { // 日 => \u65e5
+        var s
+        switch (d) {
+            case 10:
+                s = '\u521d\u5341'; break
+            case 20:
+                s = '\u4e8c\u5341'; break
+                break
+            case 30:
+                s = '\u4e09\u5341'; break
+                break
+            default:
+                s = this.nStr2[Math.floor(d / 10)]
+                s += this.nStr1[d % 10]
+        }
+        return (s)
+    },
+
+    /**
+        * 年份转生肖[!仅能大致转换] => 精确划分生肖分界线是“立春”
+        * @param y year
+        * @return Cn string
+        * @eg:var animal = calendar.getAnimal(1987) ;//animal='兔'
+        */
+    getAnimal: function (y) {
+        return this.Animals[(y - 4) % 12]
+    },
+
+    /**
+        * 传入阳历年月日获得详细的公历、农历object信息 <=>JSON
+        * @param y  solar year
+        * @param m  solar month
+        * @param d  solar day
+        * @return JSON object
+        * @eg:console.log(calendar.solar2lunar(1987,11,01));
+        */
+    solar2lunar: function (y, m, d) { // 参数区间1900.1.31~2100.12.31
+        // 年份限定、上限
+        if (y < 1900 || y > 2100) {
+            return -1// undefined转换为数字变为NaN
+        }
+        // 公历传参最下限
+        if (y == 1900 && m == 1 && d < 31) {
+            return -1
+        }
+        // 未传参  获得当天
+        if (!y) {
+            var objDate = new Date()
+        } else {
+            var objDate = new Date(y, parseInt(m) - 1, d)
+        }
+        var i; var leap = 0; var temp = 0
+        // 修正ymd参数
+        var y = objDate.getFullYear()
+        var m = objDate.getMonth() + 1
+        var d = objDate.getDate()
+        var offset = (Date.UTC(objDate.getFullYear(), objDate.getMonth(), objDate.getDate()) - Date.UTC(1900, 0, 31)) / 86400000
+        for (i = 1900; i < 2101 && offset > 0; i++) {
+            temp = this.lYearDays(i)
+            offset -= temp
+        }
+        if (offset < 0) {
+            offset += temp; i--
+        }
+
+        // 是否今天
+        var isTodayObj = new Date()
+        var isToday = false
+        if (isTodayObj.getFullYear() == y && isTodayObj.getMonth() + 1 == m && isTodayObj.getDate() == d) {
+            isToday = true
+        }
+        // 星期几
+        var nWeek = objDate.getDay()
+        var cWeek = this.nStr1[nWeek]
+        // 数字表示周几顺应天朝周一开始的惯例
+        if (nWeek == 0) {
+            nWeek = 7
+        }
+        // 农历年
+        var year = i
+        var leap = this.leapMonth(i) // 闰哪个月
+        var isLeap = false
+
+        // 效验闰月
+        for (i = 1; i < 13 && offset > 0; i++) {
+            // 闰月
+            if (leap > 0 && i == (leap + 1) && isLeap == false) {
+                --i
+                isLeap = true; temp = this.leapDays(year) // 计算农历闰月天数
+            } else {
+                temp = this.monthDays(year, i)// 计算农历普通月天数
+            }
+            // 解除闰月
+            if (isLeap == true && i == (leap + 1)) { isLeap = false }
+            offset -= temp
+        }
+        // 闰月导致数组下标重叠取反
+        if (offset == 0 && leap > 0 && i == leap + 1) {
+            if (isLeap) {
+                isLeap = false
+            } else {
+                isLeap = true; --i
+            }
+        }
+        if (offset < 0) {
+            offset += temp; --i
+        }
+        // 农历月
+        var month = i
+        // 农历日
+        var day = offset + 1
+        // 天干地支处理
+        var sm = m - 1
+        var gzY = this.toGanZhiYear(year)
+
+        // 当月的两个节气
+        // bugfix-2017-7-24 11:03:38 use lunar Year Param `y` Not `year`
+        var firstNode = this.getTerm(y, (m * 2 - 1))// 返回当月「节」为几日开始
+        var secondNode = this.getTerm(y, (m * 2))// 返回当月「节」为几日开始
+
+        // 依据12节气修正干支月
+        var gzM = this.toGanZhi((y - 1900) * 12 + m + 11)
+        if (d >= firstNode) {
+            gzM = this.toGanZhi((y - 1900) * 12 + m + 12)
+        }
+
+        // 传入的日期的节气与否
+        var isTerm = false
+        var Term = null
+        if (firstNode == d) {
+            isTerm = true
+            Term = this.solarTerm[m * 2 - 2]
+        }
+        if (secondNode == d) {
+            isTerm = true
+            Term = this.solarTerm[m * 2 - 1]
+        }
+        // 日柱 当月一日与 1900/1/1 相差天数
+        var dayCyclical = Date.UTC(y, sm, 1, 0, 0, 0, 0) / 86400000 + 25567 + 10
+        var gzD = this.toGanZhi(dayCyclical + d - 1)
+        // 该日期所属的星座
+        var astro = this.toAstro(m, d)
+
+        return { 'lYear': year, 'lMonth': month, 'lDay': day, 'Animal': this.getAnimal(year), 'IMonthCn': (isLeap ? '\u95f0' : '') + this.toChinaMonth(month), 'IDayCn': this.toChinaDay(day), 'cYear': y, 'cMonth': m, 'cDay': d, 'gzYear': gzY, 'gzMonth': gzM, 'gzDay': gzD, 'isToday': isToday, 'isLeap': isLeap, 'nWeek': nWeek, 'ncWeek': '\u661f\u671f' + cWeek, 'isTerm': isTerm, 'Term': Term, 'astro': astro }
+    },
+
+    /**
+        * 传入农历年月日以及传入的月份是否闰月获得详细的公历、农历object信息 <=>JSON
+        * @param y  lunar year
+        * @param m  lunar month
+        * @param d  lunar day
+        * @param isLeapMonth  lunar month is leap or not.[如果是农历闰月第四个参数赋值true即可]
+        * @return JSON object
+        * @eg:console.log(calendar.lunar2solar(1987,9,10));
+        */
+    lunar2solar: function (y, m, d, isLeapMonth) { // 参数区间1900.1.31~2100.12.1
+        var isLeapMonth = !!isLeapMonth
+        var leapOffset = 0
+        var leapMonth = this.leapMonth(y)
+        var leapDay = this.leapDays(y)
+        if (isLeapMonth && (leapMonth != m)) { return -1 }// 传参要求计算该闰月公历 但该年得出的闰月与传参的月份并不同
+        if (y == 2100 && m == 12 && d > 1 || y == 1900 && m == 1 && d < 31) { return -1 }// 超出了最大极限值
+        var day = this.monthDays(y, m)
+        var _day = day
+        // bugFix 2016-9-25
+        // if month is leap, _day use leapDays method
+        if (isLeapMonth) {
+            _day = this.leapDays(y, m)
+        }
+        if (y < 1900 || y > 2100 || d > _day) { return -1 }// 参数合法性效验
+
+        // 计算农历的时间差
+        var offset = 0
+        for (var i = 1900; i < y; i++) {
+            offset += this.lYearDays(i)
+        }
+        var leap = 0; var isAdd = false
+        for (var i = 1; i < m; i++) {
+            leap = this.leapMonth(y)
+            if (!isAdd) { // 处理闰月
+                if (leap <= i && leap > 0) {
+                    offset += this.leapDays(y); isAdd = true
+                }
+            }
+            offset += this.monthDays(y, i)
+        }
+        // 转换闰月农历 需补充该年闰月的前一个月的时差
+        if (isLeapMonth) { offset += day }
+        // 1900年农历正月一日的公历时间为1900年1月30日0时0分0秒(该时间也是本农历的最开始起始点)
+        var stmap = Date.UTC(1900, 1, 30, 0, 0, 0)
+        var calObj = new Date((offset + d - 31) * 86400000 + stmap)
+        var cY = calObj.getUTCFullYear()
+        var cM = calObj.getUTCMonth() + 1
+        var cD = calObj.getUTCDate()
+
+        return this.solar2lunar(cY, cM, cD)
+    }
+}
+
+export default calendar

+ 104 - 0
uni_modules/uv-calendar/components/uv-calendar/header.vue

@@ -0,0 +1,104 @@
+<template>
+	<view class="uv-calendar-header uv-border-bottom">
+		<text
+			class="uv-calendar-header__title"
+			v-if="showTitle"
+		>{{ title }}</text>
+		<text
+			class="uv-calendar-header__subtitle"
+			v-if="showSubtitle"
+		>{{ subtitle }}</text>
+		<view class="uv-calendar-header__weekdays">
+			<text class="uv-calendar-header__weekdays__weekday">一</text>
+			<text class="uv-calendar-header__weekdays__weekday">二</text>
+			<text class="uv-calendar-header__weekdays__weekday">三</text>
+			<text class="uv-calendar-header__weekdays__weekday">四</text>
+			<text class="uv-calendar-header__weekdays__weekday">五</text>
+			<text class="uv-calendar-header__weekdays__weekday">六</text>
+			<text class="uv-calendar-header__weekdays__weekday">日</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	export default {
+		name: 'uv-calendar-header',
+		mixins: [mpMixin, mixin],
+		props: {
+			// 标题
+			title: {
+				type: String,
+				default: ''
+			},
+			// 副标题
+			subtitle: {
+				type: [String,null],
+				default: ''
+			},
+			// 是否显示标题
+			showTitle: {
+				type: Boolean,
+				default: true
+			},
+			// 是否显示副标题
+			showSubtitle: {
+				type: Boolean,
+				default: true
+			},
+		},
+		data() {
+			return {
+
+			}
+		},
+		methods: {
+			name() {
+
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	$show-border: 1;
+	$show-border-bottom: 1;
+	@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	.uv-calendar-header {
+		padding-bottom: 4px;
+
+		&__title {
+			font-size: 16px;
+			color: $uv-main-color;
+			text-align: center;
+			height: 42px;
+			line-height: 42px;
+			font-weight: bold;
+		}
+
+		&__subtitle {
+			font-size: 14px;
+			color: $uv-main-color;
+			height: 40px;
+			text-align: center;
+			line-height: 40px;
+			font-weight: bold;
+		}
+
+		&__weekdays {
+			@include flex;
+			justify-content: space-between;
+
+			&__weekday {
+				font-size: 13px;
+				color: $uv-main-color;
+				line-height: 30px;
+				flex: 1;
+				text-align: center;
+			}
+		}
+	}
+</style>

+ 616 - 0
uni_modules/uv-calendar/components/uv-calendar/month.vue

@@ -0,0 +1,616 @@
+<template>
+	<view class="uv-calendar-month-wrapper" ref="uv-calendar-month-wrapper">
+		<view v-for="(item, index) in months" :key="index" :class="[`uv-calendar-month-${index}`]"
+			:ref="`uv-calendar-month-${index}`" :id="`month-${index}`">
+			<text v-if="index !== 0" class="uv-calendar-month__title">{{ item.year }}年{{ item.month }}月</text>
+			<view class="uv-calendar-month__days">
+				<view v-if="showMark" class="uv-calendar-month__days__month-mark-wrapper">
+					<text class="uv-calendar-month__days__month-mark-wrapper__text">{{ item.month }}</text>
+				</view>
+				<view class="uv-calendar-month__days__day" v-for="(item1, index1) in item.date" :key="index1"
+					:style="[dayStyle(index, index1, item1)]" @tap="clickHandler(index, index1, item1)"
+					:class="[item1.selected && 'uv-calendar-month__days__day__select--selected']">
+					<view class="uv-calendar-month__days__day__select" :style="[daySelectStyle(index, index1, item1)]">
+						<text v-if="getTopInfo(index, index1, item1)"
+						class="uv-calendar-month__days__day__select__top-info"
+						:class="[item1.disabled && 'uv-calendar-month__days__day__select__top-info--disabled']"
+						:style="[textStyle(item1)]"
+						>{{ getTopInfo(index, index1, item1) }}</text>
+						<text class="uv-calendar-month__days__day__select__info"
+							:class="[item1.disabled && 'uv-calendar-month__days__day__select__info--disabled']"
+							:style="[textStyle(item1)]">{{ item1.day }}</text>
+						<text v-if="getBottomInfo(index, index1, item1)"
+							class="uv-calendar-month__days__day__select__buttom-info"
+							:class="[item1.disabled && 'uv-calendar-month__days__day__select__buttom-info--disabled']"
+							:style="[textStyle(item1)]">{{ getBottomInfo(index, index1, item1) }}</text>
+						<text v-if="item1.dot" class="uv-calendar-month__days__day__select__dot"></text>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	// #ifdef APP-NVUE
+	// 由于nvue不支持百分比单位,需要查询宽度来计算每个日期的宽度
+	const dom = uni.requireNativePlugin('dom')
+	// #endif
+	import { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js';
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'
+	export default {
+		name: 'uv-calendar-month',
+		emits:['monthSelected','updateMonthTop','change'],
+		mixins: [mpMixin, mixin],
+		props: {
+			// 是否显示月份背景色
+			showMark: {
+				type: Boolean,
+				default: true
+			},
+			// 主题色,对底部按钮和选中日期有效
+			color: {
+				type: String,
+				default: '#3c9cff'
+			},
+			// 月份数据
+			months: {
+				type: Array,
+				default: () => []
+			},
+			// 日期选择类型
+			mode: {
+				type: String,
+				default: 'single'
+			},
+			// 日期行高
+			rowHeight: {
+				type: [String, Number],
+				default: 58
+			},
+			// mode=multiple时,最多可选多少个日期
+			maxCount: {
+				type: [String, Number],
+				default: Infinity
+			},
+			// mode=range时,第一个日期底部的提示文字
+			startText: {
+				type: String,
+				default: '开始'
+			},
+			// mode=range时,最后一个日期底部的提示文字
+			endText: {
+				type: String,
+				default: '结束'
+			},
+			// 默认选中的日期,mode为multiple或range是必须为数组格式
+			defaultDate: {
+				type: [Array, String, Date],
+				default: null
+			},
+			// 最小的可选日期
+			minDate: {
+				type: [String, Number],
+				default: 0
+			},
+			// 最大可选日期
+			maxDate: {
+				type: [String, Number],
+				default: 0
+			},
+			// 如果没有设置maxDate,则往后推多少个月
+			maxMonth: {
+				type: [String, Number],
+				default: 2
+			},
+			// 是否为只读状态,只读状态下禁止选择日期
+			readonly: {
+				type: Boolean,
+				default: false
+			},
+			// 日期区间最多可选天数,默认无限制,mode = range时有效
+			maxRange: {
+				type: [Number, String],
+				default: Infinity
+			},
+			// 范围选择超过最多可选天数时的提示文案,mode = range时有效
+			rangePrompt: {
+				type: String,
+				default: ''
+			},
+			// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+			showRangePrompt: {
+				type: Boolean,
+				default: true
+			},
+			// 是否允许日期范围的起止时间为同一天,mode = range时有效
+			allowSameDay: {
+				type: Boolean,
+				default: false
+			}
+		},
+		data() {
+			return {
+				// 每个日期的宽度
+				width: 0,
+				// 当前选中的日期item
+				item: {},
+				selected: []
+			}
+		},
+		watch: {
+			selectedChange: {
+				immediate: true,
+				handler(n) {
+					this.setDefaultDate()
+				}
+			}
+		},
+		computed: {
+			// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+			selectedChange() {
+				return [this.minDate, this.maxDate, this.defaultDate]
+			},
+			dayStyle(index1, index2, item) {
+				return (index1, index2, item) => {
+					const style = {}
+					let week = item.week
+					// 不进行四舍五入的形式保留2位小数
+					const dayWidth = Number(parseFloat(this.width / 7).toFixed(3).slice(0, -1))
+					// 得出每个日期的宽度
+					// #ifdef APP-NVUE
+					style.width = this.$uv.addUnit(dayWidth)
+					// #endif
+					style.height = this.$uv.addUnit(this.rowHeight)
+					if (index2 === 0) {
+						// 获取当前为星期几,如果为0,则为星期天,减一为每月第一天时,需要向左偏移的item个数
+						week = (week === 0 ? 7 : week) - 1
+						style.marginLeft = this.$uv.addUnit(week * dayWidth)
+					}
+					if (this.mode === 'range') {
+						// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+						style.paddingLeft = 0
+						style.paddingRight = 0
+						style.paddingBottom = 0
+						style.paddingTop = 0
+					}
+					return style
+				}
+			},
+			daySelectStyle() {
+				return (index1, index2, item) => {
+					let date = dayjs(item.date).format("YYYY-MM-DD"),
+						style = {}
+					// 判断date是否在selected数组中,因为月份可能会需要补0,所以使用dateSame判断,而不用数组的includes判断
+					if (this.selected.some(item => this.dateSame(item, date))) {
+						style.backgroundColor = this.color
+					}
+					if (this.mode === 'single') {
+						if (date === this.selected[0]) {
+							// 因为需要对nvue的兼容,只能这么写,无法缩写,也无法通过类名控制等等
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+							style.borderTopRightRadius = '3px'
+							style.borderBottomRightRadius = '3px'
+						}
+					} else if (this.mode === 'range') {
+						if (this.selected.length >= 2) {
+							const len = this.selected.length - 1
+							// 第一个日期设置左上角和左下角的圆角
+							if (this.dateSame(date, this.selected[0])) {
+								style.borderTopLeftRadius = '3px'
+								style.borderBottomLeftRadius = '3px'
+							}
+							// 最后一个日期设置右上角和右下角的圆角
+							if (this.dateSame(date, this.selected[len])) {
+								style.borderTopRightRadius = '3px'
+								style.borderBottomRightRadius = '3px'
+							}
+							// 处于第一和最后一个之间的日期,背景色设置为浅色,通过将对应颜色进行等分,再取其尾部的颜色值
+							if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+									.selected[len]))) {
+								style.backgroundColor = colorGradient(this.color, '#ffffff', 100)[90]
+								// 增加一个透明度,让范围区间的背景色也能看到底部的mark水印字符
+								style.opacity = 0.7
+							}
+						} else if (this.selected.length === 1) {
+							// 之所以需要这么写,是因为DCloud公司的iOS客户端的开发者能力有限导致的bug
+							// 进行还原操作,否则在nvue的iOS,uni-app有bug,会导致诡异的表现
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+						}
+					} else {
+						if (this.selected.some(item => this.dateSame(item, date))) {
+							style.borderTopLeftRadius = '3px'
+							style.borderBottomLeftRadius = '3px'
+							style.borderTopRightRadius = '3px'
+							style.borderBottomRightRadius = '3px'
+						}
+					}
+					return style
+				}
+			},
+			// 某个日期是否被选中
+			textStyle() {
+				return (item) => {
+					const date = dayjs(item.date).format("YYYY-MM-DD"),
+						style = {}
+					// 选中的日期,提示文字设置白色
+					if (this.selected.some(item => this.dateSame(item, date))) {
+						style.color = '#ffffff'
+					}
+					if (this.mode === 'range') {
+						const len = this.selected.length - 1
+						// 如果是范围选择模式,第一个和最后一个之间的日期,文字颜色设置为高亮的主题色
+						if (dayjs(date).isAfter(dayjs(this.selected[0])) && dayjs(date).isBefore(dayjs(this
+								.selected[len]))) {
+							style.color = this.color
+						}
+					}
+					return style
+				}
+			},
+			// 获取顶部的提示文字
+			getTopInfo() {
+				return (index1, index2, item) => {
+					return item.topInfo;
+				}
+			},
+			// 获取底部的提示文字
+			getBottomInfo() {
+				return (index1, index2, item) => {
+					const date = dayjs(item.date).format("YYYY-MM-DD")
+					const bottomInfo = item.bottomInfo
+					// 当为日期范围模式时,且选择的日期个数大于0时
+					if (this.mode === 'range' && this.selected.length > 0) {
+						if (this.selected.length === 1) {
+							// 选择了一个日期时,如果当前日期为数组中的第一个日期,则显示底部文字为“开始”
+							if (this.dateSame(date, this.selected[0])) return this.startText
+							else return bottomInfo
+						} else {
+							const len = this.selected.length - 1
+							// 如果数组中的日期大于2个时,第一个和最后一个显示为开始和结束日期
+							if (this.dateSame(date, this.selected[0]) && this.dateSame(date, this.selected[1]) &&
+								len === 1) {
+								// 如果长度为2,且第一个等于第二个日期,则提示语放在同一个item中
+								return `${this.startText}/${this.endText}`
+							} else if (this.dateSame(date, this.selected[0])) {
+								return this.startText
+							} else if (this.dateSame(date, this.selected[len])) {
+								return this.endText
+							} else {
+								return bottomInfo
+							}
+						}
+					} else {
+						return bottomInfo
+					}
+				}
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				// 初始化默认选中
+				this.$emit('monthSelected', this.selected)
+				this.$nextTick(() => {
+					// 这里需要另一个延时,因为获取宽度后,会进行月份数据渲染,只有渲染完成之后,才有真正的高度
+					// 因为nvue下,$nextTick并不是100%可靠的
+					this.$uv.sleep(10).then(() => {
+						this.getWrapperWidth()
+						this.getMonthRect()
+					})
+				})
+			},
+			// 判断两个日期是否相等
+			dateSame(date1, date2) {
+				return dayjs(date1).isSame(dayjs(date2))
+			},
+			// 获取月份数据区域的宽度,因为nvue不支持百分比,所以无法通过css设置每个日期item的宽度
+			getWrapperWidth() {
+				// #ifdef APP-NVUE
+				dom.getComponentRect(this.$refs['uv-calendar-month-wrapper'], res => {
+					this.width = res.size.width
+				})
+				// #endif
+				// #ifndef APP-NVUE
+				this.$uvGetRect('.uv-calendar-month-wrapper').then(size => {
+					this.width = size.width
+				})
+				// #endif
+			},
+			getMonthRect() {
+				// 获取每个月份数据的尺寸,用于父组件在scroll-view滚动事件中,监听当前滚动到了第几个月份
+				const promiseAllArr = this.months.map((item, index) => this.getMonthRectByPromise(
+					`uv-calendar-month-${index}`))
+				// 一次性返回
+				Promise.all(promiseAllArr).then(
+					sizes => {
+						let height = 1
+						const topArr = []
+						for (let i = 0; i < this.months.length; i++) {
+							// 添加到months数组中,供scroll-view滚动事件中,判断当前滚动到哪个月份
+							topArr[i] = height
+							height += sizes[i].height
+						}
+						// 由于微信下,无法通过this.months[i].top的形式(引用类型)去修改父组件的month的top值,所以使用事件形式对外发出
+						this.$emit('updateMonthTop', topArr)
+					})
+			},
+			// 获取每个月份区域的尺寸
+			getMonthRectByPromise(el) {
+				// #ifndef APP-NVUE
+				// $uvGetRect为uvui自带的节点查询简化方法,详见文档介绍:https://www.uvui.cn/js/getRect.html
+				// 组件内部一般用this.$uvGetRect,对外的为getRect,二者功能一致,名称不同
+				return new Promise(resolve => {
+					this.$uvGetRect(`.${el}`).then(size => {
+						resolve(size)
+					})
+				})
+				// #endif
+
+				// #ifdef APP-NVUE
+				// nvue下,使用dom模块查询元素高度
+				// 返回一个promise,让调用此方法的主体能使用then回调
+				return new Promise(resolve => {
+					dom.getComponentRect(this.$refs[el][0], res => {
+						resolve(res.size)
+					})
+				})
+				// #endif
+			},
+			// 点击某一个日期
+			clickHandler(index1, index2, item) {
+				if (this.readonly) {
+					return;
+				}
+				this.item = item
+				const date = dayjs(item.date).format("YYYY-MM-DD")
+				if (item.disabled) return
+				// 对上一次选择的日期数组进行深度克隆
+				let selected = this.$uv.deepClone(this.selected)
+				if (this.mode === 'single') {
+					// 单选情况下,让数组中的元素为当前点击的日期
+					selected = [date]
+				} else if (this.mode === 'multiple') {
+					if (selected.some(item => this.dateSame(item, date))) {
+						// 如果点击的日期已在数组中,则进行移除操作,也就是达到反选的效果
+						const itemIndex = selected.findIndex(item => dayjs(item).format("YYYY-MM-DD") === dayjs(date).format("YYYY-MM-DD"))
+						selected.splice(itemIndex, 1)
+					} else {
+						// 如果点击的日期不在数组中,且已有的长度小于总可选长度时,则添加到数组中去
+						if (selected.length < this.maxCount) selected.push(date)
+					}
+				} else {
+					// 选择区间形式
+					if (selected.length === 0 || selected.length >= 2) {
+						// 如果原来就为0或者大于2的长度,则当前点击的日期,就是开始日期
+						selected = [date]
+					} else if (selected.length === 1) {
+						// 如果已经选择了开始日期
+						const existsDate = selected[0]
+						// 如果当前选择的日期小于上一次选择的日期,则当前的日期定为开始日期
+						if (dayjs(date).isBefore(existsDate)) {
+							selected = [date]
+						} else if (dayjs(date).isAfter(existsDate)) {
+							// 当前日期减去最大可选的日期天数,如果大于起始时间,则进行提示
+							if(dayjs(dayjs(date).subtract(this.maxRange, 'day')).isAfter(dayjs(selected[0])) && this.showRangePrompt) {
+								if(this.rangePrompt) {
+									this.$uv.toast(this.rangePrompt)
+								} else {
+									this.$uv.toast(`选择天数不能超过 ${this.maxRange} 天`)
+								}
+								return
+							}
+							// 如果当前日期大于已有日期,将当前的添加到数组尾部
+							selected.push(date)
+							const startDate = selected[0]
+							const endDate = selected[1]
+							const arr = []
+							let i = 0
+							do {
+								// 将开始和结束日期之间的日期添加到数组中
+								arr.push(dayjs(startDate).add(i, 'day').format("YYYY-MM-DD"))
+								i++
+								// 累加的日期小于结束日期时,继续下一次的循环
+							} while (dayjs(startDate).add(i, 'day').isBefore(dayjs(endDate)))
+							// 为了一次性修改数组,避免computed中多次触发,这里才用arr变量一次性赋值的方式,同时将最后一个日期添加近来
+							arr.push(endDate)
+							selected = arr
+						} else {
+							// 选择区间时,只有一个日期的情况下,且不允许选择起止为同一天的话,不允许选择自己
+							if (selected[0] === date && !this.allowSameDay) return
+							selected.push(date)
+						}
+					}
+				}
+				this.setSelected(selected)
+				this.$emit('change',{
+					day: date,
+					selected: selected
+				});
+			},
+			// 设置默认日期
+			setDefaultDate() {
+				if (!this.defaultDate) {
+					// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+					const selected = [dayjs().format("YYYY-MM-DD")]
+					return this.setSelected(selected, false)
+				}
+				let defaultDate = []
+				const minDate = this.minDate || dayjs().format("YYYY-MM-DD")
+				const maxDate = this.maxDate || dayjs(minDate).add(this.maxMonth - 1, 'month').format("YYYY-MM-DD")
+				if (this.mode === 'single') {
+					// 单选模式,可以是字符串或数组,Date对象等
+					if (!this.$uv.test.array(this.defaultDate)) {
+						defaultDate = [dayjs(this.defaultDate).format("YYYY-MM-DD")]
+					} else {
+						defaultDate = [this.defaultDate[0]]
+					}
+				} else {
+					// 如果为非数组,则不执行
+					if (!this.$uv.test.array(this.defaultDate)) return
+					defaultDate = this.defaultDate
+				}
+				// 过滤用户传递的默认数组,取出只在可允许最大值与最小值之间的元素
+				defaultDate = defaultDate.filter(item => {
+					return dayjs(item).isAfter(dayjs(minDate).subtract(1, 'day')) && dayjs(item).isBefore(dayjs(
+						maxDate).add(1, 'day'))
+				})
+				this.setSelected(defaultDate, false)
+			},
+			setSelected(selected, event = true) {
+				this.selected = selected
+				event && this.$emit('monthSelected', this.selected)
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	.uv-calendar-month-wrapper {
+		margin-top: 4px;
+	}
+
+	.uv-calendar-month {
+
+		&__title {
+			font-size: 14px;
+			line-height: 42px;
+			height: 42px;
+			color: $uv-main-color;
+			text-align: center;
+			font-weight: bold;
+		}
+
+		&__days {
+			position: relative;
+			@include flex;
+			flex-wrap: wrap;
+
+			&__month-mark-wrapper {
+				position: absolute;
+				top: 0;
+				bottom: 0;
+				left: 0;
+				right: 0;
+				@include flex;
+				justify-content: center;
+				align-items: center;
+
+				&__text {
+					font-size: 155px;
+					color: rgba(231, 232, 234, 0.83);
+				}
+			}
+
+			&__day {
+				@include flex;
+				padding: 2px;
+				/* #ifndef APP-NVUE */
+				// vue下使用css进行宽度计算,因为某些安卓机会无法进行js获取父元素宽度进行计算得出,会有偏移
+				width: calc(100% / 7);
+				box-sizing: border-box;
+				/* #endif */
+
+				&__select {
+					flex: 1;
+					@include flex;
+					align-items: center;
+					justify-content: center;
+					position: relative;
+
+					&__dot {
+						width: 7px;
+						height: 7px;
+						border-radius: 100px;
+						background-color: $uv-error;
+						position: absolute;
+						top: 12px;
+						right: 7px;
+					}
+					
+					&__top-info {
+						color: $uv-content-color;
+						text-align: center;
+						position: absolute;
+						top: 2px;
+						font-size: 10px;
+						text-align: center;
+						left: 0;
+						right: 0;
+						&--selected {
+							color: #ffffff;
+						}
+						
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+					
+					&__buttom-info {
+						color: $uv-content-color;
+						text-align: center;
+						position: absolute;
+						bottom: 5px;
+						font-size: 10px;
+						text-align: center;
+						left: 0;
+						right: 0;
+
+						&--selected {
+							color: #ffffff;
+						}
+
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+
+					&__info {
+						text-align: center;
+						font-size: 16px;
+
+						&--selected {
+							color: #ffffff;
+						}
+
+						&--disabled {
+							color: #cacbcd;
+						}
+					}
+
+					&--selected {
+						background-color: $uv-primary;
+						@include flex;
+						justify-content: center;
+						align-items: center;
+						flex: 1;
+						border-radius: 3px;
+					}
+
+					&--range-selected {
+						opacity: 0.3;
+						border-radius: 0;
+					}
+
+					&--range-start-selected {
+						border-top-right-radius: 0;
+						border-bottom-right-radius: 0;
+					}
+
+					&--range-end-selected {
+						border-top-left-radius: 0;
+						border-bottom-left-radius: 0;
+					}
+				}
+			}
+		}
+	}
+</style>

+ 145 - 0
uni_modules/uv-calendar/components/uv-calendar/props.js

@@ -0,0 +1,145 @@
+export default {
+	props: {
+		// 日历顶部标题
+		title: {
+			type: String,
+			default: '日期选择'
+		},
+		// 是否显示标题
+		showTitle: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示副标题
+		showSubtitle: {
+			type: Boolean,
+			default: true
+		},
+		// 日期类型选择,single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围
+		mode: {
+			type: String,
+			default: 'single'
+		},
+		// mode=range时,第一个日期底部的提示文字
+		startText: {
+			type: String,
+			default: '开始'
+		},
+		// mode=range时,最后一个日期底部的提示文字
+		endText: {
+			type: String,
+			default: '结束'
+		},
+		// 自定义列表
+		customList: {
+			type: Array,
+			default: () => []
+		},
+		// 主题色,对底部按钮和选中日期有效
+		color: {
+			type: String,
+			default: '#3c9cff'
+		},
+		// 最小的可选日期
+		minDate: {
+			type: [String, Number],
+			default: 0
+		},
+		// 最大可选日期
+		maxDate: {
+			type: [String, Number],
+			default: 0
+		},
+		// 默认选中的日期,mode为multiple或range是必须为数组格式
+		defaultDate: {
+			type: [Array, String, Date, null],
+			default: null
+		},
+		// mode=multiple时,最多可选多少个日期
+		maxCount: {
+			type: [String, Number],
+			default: Number.MAX_SAFE_INTEGER
+		},
+		// 日期行高
+		rowHeight: {
+			type: [String, Number],
+			default: 56
+		},
+		// 日期格式化函数
+		formatter: {
+			type: [Function, null],
+			default: null
+		},
+		// 是否显示农历
+		showLunar: {
+			type: Boolean,
+			default: false
+		},
+		// 是否显示月份背景色
+		showMark: {
+			type: Boolean,
+			default: true
+		},
+		// 确定按钮的文字
+		confirmText: {
+			type: String,
+			default: '确定'
+		},
+		// 确认按钮处于禁用状态时的文字
+		confirmDisabledText: {
+			type: String,
+			default: '确定'
+		},
+		// 是否允许点击遮罩关闭日历
+		closeOnClickOverlay: {
+			type: Boolean,
+			default: false
+		},
+		// 是否允许点击确认按钮关闭日历
+		closeOnClickConfirm: {
+			type: Boolean,
+			default: true
+		},
+		// 是否为只读状态,只读状态下禁止选择日期
+		readonly: {
+			type: Boolean,
+			default: false
+		},
+		// 	是否展示确认按钮
+		showConfirm: {
+			type: Boolean,
+			default: true
+		},
+		// 日期区间最多可选天数,默认无限制,mode = range时有效 Infinity
+		maxRange: {
+			type: [Number, String],
+			default: Number.MAX_SAFE_INTEGER
+		},
+		// 范围选择超过最多可选天数时的提示文案,mode = range时有效
+		rangePrompt: {
+			type: String,
+			default: ''
+		},
+		// 范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效
+		showRangePrompt: {
+			type: Boolean,
+			default: true
+		},
+		// 是否允许日期范围的起止时间为同一天,mode = range时有效
+		allowSameDay: {
+			type: Boolean,
+			default: false
+		},
+		// 圆角值
+		round: {
+			type: [Boolean, String, Number],
+			default: 0
+		},
+		// 最多展示月份数量
+		monthNum: {
+			type: [Number, String],
+			default: 3
+		},
+		...uni.$uv?.props?.calendar
+	}
+}

+ 390 - 0
uni_modules/uv-calendar/components/uv-calendar/uv-calendar.vue

@@ -0,0 +1,390 @@
+<template>
+	<uv-popup
+		ref="calendarPopup"
+		mode="bottom"
+		closeable
+		:round="round"
+		:closeOnClickOverlay="closeOnClickOverlay"
+		@change="popupChange"
+	>
+		<view class="uv-calendar">
+			<uvHeader
+				:title="title"
+				:subtitle="subtitle"
+				:showSubtitle="showSubtitle"
+				:showTitle="showTitle"
+			></uvHeader>
+			<scroll-view
+				:style="{ height: $uv.addUnit(listHeight) }"
+				scroll-y
+				@scroll="onScroll"
+				:scroll-top="scrollTop"
+				:scrollIntoView="scrollIntoView"
+			>
+				<uvMonth
+					:color="color"
+					:rowHeight="rowHeight"
+					:showMark="showMark"
+					:months="months"
+					:mode="mode"
+					:maxCount="maxCount"
+					:startText="startText"
+					:endText="endText"
+					:defaultDate="defaultDate"
+					:minDate="innerMinDate"
+					:maxDate="innerMaxDate"
+					:maxMonth="monthNum"
+					:readonly="readonly"
+					:maxRange="maxRange"
+					:rangePrompt="rangePrompt"
+					:showRangePrompt="showRangePrompt"
+					:allowSameDay="allowSameDay"
+					ref="month"
+					@monthSelected="monthSelected"
+					@updateMonthTop="updateMonthTop"
+					@change="changeDay"
+				></uvMonth>
+			</scroll-view>
+			<slot name="footer" v-if="showConfirm">
+				<view class="uv-calendar__confirm">
+					<uv-button
+						shape="circle"
+						:text="buttonDisabled ? confirmDisabledText : confirmText"
+						:color="color"
+						@click="confirm"
+						:disabled="buttonDisabled"
+					></uv-button>
+				</view>
+			</slot>
+		</view>
+	</uv-popup>
+</template>
+
+<script>
+import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+import uvHeader from './header.vue'
+import uvMonth from './month.vue'
+import props from './props.js'
+import dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'
+import Calendar from './calendar.js'
+/**
+ * Calendar 日历
+ * @description  此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
+ * @tutorial https://www.uvui.cn/components/calendar.html
+ *
+ * @property {String}				title				标题内容 (默认 日期选择 )
+ * @property {Boolean}				showTitle			是否显示标题  (默认 true )
+ * @property {Boolean}				showSubtitle		是否显示副标题	(默认 true )
+ * @property {String}				mode				日期类型选择  single-选择单个日期,multiple-可以选择多个日期,range-选择日期范围 ( 默认 'single' )
+ * @property {String}				startText			mode=range时,第一个日期底部的提示文字  (默认 '开始' )
+ * @property {String}				endText				mode=range时,最后一个日期底部的提示文字 (默认 '结束' )
+ * @property {Array}				customList			自定义列表
+ * @property {String}				color				主题色,对底部按钮和选中日期有效  (默认 ‘#3c9cff' )
+ * @property {String | Number}		minDate				最小的可选日期	 (默认 0 )
+ * @property {String | Number}		maxDate				最大可选日期  (默认 0 )
+ * @property {Array | String| Date}	defaultDate			默认选中的日期,mode为multiple或range是必须为数组格式
+ * @property {String | Number}		maxCount			mode=multiple时,最多可选多少个日期  (默认 	Number.MAX_SAFE_INTEGER  )
+ * @property {String | Number}		rowHeight			日期行高 (默认 56 )
+ * @property {Function}				formatter			日期格式化函数
+ * @property {Boolean}				showLunar			是否显示农历  (默认 false )
+ * @property {Boolean}				showMark			是否显示月份背景色 (默认 true )
+ * @property {String}				confirmText			确定按钮的文字 (默认 '确定' )
+ * @property {String}				confirmDisabledText	确认按钮处于禁用状态时的文字 (默认 '确定' )
+ * @property {Boolean}				show				是否显示日历弹窗 (默认 false )
+ * @property {Boolean}				closeOnClickOverlay	是否允许点击遮罩关闭日历 (默认 false )
+ * @property {Boolean}				closeOnClickConfirm	是否允许点击确认按钮关闭日历,设置为false不影响confirm事件返回 (默认 true )
+ * @property {Boolean}				readonly	        是否为只读状态,只读状态下禁止选择日期 (默认 false )
+ * @property {String | Number}		maxRange	        日期区间最多可选天数,默认无限制,mode = range时有效
+ * @property {String}				rangePrompt	        范围选择超过最多可选天数时的提示文案,mode = range时有效
+ * @property {Boolean}				showRangePrompt	    范围选择超过最多可选天数时,是否展示提示文案,mode = range时有效 (默认 true )
+ * @property {Boolean}				allowSameDay	    是否允许日期范围的起止时间为同一天,mode = range时有效 (默认 false )
+ * @property {Number|String}	    round				圆角值,默认无圆角  (默认 0 )
+ * @property {Number|String}	    monthNum			最多展示的月份数量  (默认 3 )
+ *
+ * @event {Function()} confirm 		点击确定按钮时触发		选择日期相关的返回参数
+ * @event {Function()} close 		日历关闭时触发			可定义页面关闭时的回调事件
+ * @example <uv-calendar ref="calendar" :defaultDate="defaultDateMultiple" mode="multiple" @confirm="confirm">
+	</uv-calendar>
+ * */
+export default {
+	name: 'uv-calendar',
+	emits:['confirm','close','change'],
+	mixins: [mpMixin, mixin, props],
+	components: {
+		uvHeader,
+		uvMonth
+	},
+	data() {
+		return {
+			// 需要显示的月份的数组
+			months: [],
+			// 在月份滚动区域中,当前视图中月份的index索引
+			monthIndex: 0,
+			// 月份滚动区域的高度
+			listHeight: 0,
+			// month组件中选择的日期数组
+			selected: [],
+			scrollIntoView: '',
+			scrollTop:0,
+			// 过滤处理方法
+			innerFormatter: (value) => value
+		}
+	},
+	watch: {
+		selectedChange: {
+			immediate: true,
+			handler(n) {
+				this.setMonth()
+			}
+		}
+	},
+	computed: {
+		// 由于maxDate和minDate可以为字符串(2021-10-10),或者数值(时间戳),但是dayjs如果接受字符串形式的时间戳会有问题,这里进行处理
+		innerMaxDate() {
+			return this.$uv.test.number(this.maxDate)
+				? Number(this.maxDate)
+				: this.maxDate
+		},
+		innerMinDate() {
+			return this.$uv.test.number(this.minDate)
+				? Number(this.minDate)
+				: this.minDate
+		},
+		// 多个条件的变化,会引起选中日期的变化,这里统一管理监听
+		selectedChange() {
+			return [this.innerMinDate, this.innerMaxDate, this.defaultDate]
+		},
+		subtitle() {
+			// 初始化时,this.months为空数组,所以需要特别判断处理
+			if (this.months.length) {
+				return `${this.months[this.monthIndex].year}年${
+					this.months[this.monthIndex].month
+				}月`
+			} else {
+				return ''
+			}
+		},
+		buttonDisabled() {
+			// 如果为range类型,且选择的日期个数不足1个时,让底部的按钮出于disabled状态
+			if (this.mode === 'range') {
+				if (this.selected.length <= 1) {
+					return true
+				} else {
+					return false
+				}
+			} else {
+				return false
+			}
+		}
+	},
+	mounted() {
+		this.start = Date.now()
+		this.init()
+	},
+	methods: {
+		// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+		setFormatter(e) {
+			this.innerFormatter = e
+		},
+		// 点击日期框触发
+		changeDay(e) {
+			this.$emit('change',e);
+		},
+		// month组件内部选择日期后,通过事件通知给父组件
+		monthSelected(e) {
+			this.selected = e
+			if (!this.showConfirm) {
+				// 在不需要确认按钮的情况下,如果为单选,或者范围多选且已选长度大于2,则直接进行返还
+				if (
+					this.mode === 'multiple' ||
+					this.mode === 'single' ||
+					(this.mode === 'range' && this.selected.length >= 2)
+				) {
+					this.$emit('confirm', this.selected)
+				}
+			}
+		},
+		init() {
+			// 校验maxDate,不能小于minDate
+			if (
+				this.innerMaxDate &&
+				this.innerMinDate &&
+				new Date(this.innerMaxDate).getTime() < new Date(this.innerMinDate).getTime()
+			) {
+				return this.$uv.error('maxDate不能小于minDate')
+			}
+			// 滚动区域的高度
+			this.listHeight = this.rowHeight * 5 + 30
+			this.setMonth()
+		},
+		open() {
+			this.setMonth()
+			this.$refs.calendarPopup.open();
+		},
+		popupChange(e) {
+			if(!e.show) {
+				this.$emit('close');
+			}
+		},
+		// 点击确定按钮
+		confirm() {
+			if (!this.buttonDisabled) {
+				this.$emit('confirm', this.selected)
+			}
+			if (this.closeOnClickConfirm) {
+				this.$refs.calendarPopup.close();
+			}
+		},
+		// 获得两个日期之间的月份数
+		getMonths(minDate, maxDate) {
+			const minYear = dayjs(minDate).year()
+			const minMonth = dayjs(minDate).month() + 1
+			const maxYear = dayjs(maxDate).year()
+			const maxMonth = dayjs(maxDate).month() + 1
+			return (maxYear - minYear) * 12 + (maxMonth - minMonth) + 1
+		},
+		// 设置月份数据
+		setMonth() {
+			// 最小日期的毫秒数
+			const minDate = this.innerMinDate || dayjs().valueOf()
+			// 如果没有指定最大日期,则往后推3个月
+			const maxDate =
+				this.innerMaxDate ||
+				dayjs(minDate)
+					.add(this.monthNum - 1, 'month')
+					.valueOf()
+			// 最大最小月份之间的共有多少个月份,
+			const months = this.$uv.range(
+				1,
+				this.monthNum,
+				this.getMonths(minDate, maxDate)
+			)
+			// 先清空数组
+			this.months = []
+			for (let i = 0; i < months; i++) {
+				this.months.push({
+					date: new Array(
+						dayjs(minDate).add(i, 'month').daysInMonth()
+					)
+						.fill(1)
+						.map((item, index) => {
+							// 日期,取值1-31
+							let day = index + 1
+							// 星期,0-6,0为周日
+							const week = dayjs(minDate)
+								.add(i, 'month')
+								.date(day)
+								.day()
+							const date = dayjs(minDate)
+								.add(i, 'month')
+								.date(day)
+								.format('YYYY-MM-DD')
+							let topInfo = ''
+							let bottomInfo = ''
+							if (this.showLunar) {
+								// 将日期转为农历格式
+								const lunar = Calendar.solar2lunar(
+									dayjs(date).year(),
+									dayjs(date).month() + 1,
+									dayjs(date).date()
+								)
+								bottomInfo = lunar.IDayCn
+							}
+							let config = {
+								day,
+								week,
+								// 小于最小允许的日期,或者大于最大的日期,则设置为disabled状态
+								disabled:
+									dayjs(date).isBefore(
+										dayjs(minDate).format('YYYY-MM-DD')
+									) ||
+									dayjs(date).isAfter(
+										dayjs(maxDate).format('YYYY-MM-DD')
+									),
+								// 返回一个日期对象,供外部的formatter获取当前日期的年月日等信息,进行加工处理
+								date: new Date(date),
+								topInfo,
+								bottomInfo,
+								dot: false,
+								month:
+									dayjs(minDate).add(i, 'month').month() + 1
+							}
+							const formatter =
+								this.formatter || this.innerFormatter
+							return formatter(config)
+						}),
+					// 当前所属的月份
+					month: dayjs(minDate).add(i, 'month').month() + 1,
+					// 当前年份
+					year: dayjs(minDate).add(i, 'month').year()
+				})
+			}
+
+		},
+		// 滚动到默认设置的月份
+		scrollIntoDefaultMonth(selected) {
+			// 查询默认日期在可选列表的下标
+			const _index = this.months.findIndex(({
+				  year,
+				  month
+			  }) => {
+				month = this.$uv.padZero(month)
+				return `${year}-${month}` === selected
+			})
+			if (_index !== -1) {
+				// #ifndef MP-WEIXIN
+				this.$nextTick(() => {
+					this.scrollIntoView = `month-${_index}`
+				})
+				// #endif
+				// #ifdef MP-WEIXIN
+				this.scrollTop = this.months[_index].top || 0;
+				// #endif
+			}
+		},
+		// scroll-view滚动监听
+		onScroll(event) {
+			// 不允许小于0的滚动值,如果scroll-view到顶了,继续下拉,会出现负数值
+			const scrollTop = Math.max(0, event.detail.scrollTop)
+			// 将当前滚动条数值,除以滚动区域的高度,可以得出当前滚动到了哪一个月份的索引
+			for (let i = 0; i < this.months.length; i++) {
+				if (scrollTop >= (this.months[i].top || this.listHeight)) {
+					this.monthIndex = i
+				}
+			}
+		},
+		// 更新月份的top值
+		updateMonthTop(topArr = []) {
+			// 设置对应月份的top值,用于onScroll方法更新月份
+			topArr.map((item, index) => {
+				this.months[index].top = item
+			})
+
+			// 获取默认日期的下标
+			if (!this.defaultDate) {
+				// 如果没有设置默认日期,则将当天日期设置为默认选中的日期
+				const selected = dayjs().format("YYYY-MM")
+				this.scrollIntoDefaultMonth(selected)
+				return
+			}
+			let selected = dayjs().format("YYYY-MM");
+			// 单选模式,可以是字符串或数组,Date对象等
+			if (!this.$uv.test.array(this.defaultDate)) {
+				selected = dayjs(this.defaultDate).format("YYYY-MM")
+			} else {
+				selected = dayjs(this.defaultDate[0]).format("YYYY-MM");
+			}
+			this.scrollIntoDefaultMonth(selected)
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.uv-calendar {
+	&__confirm {
+		padding: 7px 18px;
+	}
+}
+</style>

+ 89 - 0
uni_modules/uv-calendar/package.json

@@ -0,0 +1,89 @@
+{
+	"id": "uv-calendar",
+	"displayName": "uv-calendar 日历 全面兼容小程序、nvue、vue2、vue3等多端",
+	"version": "1.0.5",
+	"description": "日历组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中,灵活配置,功能齐全,兼容全端。",
+	"keywords": [
+        "uv-calendar",
+        "uvui",
+        "uv-ui",
+        "calendar",
+        "日历"
+    ],
+	"repository": "",
+	"engines": {
+		"HBuilderX": "^3.1.0"
+	},
+	"dcloudext": {
+		"type": "component-vue",
+		"sale": {
+			"regular": {
+				"price": "0.00"
+			},
+			"sourcecode": {
+				"price": "0.00"
+			}
+		},
+		"contact": {
+			"qq": ""
+		},
+		"declaration": {
+			"ads": "无",
+			"data": "插件不采集任何数据",
+			"permissions": "无"
+		},
+		"npmurl": ""
+	},
+	"uni_modules": {
+		"dependencies": [
+			"uv-ui-tools",
+			"uv-button",
+			"uv-popup"
+		],
+		"encrypt": [],
+		"platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+	}
+}

+ 11 - 0
uni_modules/uv-calendar/readme.md

@@ -0,0 +1,11 @@
+## Calendar 日历 
+
+> **组件名:uv-calendar**
+
+此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中。灵活配置,功能齐全,兼容全端。
+
+### <a href="https://www.uvui.cn/components/calendar.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 20 - 0
uni_modules/uv-datetime-picker/changelog.md

@@ -0,0 +1,20 @@
+## 1.0.8(2023-07-17)
+1. 优化文档
+2. 优化其他
+## 1.0.7(2023-07-13)
+1. 修复 uv-datetime-picker 设置value属性不生效的BUG 
+## 1.0.6(2023-07-05)
+修复vue3模式下,动态修改v-model绑定的值无效的BUG
+## 1.0.5(2023-07-02)
+uv-datetime-picker  由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/datetimePicker.html
+## 1.0.4(2023-06-29)
+1. 修复抖音小程序报错的BUG
+## 1.0.3(2023-06-07)
+1.  取消defaultIndex参数,传该值没实际意义,后续更新文档
+## 1.0.2(2023-06-02)
+1. 修复v-model重新赋值不更新的BUG
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-datetime-picker 时间选择器

+ 120 - 0
uni_modules/uv-datetime-picker/components/uv-datetime-picker/props.js

@@ -0,0 +1,120 @@
+export default {
+	props: {
+		value: {
+			type: [String, Number],
+			default: ''
+		},
+		modelValue: {
+			type: [String, Number],
+			default: ''
+		},
+		// 是否打开组件
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 是否展示顶部的操作栏
+		showToolbar: {
+			type: Boolean,
+			default: true
+		},
+		// 顶部标题
+		title: {
+			type: String,
+			default: ''
+		},
+		// 展示格式,mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择
+		mode: {
+			type: String,
+			default: 'datetime'
+		},
+		// 可选的最大时间
+		maxDate: {
+			type: Number,
+			// 最大默认值为后10年
+			default: new Date(new Date().getFullYear() + 10, 0, 1).getTime()
+		},
+		// 可选的最小时间
+		minDate: {
+			type: Number,
+			// 最小默认值为前10年
+			default: new Date(new Date().getFullYear() - 10, 0, 1).getTime()
+		},
+		// 可选的最小小时,仅mode=time有效
+		minHour: {
+			type: Number,
+			default: 0
+		},
+		// 可选的最大小时,仅mode=time有效
+		maxHour: {
+			type: Number,
+			default: 23
+		},
+		// 可选的最小分钟,仅mode=time有效
+		minMinute: {
+			type: Number,
+			default: 0
+		},
+		// 可选的最大分钟,仅mode=time有效
+		maxMinute: {
+			type: Number,
+			default: 59
+		},
+		// 选项过滤函数
+		filter: {
+			type: [Function, null],
+			default: null
+		},
+		// 选项格式化函数
+		formatter: {
+			type: [Function, null],
+			default: null
+		},
+		// 是否显示加载中状态
+		loading: {
+			type: Boolean,
+			default: false
+		},
+		// 各列中,单个选项的高度
+		itemHeight: {
+			type: [String, Number],
+			default: 44
+		},
+		// 取消按钮的文字
+		cancelText: {
+			type: String,
+			default: '取消'
+		},
+		// 确认按钮的文字
+		confirmText: {
+			type: String,
+			default: '确认'
+		},
+		// 取消按钮的颜色
+		cancelColor: {
+			type: String,
+			default: '#909193'
+		},
+		// 确认按钮的颜色
+		confirmColor: {
+			type: String,
+			default: '#3c9cff'
+		},
+		// 每列中可见选项的数量
+		visibleItemCount: {
+			type: [String, Number],
+			default: 5
+		},
+		// 是否允许点击遮罩关闭选择器
+		closeOnClickOverlay: {
+			type: Boolean,
+			default: true
+		},
+		// 是否允许点击确认关闭选择器
+		closeOnClickConfirm: {
+			type: Boolean,
+			default: true
+		},
+		...uni.$uv?.props?.datetimePicker
+	}
+}

+ 356 - 0
uni_modules/uv-datetime-picker/components/uv-datetime-picker/uv-datetime-picker.vue

@@ -0,0 +1,356 @@
+<template>
+	<uv-picker
+		ref="picker"
+		:closeOnClickOverlay="closeOnClickOverlay"
+		:closeOnClickConfirm="closeOnClickConfirm"
+		:columns="columns"
+		:title="title"
+		:itemHeight="itemHeight"
+		:showToolbar="showToolbar"
+		:visibleItemCount="visibleItemCount"
+		:defaultIndex="innerDefaultIndex"
+		:cancelText="cancelText"
+		:confirmText="confirmText"
+		:cancelColor="cancelColor"
+		:confirmColor="confirmColor"
+		@close="close"
+		@cancel="cancel"
+		@confirm="confirm"
+		@change="change"
+	>
+	</uv-picker>
+</template>
+
+<script>
+	function times(n, iteratee) {
+	    let index = -1
+	    const result = Array(n < 0 ? 0 : n)
+	    while (++index < n) {
+	        result[index] = iteratee(index)
+	    }
+	    return result
+	}
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	import dayjs from '@/uni_modules/uv-ui-tools/libs/util/dayjs.js'
+	/**
+	 * DatetimePicker 时间日期选择器
+	 * @description 此选择器用于时间日期
+	 * @tutorial https://www.uvui.cn/components/datetimePicker.html
+	 * @property {Boolean}			showToolbar			是否显示顶部的操作栏  ( 默认 true )
+	 * @property {String | Number}	value				绑定值
+	 * @property {String}			title				顶部标题
+	 * @property {String}			mode				展示格式 mode=date为日期选择,mode=time为时间选择,mode=year-month为年月选择,mode=datetime为日期时间选择  ( 默认 ‘datetime )
+	 * @property {Number}			maxDate				可选的最大时间  默认值为后10年
+	 * @property {Number}			minDate				可选的最小时间  默认值为前10年
+	 * @property {Number}			minHour				可选的最小小时,仅mode=time有效   ( 默认 0 )
+	 * @property {Number}			maxHour				可选的最大小时,仅mode=time有效	  ( 默认 23 )
+	 * @property {Number}			minMinute			可选的最小分钟,仅mode=time有效	  ( 默认 0 )
+	 * @property {Number}			maxMinute			可选的最大分钟,仅mode=time有效   ( 默认 59 )
+	 * @property {Function}			filter				选项过滤函数
+	 * @property {Function}			formatter			选项格式化函数
+	 * @property {Boolean}			loading				是否显示加载中状态   ( 默认 false )
+	 * @property {String | Number}	itemHeight			各列中,单个选项的高度   ( 默认 44 )
+	 * @property {String}			cancelText			取消按钮的文字  ( 默认 '取消' )
+	 * @property {String}			confirmText			确认按钮的文字  ( 默认 '确认' )
+	 * @property {String}			cancelColor			取消按钮的颜色  ( 默认 '#909193' )
+	 * @property {String}			confirmColor		确认按钮的颜色  ( 默认 '#3c9cff' )
+	 * @property {String | Number}	visibleItemCount	每列中可见选项的数量  ( 默认 5 )
+	 * @property {Boolean}			closeOnClickOverlay	是否允许点击遮罩关闭选择器  ( 默认 true )
+	 * @event {Function} close 关闭选择器时触发
+	 * @event {Function} confirm 点击确定按钮,返回当前选择的值
+	 * @event {Function} change 当选择值变化时触发
+	 * @event {Function} cancel 点击取消按钮
+	 * @example  <uv-datetime-picker ref="datetimepicker" :value="value1"  mode="datetime" ></uv-datetime-picker>
+	 */
+	export default {
+		name: 'uv-datetime-picker',
+		emits: ['close','cancel','confirm','input','change','update:modelValue'],
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				columns: [],
+				innerDefaultIndex: [],
+				innerFormatter: (type, value) => value
+			}
+		},
+		watch: {
+			propsChange() {
+				this.init()
+			}
+		},
+		computed: {
+			// 如果以下这些变量发生了变化,意味着需要重新初始化各列的值
+			propsChange() {
+				const propsValue = this.value || this.modelValue;
+				return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, propsValue ]
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				this.getValue();
+				this.updateColumnValue(this.innerValue)
+			},
+			getValue() {
+				const propsValue = this.value || this.modelValue;
+				this.innerValue = this.correctValue(propsValue)
+			},
+			// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+			setFormatter(e) {
+				this.innerFormatter = e
+			},
+			open() {
+				this.$refs.picker.open();
+				this.getValue();
+				this.updateColumnValue(this.innerValue);
+			},
+			close() {
+					this.$emit('close');
+			},
+			// 点击工具栏的取消按钮
+			cancel() {
+				this.$emit('cancel')
+			},
+			// 点击工具栏的确定按钮
+			confirm() {
+				this.$emit('confirm', {
+					value: this.innerValue,
+					mode: this.mode
+				})
+				this.$emit('input', this.innerValue)
+				this.$emit('update:modelValue',this.innerValue)
+			},
+			//用正则截取输出值,当出现多组数字时,抛出错误
+			intercept(e,type){
+				let judge = e.match(/\d+/g)
+				//判断是否掺杂数字
+				if(judge.length>1){
+					this.$uv.error("请勿在过滤或格式化函数时添加数字")
+					return 0
+				}else if(type&&judge[0].length==4){//判断是否是年份
+					return judge[0]
+				}else if(judge[0].length>2){
+					this.$uv.error("请勿在过滤或格式化函数时添加数字")
+					return 0
+				}else{
+					return judge[0]
+				}
+			},
+			// 列发生变化时触发
+			change(e) {
+				const { indexs, values } = e
+				let selectValue = ''
+				if(this.mode === 'time') {
+					// 根据value各列索引,从各列数组中,取出当前时间的选中值
+					selectValue = `${this.intercept(values[0][indexs[0]])}:${this.intercept(values[1][indexs[1]])}`
+				} else {
+					// 将选择的值转为数值,比如'03'转为数值的3,'2019'转为数值的2019
+					const year = parseInt(this.intercept(values[0][indexs[0]],'year'))
+					const month = parseInt(this.intercept(values[1][indexs[1]]))
+					let date = parseInt(values[2] ? this.intercept(values[2][indexs[2]]) : 1)
+					let hour = 0, minute = 0
+					// 此月份的最大天数
+					const maxDate = dayjs(`${year}-${month}`).daysInMonth()
+					// year-month模式下,date不会出现在列中,设置为1,为了符合后边需要减1的需求
+					if (this.mode === 'year-month') {
+					    date = 1
+					}
+					// 不允许超过maxDate值
+					date = Math.min(maxDate, date)
+					if (this.mode === 'datetime') {
+					    hour = parseInt(this.intercept(values[3][indexs[3]]))
+					    minute = parseInt(this.intercept(values[4][indexs[4]]))
+					}
+					// 转为时间模式
+					selectValue = Number(new Date(year, month - 1, date, hour, minute))
+				}
+				// 取出准确的合法值,防止超越边界的情况
+				selectValue = this.correctValue(selectValue)
+				this.innerValue = selectValue
+				this.updateColumnValue(selectValue)
+				// 发出change时间,value为当前选中的时间戳
+				this.$emit('change', {
+					value: selectValue,
+					mode: this.mode
+				})
+			},
+			// 更新各列的值,进行补0、格式化等操作
+			updateColumnValue(value) {
+				this.innerValue = value
+				this.updateColumns()
+				this.updateIndexs(value)
+			},
+			// 更新索引
+			updateIndexs(value) {
+				let values = []
+				const formatter = this.formatter || this.innerFormatter;
+				if (this.mode === 'time') {
+					// 将time模式的时间用:分隔成数组
+				    const timeArr = value.split(':')
+					// 使用formatter格式化方法进行管道处理
+				    values = [formatter('hour', timeArr[0]), formatter('minute', timeArr[1])]
+				} else {
+				    const date = new Date(value)
+				    values = [
+				        formatter('year', `${dayjs(value).year()}`),
+						// 月份补0
+				        formatter('month', this.$uv.padZero(dayjs(value).month() + 1))
+				    ]
+				    if (this.mode === 'date') {
+						// date模式,需要添加天列
+				        values.push(formatter('day', this.$uv.padZero(dayjs(value).date())))
+				    }
+				    if (this.mode === 'datetime') {
+						// 数组的push方法,可以写入多个参数
+				        values.push(formatter('day', this.$uv.padZero(dayjs(value).date())), formatter('hour', this.$uv.padZero(dayjs(value).hour())), formatter('minute', this.$uv.padZero(dayjs(value).minute())))
+				    }
+				}
+
+				// 根据当前各列的所有值,从各列默认值中找到默认值在各列中的索引
+				const indexs = this.columns.map((column, index) => {
+					// 通过取大值,可以保证不会出现找不到索引的-1情况
+					return Math.max(0, column.findIndex(item => item === values[index]))
+				})
+				this.innerDefaultIndex = indexs
+			},
+			// 更新各列的值
+			updateColumns() {
+			    const formatter = this.formatter || this.innerFormatter
+				// 获取各列的值,并且map后,对各列的具体值进行补0操作
+			    const results = this.getOriginColumns().map((column) => column.values.map((value) => formatter(column.type, value)))
+				this.columns = results
+			},
+			getOriginColumns() {
+			    // 生成各列的值
+			    const results = this.getRanges().map(({ type, range }) => {
+			        let values = times(range[1] - range[0] + 1, (index) => {
+			            let value = range[0] + index
+			            value = type === 'year' ? `${value}` : this.$uv.padZero(value)
+			            return value
+			        })
+					// 进行过滤
+			        if (this.filter) {
+			            values = this.filter(type, values)
+			        }
+			        return { type, values }
+			    })
+			    return results
+			},
+			// 通过最大值和最小值生成数组
+			generateArray(start, end) {
+				return Array.from(new Array(end + 1).keys()).slice(start)
+			},
+			// 得出合法的时间
+			correctValue(value) {
+				const isDateMode = this.mode !== 'time'
+				if (isDateMode && !this.$uv.test.date(value)) {
+					// 如果是日期类型,但是又没有设置合法的当前时间的话,使用最小时间为当前时间
+					value = this.minDate
+				} else if (!isDateMode && !value) {
+					// 如果是时间类型,而又没有默认值的话,就用最小时间
+					value = `${this.$uv.padZero(this.minHour)}:${this.$uv.padZero(this.minMinute)}`
+				}
+				// 时间类型
+				if (!isDateMode) {
+					if (String(value).indexOf(':') === -1) return this.$uv.error('时间错误,请传递如12:24的格式')
+					let [hour, minute] = value.split(':')
+					// 对时间补零,同时控制在最小值和最大值之间
+					hour = this.$uv.padZero(this.$uv.range(this.minHour, this.maxHour, Number(hour)))
+					minute = this.$uv.padZero(this.$uv.range(this.minMinute, this.maxMinute, Number(minute)))
+					return `${ hour }:${ minute }`
+				} else {
+					// 如果是日期格式,控制在最小日期和最大日期之间
+					value = dayjs(value).isBefore(dayjs(this.minDate)) ? this.minDate : value
+					value = dayjs(value).isAfter(dayjs(this.maxDate)) ? this.maxDate : value
+					return value
+				}
+			},
+			// 获取每列的最大和最小值
+			getRanges() {
+			    if (this.mode === 'time') {
+			        return [
+			            {
+			                type: 'hour',
+			                range: [this.minHour, this.maxHour],
+			            },
+			            {
+			                type: 'minute',
+			                range: [this.minMinute, this.maxMinute],
+			            },
+			        ];
+			    }
+			    const { maxYear, maxDate, maxMonth, maxHour, maxMinute, } = this.getBoundary('max', this.innerValue);
+			    const { minYear, minDate, minMonth, minHour, minMinute, } = this.getBoundary('min', this.innerValue);
+			    const result = [
+			        {
+			            type: 'year',
+			            range: [minYear, maxYear],
+			        },
+			        {
+			            type: 'month',
+			            range: [minMonth, maxMonth],
+			        },
+			        {
+			            type: 'day',
+			            range: [minDate, maxDate],
+			        },
+			        {
+			            type: 'hour',
+			            range: [minHour, maxHour],
+			        },
+			        {
+			            type: 'minute',
+			            range: [minMinute, maxMinute],
+			        },
+			    ];
+			    if (this.mode === 'date')
+			        result.splice(3, 2);
+			    if (this.mode === 'year-month')
+			        result.splice(2, 3);
+			    return result;
+			},
+			// 根据minDate、maxDate、minHour、maxHour等边界值,判断各列的开始和结束边界值
+			getBoundary(type, innerValue) {
+			    const value = new Date(innerValue)
+			    const boundary = new Date(this[`${type}Date`])
+			    const year = dayjs(boundary).year()
+			    let month = 1
+			    let date = 1
+			    let hour = 0
+			    let minute = 0
+			    if (type === 'max') {
+			        month = 12
+					// 月份的天数
+			        date = dayjs(value).daysInMonth()
+			        hour = 23
+			        minute = 59
+			    }
+				// 获取边界值,逻辑是:当年达到了边界值(最大或最小年),就检查月允许的最大和最小值,以此类推
+			    if (dayjs(value).year() === year) {
+			        month = dayjs(boundary).month() + 1
+			        if (dayjs(value).month() + 1 === month) {
+			            date = dayjs(boundary).date()
+			            if (dayjs(value).date() === date) {
+			                hour = dayjs(boundary).hour()
+			                if (dayjs(value).hour() === hour) {
+			                    minute = dayjs(boundary).minute()
+			                }
+			            }
+			        }
+			    }
+			    return {
+			        [`${type}Year`]: year,
+			        [`${type}Month`]: month,
+			        [`${type}Date`]: date,
+			        [`${type}Hour`]: hour,
+			        [`${type}Minute`]: minute
+			    }
+			},
+		},
+	}
+</script>
+

+ 88 - 0
uni_modules/uv-datetime-picker/package.json

@@ -0,0 +1,88 @@
+{
+  "id": "uv-datetime-picker",
+  "displayName": "uv-datetime-picker 时间选择器 全面兼容vue3+2、app、h5、小程序等多端",
+  "version": "1.0.8",
+  "description": "时间选择器组件用于时间日期,主要用于年月日时分的选择,具体选择的精确度由参数控制。",
+  "keywords": [
+    "datetime-picker",
+    "uvui",
+    "uv-ui",
+    "datetime",
+    "时间选择"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-picker"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 19 - 0
uni_modules/uv-datetime-picker/readme.md

@@ -0,0 +1,19 @@
+## DatetimePicker 时间选择器
+
+> **组件名:uv-datetime-picker**
+
+此选择器用于时间日期,主要用于年月日时分的选择,具体选择的精确度由参数控制。
+
+# <a href="https://www.uvui.cn/components/datetimePicker.html" target="_blank">查看文档</a>
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) <small>(请不要 下载插件ZIP)</small>
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+<a href="https://ext.dcloud.net.cn/plugin?name=uv-ui" target="_blank">
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+</a>
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 2 - 0
uni_modules/uv-icon/changelog.md

@@ -1,3 +1,5 @@
+## 1.0.9(2023-07-28)
+1. 修改几个对应错误图标的BUG
 ## 1.0.8(2023-07-24)
 1. 优化 支持base64图片
 ## 1.0.7(2023-07-17)

+ 5 - 5
uni_modules/uv-icon/components/uv-icon/icons.js

@@ -13,13 +13,13 @@ export default {
 	'uvicon-empty-data': 'e671',
 	'uvicon-empty-address': 'e68a',
 	'uvicon-empty-favor': 'e662',
-	'uvicon-empty-car': 'e656',
+	'uvicon-empty-car': 'e657',
 	'uvicon-empty-order': 'e66b',
-	'uvicon-empty-list': 'e671',
+	'uvicon-empty-list': 'e672',
 	'uvicon-empty-search': 'e677',
-	'uvicon-empty-permission': 'e67c',
-	'uvicon-empty-news': 'e67d',
-	'uvicon-empty-history': 'e684',
+	'uvicon-empty-permission': 'e67d',
+	'uvicon-empty-news': 'e67e',
+	'uvicon-empty-history': 'e685',
 	'uvicon-empty-coupon': 'e69b',
 	'uvicon-empty-page': 'e60e',
 	'uvicon-empty-wifi-off': 'e6cc',

+ 2 - 2
uni_modules/uv-icon/package.json

@@ -1,7 +1,7 @@
 {
   "id": "uv-icon",
-  "displayName": "uv-icon 图标  全面兼容小程序、nvue、vue2、vue3等多端",
-  "version": "1.0.8",
+  "displayName": "uv-icon 图标 全面兼容vue3+2、app、h5、小程序等多端",
+  "version": "1.0.9",
   "description": "基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。可自定义颜色、大小。",
   "keywords": [
     "uv-ui,uvui,uv-icon,icon,图标,字体图标"

+ 7 - 3
uni_modules/uv-icon/readme.md

@@ -4,8 +4,12 @@
 
 基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。
 
-### <a href="https://www.uvui.cn/components/icon.html" target="_blank">查看文档</a>
+# <a href="https://www.uvui.cn/components/icon.html" target="_blank">查看文档</a>
 
-### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui)
 
-#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:549833913 或 <a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+![image](https://mp-a667b617-c5f1-4a2d-9a54-683a67cff588.cdn.bspapp.com/uv-ui/banner.png)
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 7 - 0
uni_modules/uv-loading-icon/changelog.md

@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-27)
+优化
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-loading-icon组件

+ 60 - 0
uni_modules/uv-loading-icon/components/uv-loading-icon/props.js

@@ -0,0 +1,60 @@
+export default {
+	props: {
+		// 是否显示组件
+		show: {
+			type: Boolean,
+			default: true
+		},
+		// 颜色
+		color: {
+			type: String,
+			default: '#909193'
+		},
+		// 提示文字颜色
+		textColor: {
+			type: String,
+			default: '#909193'
+		},
+		// 文字和图标是否垂直排列
+		vertical: {
+			type: Boolean,
+			default: false
+		},
+		// 模式选择,circle-圆形,spinner-花朵形,semicircle-半圆形
+		mode: {
+			type: String,
+			default: 'spinner'
+		},
+		// 图标大小,单位默认px
+		size: {
+			type: [String, Number],
+			default: 24
+		},
+		// 文字大小
+		textSize: {
+			type: [String, Number],
+			default: 15
+		},
+		// 文字内容
+		text: {
+			type: [String, Number],
+			default: ''
+		},
+		// 动画模式 https://www.runoob.com/cssref/css3-pr-animation-timing-function.html
+		timingFunction: {
+			type: String,
+			default: 'linear'
+		},
+		// 动画执行周期时间
+		duration: {
+			type: [String, Number],
+			default: 1200
+		},
+		// mode=circle时的暗边颜色
+		inactiveColor: {
+			type: String,
+			default: ''
+		},
+		...uni.$uv?.props?.loadingIcon
+	}
+}

+ 346 - 0
uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue

@@ -0,0 +1,346 @@
+<template>
+	<view
+		class="uv-loading-icon"
+		:style="[$uv.addStyle(customStyle)]"
+		:class="[vertical && 'uv-loading-icon--vertical']"
+		v-if="show"
+	>
+		<view
+			v-if="!webviewHide"
+			class="uv-loading-icon__spinner"
+			:class="[`uv-loading-icon__spinner--${mode}`]"
+			ref="ani"
+			:style="{
+				color: color,
+				width: $uv.addUnit(size),
+				height: $uv.addUnit(size),
+				borderTopColor: color,
+				borderBottomColor: otherBorderColor,
+				borderLeftColor: otherBorderColor,
+				borderRightColor: otherBorderColor,
+				'animation-duration': `${duration}ms`,
+				'animation-timing-function': mode === 'semicircle' || mode === 'circle' ? timingFunction : ''
+			}"
+		>
+			<block v-if="mode === 'spinner'">
+				<!-- #ifndef APP-NVUE -->
+				<view
+					v-for="(item, index) in array12"
+					:key="index"
+					class="uv-loading-icon__dot"
+				>
+				</view>
+				<!-- #endif -->
+				<!-- #ifdef APP-NVUE -->
+				<!-- 此组件内部图标部分无法设置宽高,即使通过width和height配置了也无效 -->
+				<loading-indicator
+					v-if="!webviewHide"
+					class="uv-loading-indicator"
+					:animating="true"
+					:style="{
+						color: color,
+						width: $uv.addUnit(size),
+						height: $uv.addUnit(size)
+					}"
+				/>
+				<!-- #endif -->
+			</block>
+		</view>
+		<text
+			v-if="text"
+			class="uv-loading-icon__text"
+			:style="{
+				fontSize: $uv.addUnit(textSize),
+				color: textColor,
+			}"
+		>{{text}}</text>
+	</view>
+</template>
+
+<script>
+	import { colorGradient } from '@/uni_modules/uv-ui-tools/libs/function/colorGradient.js'
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	// #ifdef APP-NVUE
+	const animation = weex.requireModule('animation');
+	// #endif
+	/**
+	 * loading 加载动画
+	 * @description 警此组件为一个小动画,目前用在uvui的loadmore加载更多和switch开关等组件的正在加载状态场景。
+	 * @tutorial https://www.uvui.cn/components/loading.html
+	 * @property {Boolean}			show			是否显示组件  (默认 true)
+	 * @property {String}			color			动画活动区域的颜色,只对 mode = flower 模式有效(默认#909193)
+	 * @property {String}			textColor		提示文本的颜色(默认#909193)
+	 * @property {Boolean}			vertical		文字和图标是否垂直排列 (默认 false )
+	 * @property {String}			mode			模式选择,见官网说明(默认 'circle' )
+	 * @property {String | Number}	size			加载图标的大小,单位px (默认 24 )
+	 * @property {String | Number}	textSize		文字大小(默认 15 )
+	 * @property {String | Number}	text			文字内容 
+	 * @property {String}			timingFunction	动画模式 (默认 'ease-in-out' )
+	 * @property {String | Number}	duration		动画执行周期时间(默认 1200)
+	 * @property {String}			inactiveColor	mode=circle时的暗边颜色 
+	 * @property {Object}			customStyle		定义需要用到的外部样式
+	 * @example <uv-loading mode="circle"></uv-loading>
+	 */
+	export default {
+		name: 'uv-loading-icon',
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				// Array.form可以通过一个伪数组对象创建指定长度的数组
+				// https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from
+				array12: Array.from({
+					length: 12
+				}),
+				// 这里需要设置默认值为360,否则在安卓nvue上,会延迟一个duration周期后才执行
+				// 在iOS nvue上,则会一开始默认执行两个周期的动画
+				aniAngel: 360, // 动画旋转角度
+				webviewHide: false, // 监听webview的状态,如果隐藏了页面,则停止动画,以免性能消耗
+				loading: false, // 是否运行中,针对nvue使用
+			}
+		},
+		computed: {
+			// 当为circle类型时,给其另外三边设置一个更轻一些的颜色
+			// 之所以需要这么做的原因是,比如父组件传了color为红色,那么需要另外的三个边为浅红色
+			// 而不能是固定的某一个其他颜色(因为这个固定的颜色可能浅蓝,导致效果没有那么细腻良好)
+			otherBorderColor() {
+				const lightColor = colorGradient(this.color, '#ffffff', 100)[80]
+				if (this.mode === 'circle') {
+					return this.inactiveColor ? this.inactiveColor : lightColor
+				} else {
+					return 'transparent'
+				}
+			}
+		},
+		watch: {
+			show(n) {
+				// nvue中,show为true,且为非loading状态,就重新执行动画模块
+				// #ifdef APP-NVUE
+				if (n && !this.loading) {
+					setTimeout(() => {
+						this.startAnimate()
+					}, 30)
+				}
+				// #endif
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				setTimeout(() => {
+					// #ifdef APP-NVUE
+					this.show && this.nvueAnimate()
+					// #endif
+					// #ifdef APP-PLUS 
+					this.show && this.addEventListenerToWebview()
+					// #endif
+				}, 20)
+			},
+			// 监听webview的显示与隐藏
+			addEventListenerToWebview() {
+				// webview的堆栈
+				const pages = getCurrentPages()
+				// 当前页面
+				const page = pages[pages.length - 1]
+				// 当前页面的webview实例
+				const currentWebview = page.$getAppWebview()
+				// 监听webview的显示与隐藏,从而停止或者开始动画(为了性能)
+				currentWebview.addEventListener('hide', () => {
+					this.webviewHide = true
+				})
+				currentWebview.addEventListener('show', () => {
+					this.webviewHide = false
+				})
+			},
+			// #ifdef APP-NVUE
+			nvueAnimate() {
+				// nvue下,非spinner类型时才需要旋转,因为nvue的spinner类型,使用了weex的
+				// loading-indicator组件,自带旋转功能
+				this.mode !== 'spinner' && this.startAnimate()
+			},
+			// 执行nvue的animate模块动画
+			startAnimate() {
+				this.loading = true
+				const ani = this.$refs.ani
+				if (!ani) return
+				animation.transition(ani, {
+					// 进行角度旋转
+					styles: {
+						transform: `rotate(${this.aniAngel}deg)`,
+						transformOrigin: 'center center'
+					},
+					duration: this.duration,
+					timingFunction: this.timingFunction,
+					// delay: 10
+				}, () => {
+					// 每次增加360deg,为了让其重新旋转一周
+					this.aniAngel += 360
+					// 动画结束后,继续循环执行动画,需要同时判断webviewHide变量
+					// nvue安卓,页面隐藏后依然会继续执行startAnimate方法
+					this.show && !this.webviewHide ? this.startAnimate() : this.loading = false
+				})
+			}
+			// #endif
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	$uv-loading-icon-color: #c8c9cc !default;
+	$uv-loading-icon-text-margin-left:4px !default;
+	$uv-loading-icon-text-color:$uv-content-color !default;
+	$uv-loading-icon-text-font-size:14px !default;
+	$uv-loading-icon-text-line-height:20px !default;
+	$uv-loading-width:30px !default;
+	$uv-loading-height:30px !default;
+	$uv-loading-max-width:100% !default;
+	$uv-loading-max-height:100% !default;
+	$uv-loading-semicircle-border-width: 2px !default;
+	$uv-loading-semicircle-border-color:transparent !default;
+	$uv-loading-semicircle-border-top-right-radius: 100px !default;
+	$uv-loading-semicircle-border-top-left-radius: 100px !default;
+	$uv-loading-semicircle-border-bottom-left-radius: 100px !default;
+	$uv-loading-semicircle-border-bottom-right-radiu: 100px !default;
+	$uv-loading-semicircle-border-style: solid !default;
+	$uv-loading-circle-border-top-right-radius: 100px !default;
+	$uv-loading-circle-border-top-left-radius: 100px !default;
+	$uv-loading-circle-border-bottom-left-radius: 100px !default;
+	$uv-loading-circle-border-bottom-right-radiu: 100px !default;
+	$uv-loading-circle-border-width:2px !default;
+	$uv-loading-circle-border-top-color:#e5e5e5 !default;
+	$uv-loading-circle-border-right-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-bottom-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-left-color:$uv-loading-circle-border-top-color !default;
+	$uv-loading-circle-border-style:solid !default;
+	$uv-loading-icon-host-font-size:0px !default;
+	$uv-loading-icon-host-line-height:1 !default;
+	$uv-loading-icon-vertical-margin:6px 0 0 !default;
+	$uv-loading-icon-dot-top:0 !default;
+	$uv-loading-icon-dot-left:0 !default;
+	$uv-loading-icon-dot-width:100% !default;
+	$uv-loading-icon-dot-height:100% !default;
+	$uv-loading-icon-dot-before-width:2px !default;
+	$uv-loading-icon-dot-before-height:25% !default;
+	$uv-loading-icon-dot-before-margin:0 auto !default;
+	$uv-loading-icon-dot-before-background-color:currentColor !default;
+	$uv-loading-icon-dot-before-border-radius:40% !default;
+
+	.uv-loading-icon {
+		/* #ifndef APP-NVUE */
+		// display: inline-flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		justify-content: center;
+		color: $uv-loading-icon-color;
+
+		&__text {
+			margin-left: $uv-loading-icon-text-margin-left;
+			color: $uv-loading-icon-text-color;
+			font-size: $uv-loading-icon-text-font-size;
+			line-height: $uv-loading-icon-text-line-height;
+		}
+
+		&__spinner {
+			width: $uv-loading-width;
+			height: $uv-loading-height;
+			position: relative;
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			max-width: $uv-loading-max-width;
+			max-height: $uv-loading-max-height;
+			animation: uv-rotate 1s linear infinite;
+			/* #endif */
+		}
+
+		&__spinner--semicircle {
+			border-width: $uv-loading-semicircle-border-width;
+			border-color: $uv-loading-semicircle-border-color;
+			border-top-right-radius: $uv-loading-semicircle-border-top-right-radius;
+			border-top-left-radius: $uv-loading-semicircle-border-top-left-radius;
+			border-bottom-left-radius: $uv-loading-semicircle-border-bottom-left-radius;
+			border-bottom-right-radius: $uv-loading-semicircle-border-bottom-right-radiu;
+			border-style: $uv-loading-semicircle-border-style;
+		}
+
+		&__spinner--circle {
+			border-top-right-radius: $uv-loading-circle-border-top-right-radius;
+			border-top-left-radius: $uv-loading-circle-border-top-left-radius;
+			border-bottom-left-radius: $uv-loading-circle-border-bottom-left-radius;
+			border-bottom-right-radius: $uv-loading-circle-border-bottom-right-radiu;
+			border-width: $uv-loading-circle-border-width;
+			border-top-color: $uv-loading-circle-border-top-color;
+			border-right-color: $uv-loading-circle-border-right-color;
+			border-bottom-color: $uv-loading-circle-border-bottom-color;
+			border-left-color: $uv-loading-circle-border-left-color;
+			border-style: $uv-loading-circle-border-style;
+		}
+
+		&--vertical {
+			flex-direction: column
+		}
+	}
+
+	/* #ifndef APP-NVUE */
+	:host {
+		font-size: $uv-loading-icon-host-font-size;
+		line-height: $uv-loading-icon-host-line-height;
+	}
+
+	.uv-loading-icon {
+		&__spinner--spinner {
+			animation-timing-function: steps(12)
+		}
+
+		&__text:empty {
+			display: none
+		}
+
+		&--vertical &__text {
+			margin: $uv-loading-icon-vertical-margin;
+			color: $uv-content-color;
+		}
+
+		&__dot {
+			position: absolute;
+			top: $uv-loading-icon-dot-top;
+			left: $uv-loading-icon-dot-left;
+			width: $uv-loading-icon-dot-width;
+			height: $uv-loading-icon-dot-height;
+
+			&:before {
+				display: block;
+				width: $uv-loading-icon-dot-before-width;
+				height: $uv-loading-icon-dot-before-height;
+				margin: $uv-loading-icon-dot-before-margin;
+				background-color: $uv-loading-icon-dot-before-background-color;
+				border-radius: $uv-loading-icon-dot-before-border-radius;
+				content: " "
+			}
+		}
+	}
+
+	@for $i from 1 through 12 {
+		.uv-loading-icon__dot:nth-of-type(#{$i}) {
+			transform: rotate($i * 30deg);
+			opacity: 1 - 0.0625 * ($i - 1);
+		}
+	}
+
+	@keyframes uv-rotate {
+		0% {
+			transform: rotate(0deg)
+		}
+
+		to {
+			transform: rotate(1turn)
+		}
+	}
+
+	/* #endif */
+</style>

+ 87 - 0
uni_modules/uv-loading-icon/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-loading-icon",
+  "displayName": "uv-loading-icon 加载动画  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.2",
+  "description": "uv-loading-icon 此组件为一个小动画,目前用在uv-ui的loadMore加载更多等组件的正在加载状态场景。",
+  "keywords": [
+    "uv-loading-icon",
+    "uvui",
+    "uv-ui",
+    "loading",
+    "加载动画"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-loading-icon/readme.md

@@ -0,0 +1,11 @@
+## LoadingIcon 加载动画
+
+> **组件名:uv-loading-icon**
+
+此组件为一个小动画,目前用在uv-ui的loadMore加载更多等组件的正在加载状态场景。
+
+### <a href="https://www.uvui.cn/components/loadingIcon.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 9 - 0
uni_modules/uv-overlay/changelog.md

@@ -0,0 +1,9 @@
+## 1.0.3(2023-07-02)
+uv-overlay  由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+## 1.0.2(2023-06-29)
+1. 优化,H5端禁止穿透滚动
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-overlay组件

+ 25 - 0
uni_modules/uv-overlay/components/uv-overlay/props.js

@@ -0,0 +1,25 @@
+export default {
+	props: {
+		// 是否显示遮罩
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 层级z-index
+		zIndex: {
+			type: [String, Number],
+			default: 10070
+		},
+		// 遮罩的过渡时间,单位为ms
+		duration: {
+			type: [String, Number],
+			default: 300
+		},
+		// 不透明度值,当做rgba的第四个参数
+		opacity: {
+			type: [String, Number],
+			default: 0.5
+		},
+		...uni.$uv?.props?.overlay
+	}
+}

+ 85 - 0
uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue

@@ -0,0 +1,85 @@
+<template>
+	<uv-transition
+	  :show="show"
+		mode="fade"
+	  custom-class="uv-overlay"
+	  :duration="duration"
+	  :custom-style="overlayStyle"
+	  @click="clickHandler"
+		@touchmove.stop.prevent="clear"
+	>
+		<slot />
+	</uv-transition>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+
+	/**
+	 * overlay 遮罩
+	 * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
+	 * @tutorial https://www.uvui.cn/components/overlay.html
+	 * @property {Boolean}			show		是否显示遮罩(默认 false )
+	 * @property {String | Number}	zIndex		zIndex 层级(默认 10070 )
+	 * @property {String | Number}	duration	动画时长,单位毫秒(默认 300 )
+	 * @property {String | Number}	opacity		不透明度值,当做rgba的第四个参数 (默认 0.5 )
+	 * @property {Object}			customStyle	定义需要用到的外部样式
+	 * @event {Function} click 点击遮罩发送事件
+	 * @example <uv-overlay :show="show" @click="show = false"></uv-overlay>
+	 */
+	export default {
+		name: "uv-overlay",
+		emits: ['click'],
+		mixins: [mpMixin, mixin, props],
+		watch: {
+			show(newVal){
+				// #ifdef H5
+				if(newVal){
+					document.querySelector('body').style.overflow = 'hidden';
+				}else{
+					document.querySelector('body').style.overflow = '';
+				}
+				// #endif
+			}
+		},
+		computed: {
+			overlayStyle() {
+				const style = {
+					position: 'fixed',
+					top: 0,
+					left: 0,
+					right: 0,
+					zIndex: this.zIndex,
+					bottom: 0,
+					'background-color': `rgba(0, 0, 0, ${this.opacity})`
+				}
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+			}
+		},
+		methods: {
+			clickHandler() {
+				this.$emit('click')
+			},
+			clear() {}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+/* #ifndef APP-NVUE */
+$uv-overlay-top:0 !default;
+$uv-overlay-left:0 !default;
+$uv-overlay-width:100% !default;
+$uv-overlay-height:100% !default;
+$uv-overlay-background-color:rgba(0, 0, 0, .7) !default;
+.uv-overlay {
+	position: fixed;
+	top:$uv-overlay-top;
+	left:$uv-overlay-left;
+	width: $uv-overlay-width;
+	height:$uv-overlay-height;
+	background-color:$uv-overlay-background-color;
+}
+/* #endif */
+</style>

+ 88 - 0
uni_modules/uv-overlay/package.json

@@ -0,0 +1,88 @@
+{
+  "id": "uv-overlay",
+  "displayName": "uv-overlay 遮罩层  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.3",
+  "description": "uv-overlay 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景,uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。",
+  "keywords": [
+    "uv-overlay",
+    "uvui",
+    "uv-ui",
+    "overlay",
+    "遮罩层"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-transition"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-overlay/readme.md

@@ -0,0 +1,11 @@
+## Overlay 遮罩层
+
+> **组件名:uv-overlay**
+
+创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景,uv-popup、uv-toast、uv-tooltip等组件就是用了该组件。
+
+### <a href="https://www.uvui.cn/components/overlay.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 17 - 0
uni_modules/uv-picker/changelog.md

@@ -0,0 +1,17 @@
+## 1.0.6(2023-07-02)
+uv-picker  由于弹出层uv-popup的修改,打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/picker.html
+## 1.0.5(2023-06-26)
+1. 增加color参数
+2. 增加activeColor参数
+## 1.0.4(2023-06-15)
+1. 修改支付宝报错的BUG
+## 1.0.3(2023-06-12)
+1. setColumnValues的使用统一化,避免某些平台报错
+2. 取消change回调回传的组件实例,直接统一通过ref的方式调取setColumnValues方法
+## 1.0.2(2023-05-23)
+1. uv-toolbar组件新增下边框属性 
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-picker 选择器

+ 90 - 0
uni_modules/uv-picker/components/uv-picker/props.js

@@ -0,0 +1,90 @@
+export default {
+	props: {
+		// 是否展示顶部的操作栏
+		showToolbar: {
+			type: Boolean,
+			default: true
+		},
+		// 顶部标题
+		title: {
+			type: String,
+			default: ''
+		},
+		// 对象数组,设置每一列的数据
+		columns: {
+			type: Array,
+			default: () => []
+		},
+		// 是否显示加载中状态
+		loading: {
+			type: Boolean,
+			default: false
+		},
+		// 各列中,单个选项的高度
+		itemHeight: {
+			type: [String, Number],
+			default: 44
+		},
+		// 取消按钮的文字
+		cancelText: {
+			type: String,
+			default: '取消'
+		},
+		// 确认按钮的文字
+		confirmText: {
+			type: String,
+			default: '确定'
+		},
+		// 取消按钮的颜色
+		cancelColor: {
+			type: String,
+			default: '#909193'
+		},
+		// 确认按钮的颜色
+		confirmColor: {
+			type: String,
+			default: '#3c9cff'
+		},
+		// 文字颜色
+		color: {
+			type: String,
+			default: ''
+		},
+		// 选中文字的颜色
+		activeColor: {
+			type: String,
+			default: ''
+		},
+		// 每列中可见选项的数量
+		visibleItemCount: {
+			type: [String, Number],
+			default: 5
+		},
+		// 选项对象中,需要展示的属性键名
+		keyName: {
+			type: String,
+			default: 'text'
+		},
+		// 是否允许点击遮罩关闭选择器
+		closeOnClickOverlay: {
+			type: Boolean,
+			default: true
+		},
+		// 是否允许点击确认关闭选择器
+		closeOnClickConfirm: {
+			type: Boolean,
+			default: true
+		},
+		// 各列的默认索引
+		defaultIndex: {
+			type: Array,
+			default: () => [],
+		},
+		// 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件,只在微信2.21.1及以上有效
+		immediateChange: {
+			type: Boolean,
+			default: false
+		},
+		...uni.$uv?.props?.picker
+	}
+}

+ 312 - 0
uni_modules/uv-picker/components/uv-picker/uv-picker.vue

@@ -0,0 +1,312 @@
+<template>
+	<uv-popup
+		ref="pickerPopup"
+		mode="bottom"
+		:close-on-click-overlay="closeOnClickOverlay"
+		@change="popupChange"
+	>
+		<view class="uv-picker">
+			<uv-toolbar
+				v-if="showToolbar"
+				:cancelColor="cancelColor"
+				:confirmColor="confirmColor"
+				:cancelText="cancelText"
+				:confirmText="confirmText"
+				:title="title"
+				@cancel="cancel"
+				@confirm="confirm"
+			></uv-toolbar>
+			<picker-view
+				class="uv-picker__view"
+				:indicatorStyle="`height: ${$uv.addUnit(itemHeight)}`"
+				:value="innerIndex"
+				:immediateChange="immediateChange"
+				:style="{
+					height: `${$uv.addUnit(visibleItemCount * itemHeight)}`
+				}"
+				@change="changeHandler"
+			>
+				<picker-view-column
+					v-for="(item, index) in innerColumns"
+					:key="index"
+					class="uv-picker__view__column"
+				>
+					<text
+						v-if="$uv.test.array(item)"
+						class="uv-picker__view__column__item uv-line-1"
+						v-for="(item1, index1) in item"
+						:key="index1"
+						:style="[{
+								height: $uv.addUnit(itemHeight),
+								lineHeight: $uv.addUnit(itemHeight),
+								fontWeight: index1 === innerIndex[index] ? 'bold' : 'normal'
+							},textStyle(index,index1)]"
+					>{{ getItemText(item1) }}</text>
+				</picker-view-column>
+			</picker-view>
+			<view
+				v-if="loading"
+				class="uv-picker--loading"
+			>
+				<uv-loading-icon mode="circle"></uv-loading-icon>
+			</view>
+		</view>
+	</uv-popup>
+</template>
+
+<script>
+/**
+ * uv-picker
+ * @description 选择器
+ * @property {Boolean}			showToolbar			是否显示顶部的操作栏(默认 true )
+ * @property {String}			title				顶部标题
+ * @property {Array}			columns				对象数组,设置每一列的数据
+ * @property {Boolean}			loading				是否显示加载中状态(默认 false )
+ * @property {String | Number}	itemHeight			各列中,单个选项的高度(默认 44 )
+ * @property {String}			cancelText			取消按钮的文字(默认 '取消' )
+ * @property {String}			confirmText			确认按钮的文字(默认 '确定' )
+ * @property {String}			cancelColor			取消按钮的颜色(默认 '#909193' )
+ * @property {String}			confirmColor		确认按钮的颜色(默认 '#3c9cff' )
+ * @property {String}			color		文字颜色(默认 '' )
+ * @property {String}			activeColor		选中文字的颜色(默认 '' )
+ * @property {String | Number}	visibleItemCount	每列中可见选项的数量(默认 5 )
+ * @property {String}			keyName				选项对象中,需要展示的属性键名(默认 'text' )
+ * @property {Boolean}			closeOnClickOverlay	是否允许点击遮罩关闭选择器(默认 false )
+ * @property {Array}			defaultIndex		各列的默认索引
+ * @property {Boolean}			immediateChange		是否在手指松开时立即触发change事件(默认 false )
+ * @event {Function} close		关闭选择器时触发
+ * @event {Function} cancel		点击取消按钮触发
+ * @event {Function} change		当选择值变化时触发
+ * @event {Function} confirm	点击确定按钮,返回当前选择的值
+ */
+import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+import props from './props.js';
+export default {
+	name: 'uv-picker',
+	emits: ['confirm','cancel','close','change'],
+	mixins: [mpMixin, mixin, props],
+	computed: {
+		// 为了解决支付宝不生效
+		textStyle(){
+			return (index,index1) => {
+				const style = {};
+				// #ifndef APP-NVUE 
+				style.display = 'block';
+				// #endif
+				if(this.color) {
+					style.color = this.color;
+				}
+				if(this.activeColor && index1 === this.innerIndex[index]) {
+					style.color = this.activeColor;
+				}
+				return style;
+			}
+		}
+	},
+	data() {
+		return {
+			// 上一次选择的列索引
+			lastIndex: [],
+			// 索引值 ,对应picker-view的value
+			innerIndex: [],
+			// 各列的值
+			innerColumns: [],
+			// 上一次的变化列索引
+			columnIndex: 0,
+		}
+	},
+	watch: {
+		// 监听默认索引的变化,重新设置对应的值
+		defaultIndex: {
+			immediate: true,
+			handler(n) {
+				this.setIndexs(n, true)
+			}
+		},
+		// 监听columns参数的变化
+		columns: {
+			immediate: true,
+			handler(n) {
+				this.setColumns(n)
+			}
+		},
+	},
+	methods: {
+		open() {
+			this.$refs.pickerPopup.open();
+		},
+		close() {
+			this.$refs.pickerPopup.close();
+		},
+		popupChange(e) {
+			if(!e.show) this.$emit('close');
+		},
+		// 获取item需要显示的文字,判别为对象还是文本
+		getItemText(item) {
+			if (this.$uv.test.object(item)) {
+				return item[this.keyName]
+			} else {
+				return item
+			}
+		},
+		// 点击工具栏的取消按钮
+		cancel() {
+			this.$emit('cancel');
+			this.close();
+		},
+		// 点击工具栏的确定按钮
+		confirm() {
+			this.$emit('confirm', {
+				indexs: this.innerIndex,
+				value: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),
+				values: this.innerColumns
+			});
+			if(this.closeOnClickConfirm) {
+				this.close();
+			}
+		},
+		// 选择器某一列的数据发生变化时触发
+		changeHandler(e) {
+			const {
+				value
+			} = e.detail
+			let index = 0,
+				columnIndex = 0
+			// 通过对比前后两次的列索引,得出当前变化的是哪一列
+			for (let i = 0; i < value.length; i++) {
+				let item = value[i]
+				if (item !== (this.lastIndex[i] || 0)) { // 把undefined转为合法假值0
+					// 设置columnIndex为当前变化列的索引
+					columnIndex = i
+					// index则为变化列中的变化项的索引
+					index = item
+					break // 终止循环,即使少一次循环,也是性能的提升
+				}
+			}
+			this.columnIndex = columnIndex
+			const values = this.innerColumns
+			// 将当前的各项变化索引,设置为"上一次"的索引变化值
+			this.setLastIndex(value)
+			this.setIndexs(value)
+
+			this.$emit('change', {
+				value: this.innerColumns.map((item, index) => item[value[index]]),
+				index,
+				indexs: value,
+				// values为当前变化列的数组内容
+				values,
+				columnIndex
+			})
+		},
+		// 设置index索引,此方法可被外部调用设置
+		setIndexs(index, setLastIndex) {
+			this.innerIndex = this.$uv.deepClone(index)
+			if (setLastIndex) {
+				this.setLastIndex(index)
+			}
+		},
+		// 记录上一次的各列索引位置
+		setLastIndex(index) {
+			// 当能进入此方法,意味着当前设置的各列默认索引,即为“上一次”的选中值,需要记录,是因为changeHandler中
+			// 需要拿前后的变化值进行对比,得出当前发生改变的是哪一列
+			this.lastIndex = this.$uv.deepClone(index)
+		},
+		// 设置对应列选项的所有值
+		setColumnValues(columnIndex, values) {
+			// 替换innerColumns数组中columnIndex索引的值为values,使用的是数组的splice方法
+			this.innerColumns.splice(columnIndex, 1, values)
+			// 拷贝一份原有的innerIndex做临时变量,将大于当前变化列的所有的列的默认索引设置为0
+			let tmpIndex = this.$uv.deepClone(this.innerIndex)
+			for (let i = 0; i < this.innerColumns.length; i++) {
+				if (i > this.columnIndex) {
+					tmpIndex[i] = 0
+				}
+			}
+			// 一次性赋值,不能单个修改,否则无效
+			this.setIndexs(tmpIndex)
+		},
+		// 获取对应列的所有选项
+		getColumnValues(columnIndex) {
+			// 进行同步阻塞,因为外部得到change事件之后,可能需要执行setColumnValues更新列的值
+			// 索引如果在外部change的回调中调用getColumnValues的话,可能无法得到变更后的列值,这里进行一定延时,保证值的准确性
+			(async () => {
+				await this.$uv.sleep()
+			})()
+			return this.innerColumns[columnIndex]
+		},
+		// 设置整体各列的columns的值
+		setColumns(columns) {
+			this.innerColumns = this.$uv.deepClone(columns)
+			// 如果在设置各列数据时,没有被设置默认的各列索引defaultIndex,那么用0去填充它,数组长度为列的数量
+			if (this.innerIndex.length === 0) {
+				this.innerIndex = new Array(columns.length).fill(0)
+			}
+		},
+		// 获取各列选中值对应的索引
+		getIndexs() {
+			return this.innerIndex
+		},
+		// 获取各列选中的值
+		getValues() {
+			// 进行同步阻塞,因为外部得到change事件之后,可能需要执行setColumnValues更新列的值
+			// 索引如果在外部change的回调中调用getValues的话,可能无法得到变更后的列值,这里进行一定延时,保证值的准确性
+			(async () => {
+				await this.$uv.sleep()
+			})()
+			return this.innerColumns.map((item, index) => item[this.innerIndex[index]])
+		}
+	},
+}
+</script>
+
+<style lang="scss" scoped>
+	$show-lines: 1;
+	@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	.uv-picker {
+		position: relative;
+
+		&__view {
+
+			&__column {
+				@include flex;
+				flex: 1;
+				justify-content: center;
+
+				&__item {
+					@include flex;
+					justify-content: center;
+					align-items: center;
+					font-size: 16px;
+					text-align: center;
+					/* #ifndef APP-NVUE */
+					display: block;
+					/* #endif */
+					color: $uv-main-color;
+
+					&--disabled {
+						/* #ifndef APP-NVUE */
+						cursor: not-allowed;
+						/* #endif */
+						opacity: 0.35;
+					}
+				}
+			}
+		}
+
+		&--loading {
+			position: absolute;
+			top: 0;
+			right: 0;
+			left: 0;
+			bottom: 0;
+			@include flex;
+			justify-content: center;
+			align-items: center;
+			background-color: rgba(255, 255, 255, 0.87);
+			z-index: 1000;
+		}
+	}
+</style>

+ 39 - 0
uni_modules/uv-picker/components/uv-toolbar/props.js

@@ -0,0 +1,39 @@
+export default {
+	props: {
+		// 是否展示工具条
+		show: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示下边框
+		showBorder: {
+			type: Boolean,
+			default: false
+		},
+		// 取消按钮的文字
+		cancelText: {
+			type: String,
+			default: '取消'
+		},
+		// 确认按钮的文字
+		confirmText: {
+			type: String,
+			default: '确认'
+		},
+		// 取消按钮的颜色
+		cancelColor: {
+			type: String,
+			default: '#909193'
+		},
+		// 确认按钮的颜色
+		confirmColor: {
+			type: String,
+			default: '#3c9cff'
+		},
+		// 标题文字
+		title: {
+			type: String,
+			default: ''
+		}
+	}
+}

+ 109 - 0
uni_modules/uv-picker/components/uv-toolbar/uv-toolbar.vue

@@ -0,0 +1,109 @@
+<template>
+	<view
+		:class="['uv-toolbar',{'uv-border-bottom':showBorder}]"
+		@touchmove.stop.prevent="noop"
+		v-if="show"
+	>
+		<view
+			class="uv-toolbar__cancel__wrapper"
+			hover-class="uv-hover-class"
+		>
+			<text
+				class="uv-toolbar__wrapper__cancel"
+				@tap="cancel"
+				:style="{
+					color: cancelColor
+				}"
+			>{{ cancelText }}</text>
+		</view>
+		<text
+			class="uv-toolbar__title uv-line-1"
+			v-if="title"
+		>{{ title }}</text>
+		<view
+			class="uv-toolbar__confirm__wrapper"
+			hover-class="uv-hover-class"
+		>
+			<text
+				class="uv-toolbar__wrapper__confirm"
+				@tap="confirm"
+				:style="{
+				color: confirmColor
+			}"
+			>{{ confirmText }}</text>
+		</view>
+	</view>
+</template>
+
+<script>
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	import props from './props.js';
+	/**
+	 * Toolbar 工具条
+	 * @description 
+	 * @tutorial https://www.uvui.cn/components/toolbar.html
+	 * @property {Boolean}	show			是否展示工具条(默认 true )
+	 * @property {Boolean}	showBorder			是否展示工具条下方边框(默认 false )
+	 * @property {String}	cancelText		取消按钮的文字(默认 '取消' )
+	 * @property {String}	confirmText		确认按钮的文字(默认 '确认' )
+	 * @property {String}	cancelColor		取消按钮的颜色(默认 '#909193' )
+	 * @property {String}	confirmColor	确认按钮的颜色(默认 '#3c9cff' )
+	 * @property {String}	title			标题文字
+	 * @event {Function} 
+	 * @example 
+	 */
+	export default {
+		name: 'uv-toolbar',
+		emits: ['confirm','cancel'],
+		mixins: [mpMixin, mixin, props],
+		methods: {
+			// 点击取消按钮
+			cancel() {
+				this.$emit('cancel')
+			},
+			// 点击确定按钮
+			confirm() {
+				this.$emit('confirm')
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	$show-lines: 1;
+	$show-hover: 1;
+	@import '@/uni_modules/uv-ui-tools/libs/css/variable.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+	@import '@/uni_modules/uv-ui-tools/libs/css/color.scss';
+	.uv-toolbar {
+		height: 42px;
+		@include flex;
+		justify-content: space-between;
+		align-items: center;
+
+		&__wrapper {
+			&__cancel {
+				color: $uv-tips-color;
+				font-size: 15px;
+				padding: 0 15px;
+			}
+		}
+
+		&__title {
+			color: $uv-main-color;
+			padding: 0 60rpx;
+			font-size: 16px;
+			flex: 1;
+			text-align: center;
+		}
+
+		&__wrapper {
+			&__confirm {
+				color: $uv-primary;
+				font-size: 15px;
+				padding: 0 15px;
+			}
+		}
+	}
+</style>

+ 89 - 0
uni_modules/uv-picker/package.json

@@ -0,0 +1,89 @@
+{
+  "id": "uv-picker",
+  "displayName": "uv-picker 选择器  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.6",
+  "description": "uv-picker 此选择器用于单列,多列,多列联动的选择场景。",
+  "keywords": [
+    "uv-picker",
+    "uvui",
+    "uv-ui",
+    "picker",
+    "联动选择"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-popup",
+			"uv-loading-icon"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-picker/readme.md

@@ -0,0 +1,11 @@
+## Picker 选择器
+
+> **组件名:uv-picker**
+
+此选择器用于单列,多列,多列联动的选择场景。
+
+### <a href="https://www.uvui.cn/components/picker.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 9 - 0
uni_modules/uv-popup/changelog.md

@@ -0,0 +1,9 @@
+## 1.0.3(2023-07-02)
+uv-popup  弹出层,代码重构优化,性能翻倍,小程序体验性能更加,避免卡顿。打开和关闭方法更改,详情参考文档:https://www.uvui.cn/components/popup.html
+## 1.0.2(2023-06-11)
+1. 修复zIndex层级问题
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-popup组件

+ 45 - 0
uni_modules/uv-popup/components/uv-popup/keypress.js

@@ -0,0 +1,45 @@
+// #ifdef H5
+export default {
+  name: 'Keypress',
+  props: {
+    disable: {
+      type: Boolean,
+      default: false
+    }
+  },
+  mounted () {
+    const keyNames = {
+      esc: ['Esc', 'Escape'],
+      tab: 'Tab',
+      enter: 'Enter',
+      space: [' ', 'Spacebar'],
+      up: ['Up', 'ArrowUp'],
+      left: ['Left', 'ArrowLeft'],
+      right: ['Right', 'ArrowRight'],
+      down: ['Down', 'ArrowDown'],
+      delete: ['Backspace', 'Delete', 'Del']
+    }
+    const listener = ($event) => {
+      if (this.disable) {
+        return
+      }
+      const keyName = Object.keys(keyNames).find(key => {
+        const keyName = $event.key
+        const value = keyNames[key]
+        return value === keyName || (Array.isArray(value) && value.includes(keyName))
+      })
+      if (keyName) {
+        // 避免和其他按键事件冲突
+        setTimeout(() => {
+          this.$emit(keyName, {})
+        }, 0)
+      }
+    }
+    document.addEventListener('keyup', listener)
+    // this.$once('hook:beforeDestroy', () => {
+    //   document.removeEventListener('keyup', listener)
+    // })
+  },
+	render: () => {}
+}
+// #endif

+ 80 - 0
uni_modules/uv-popup/components/uv-popup/props.js

@@ -0,0 +1,80 @@
+export default {
+	props: {
+		// 是否展示弹窗
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 是否显示遮罩
+		overlay: {
+			type: Boolean,
+			default: true
+		},
+		// 弹出的方向,可选值为 top bottom right left center
+		mode: {
+			type: String,
+			default: 'bottom'
+		},
+		// 动画时长,单位ms
+		duration: {
+			type: [String, Number],
+			default: 300
+		},
+		// 是否显示关闭图标
+		closeable: {
+			type: Boolean,
+			default: false
+		},
+		// 自定义遮罩的样式
+		overlayStyle: {
+			type: [Object, String],
+			default: ''
+		},
+		// 点击遮罩是否关闭弹窗
+		closeOnClickOverlay: {
+			type: Boolean,
+			default: true
+		},
+		// 层级
+		zIndex: {
+			type: [String, Number],
+			default: 10075
+		},
+		// 是否为iPhoneX留出底部安全距离
+		safeAreaInsetBottom: {
+			type: Boolean,
+			default: true
+		},
+		// 是否留出顶部安全距离(状态栏高度)
+		safeAreaInsetTop: {
+			type: Boolean,
+			default: false
+		},
+		// 自定义关闭图标位置,top-left为左上角,top-right为右上角,bottom-left为左下角,bottom-right为右下角
+		closeIconPos: {
+			type: String,
+			default: 'top-right'
+		},
+		// 圆角值
+		round: {
+			type: [Boolean, String, Number],
+			default: 0
+		},
+		// mode=center,也即中部弹出时,是否使用缩放模式
+		zoom: {
+			type: Boolean,
+			default: true
+		},
+		// 弹窗背景色,设置为transparent可去除白色背景
+		bgColor: {
+			type: String,
+			default: ''
+		},
+		// 遮罩的透明度,0-1之间
+		overlayOpacity: {
+			type: [Number, String],
+			default: 0.5
+		},
+		...uni.$uv?.props?.popup
+	}
+}

+ 527 - 0
uni_modules/uv-popup/components/uv-popup/uv-popup.vue

@@ -0,0 +1,527 @@
+<template>
+	<view 
+		v-if="showPopup" 
+		class="uv-popup" 
+		:class="[popupClass, isDesktop ? 'fixforpc-z-index' : '']"
+	>
+		<view @touchstart="touchstart">
+			<!-- 遮罩层 -->
+			<uv-overlay
+				key="1"
+				v-if="maskShow && overlay"
+				:show="showTrans"
+				:duration="duration"
+				:custom-style="overlayStyle"
+				:opacity="overlayOpacity"
+			  :zIndex="zIndex"
+				@click="onTap"
+			></uv-overlay>
+			<uv-transition 
+				key="2" 
+				:mode="ani" 
+				name="content" 
+				:custom-style="transitionStyle" 
+				:duration="duration"
+				:show="showTrans" 
+				@click="onTap"
+			>
+				<view 
+					class="uv-popup__content" 
+					:style="[contentStyle]" 
+					:class="[popupClass]" 
+					@click="clear"
+				>
+					<uv-status-bar v-if="safeAreaInsetTop"></uv-status-bar>
+					<slot />
+					<uv-safe-bottom v-if="safeAreaInsetBottom"></uv-safe-bottom>
+					<view
+						v-if="closeable"
+						@tap.stop="close"
+						class="uv-popup__content__close"
+						:class="['uv-popup__content__close--' + closeIconPos]"
+						hover-class="uv-popup__content__close--hover"
+						hover-stay-time="150"
+					>
+						<uv-icon
+							name="close"
+							color="#909399"
+							size="18"
+							bold
+						></uv-icon>
+					</view>
+				</view>
+			</uv-transition>
+		</view>
+		<!-- #ifdef H5 -->
+		<keypress v-if="maskShow" @esc="onTap" />
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+	// #ifdef H5
+	import keypress from './keypress.js'
+	// #endif
+	import mpMixin from '@/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js'
+	import mixin from '@/uni_modules/uv-ui-tools/libs/mixin/mixin.js'
+	/**
+	* PopUp 弹出层
+	* @description 弹出层组件,为了解决遮罩弹层的问题
+	* @tutorial https://www.uvui.cn/components/popup.html
+	* @property {String} mode = [top|center|bottom|left|right] 弹出方式
+	* 	@value top 顶部弹出
+	* 	@value center 中间弹出
+	* 	@value bottom 底部弹出
+	* 	@value left		左侧弹出
+	* 	@value right  右侧弹出
+	* @property {Number} duration 动画时长,默认300
+	* @property {Boolean} overlay 是否显示遮罩,默认true
+	* @property {Boolean} overlayOpacity 遮罩透明度,默认0.5 
+	* @property {Object} overlayStyle 遮罩自定义样式
+	* @property {Boolean} closeOnClickOverlay = [true|false] 蒙版点击是否关闭弹窗,默认true
+	* @property {Number | String} zIndex 弹出层的层级
+	* @property {Boolean} safeAreaInsetTop 是否留出顶部安全区(状态栏高度),默认false
+	* @property {Boolean} safeAreaInsetBottom 是否为留出底部安全区适配,默认true
+	* @property {Boolean} closeable 是否显示关闭图标,默认false
+	* @property {Boolean} closeIconPos 自定义关闭图标位置,`top-left`-左上角,`top-right`-右上角,`bottom-left`-左下角,`bottom-right`-右下角,默认top-right
+	* @property {String}  bgColor 主窗口背景色
+	* @property {String}  maskBackgroundColor 蒙版颜色
+	* @property {Boolean} customStyle 自定义样式
+	* @event {Function} change 打开关闭弹窗触发,e={show: false}
+	* @event {Function} maskClick 点击遮罩触发
+	*/
+	export default {
+		name: 'uv-popup',
+		components: {
+			// #ifdef H5
+			keypress
+			// #endif
+		},
+		mixins: [mpMixin, mixin],
+		emits: ['change', 'maskClick'],
+		props: {
+			// 弹出层类型,可选值,top: 顶部弹出层;bottom:底部弹出层;center:全屏弹出层
+			// message: 消息提示 ; dialog : 对话框
+			mode: {
+				type: String,
+				default: 'center'
+			},
+			// 动画时长,单位ms
+			duration: {
+				type: [String, Number],
+				default: 300
+			},
+			// 层级
+			zIndex: {
+				type: [String, Number],
+				default: 10075
+			},
+			bgColor: {
+				type: String,
+				default: '#ffffff'
+			},
+			safeArea: {
+				type: Boolean,
+				default: true
+			},
+			// 是否显示遮罩
+			overlay: {
+				type: Boolean,
+				default: true
+			},
+			// 点击遮罩是否关闭弹窗
+			closeOnClickOverlay: {
+				type: Boolean,
+				default: true
+			},
+			// 遮罩的透明度,0-1之间
+			overlayOpacity: {
+				type: [Number, String],
+				default: 0.4
+			},
+			// 自定义遮罩的样式
+			overlayStyle: {
+				type: [Object, String],
+				default: ''
+			},
+			// 是否为iPhoneX留出底部安全距离
+			safeAreaInsetBottom: {
+				type: Boolean,
+				default: true
+			},
+			// 是否留出顶部安全距离(状态栏高度)
+			safeAreaInsetTop: {
+				type: Boolean,
+				default: false
+			},
+			// 是否显示关闭图标
+			closeable: {
+				type: Boolean,
+				default: false
+			},
+			// 自定义关闭图标位置,top-left为左上角,top-right为右上角,bottom-left为左下角,bottom-right为右下角
+			closeIconPos: {
+				type: String,
+				default: 'top-right'
+			},
+			// mode=center,也即中部弹出时,是否使用缩放模式
+			zoom: {
+				type: Boolean,
+				default: true
+			},
+			round: {
+				type: [Number, String],
+				default: 0
+			}
+		},
+		watch: {
+			/**
+			 * 监听type类型
+			 */
+			type: {
+				handler: function(type) {
+					if (!this.config[type]) return
+					this[this.config[type]](true)
+				},
+				immediate: true
+			},
+			isDesktop: {
+				handler: function(newVal) {
+					if (!this.config[newVal]) return
+					this[this.config[this.mode]](true)
+				},
+				immediate: true
+			},
+			// H5 下禁止底部滚动
+			showPopup(show) {
+				// #ifdef H5
+				// fix by mehaotian 处理 h5 滚动穿透的问题
+				document.getElementsByTagName('body')[0].style.overflow = show ? 'hidden' : 'visible'
+				// #endif
+			}
+		},
+		data() {
+			return {
+				ani: [],
+				showPopup: false,
+				showTrans: false,
+				popupWidth: 0,
+				popupHeight: 0,
+				config: {
+					top: 'top',
+					bottom: 'bottom',
+					center: 'center',
+					left: 'left',
+					right: 'right',
+					message: 'top',
+					dialog: 'center',
+					share: 'bottom'
+				},
+				transitionStyle: {
+					position: 'fixed',
+					left: 0,
+					right: 0
+				},
+				maskShow: true,
+				mkclick: true,
+				popupClass: this.isDesktop ? 'fixforpc-top' : 'top'
+			}
+		},
+		computed: {
+			isDesktop() {
+				return this.popupWidth >= 500 && this.popupHeight >= 500
+			},
+			bg() {
+				if (this.bgColor === '' || this.bgColor === 'none' || this.$uv.getPx(this.round)>0) {
+					return 'transparent'
+				}
+				return this.bgColor
+			},
+			contentStyle() {
+				const style = {};
+				if (this.bgColor) {
+					style.backgroundColor = this.bg
+				}
+				if(this.round) {
+					const value = this.$uv.addUnit(this.round)
+					style.backgroundColor = this.bgColor
+					if(this.mode === 'top') {
+						style.borderBottomLeftRadius = value
+						style.borderBottomRightRadius = value
+					} else if(this.mode === 'bottom') {
+						style.borderTopLeftRadius = value
+						style.borderTopRightRadius = value
+					} else if(this.mode === 'center') {
+						style.borderRadius = value
+					} 
+				}
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+			}
+		},
+		// #ifndef VUE3
+		// TODO vue2
+		destroyed() {
+			this.setH5Visible()
+		},
+		// #endif
+		// #ifdef VUE3
+		// TODO vue3
+		unmounted() {
+			this.setH5Visible()
+		},
+		// #endif
+		created() {
+			// TODO 处理 message 组件生命周期异常的问题
+			this.messageChild = null
+			// TODO 解决头条冒泡的问题
+			this.clearPropagation = false
+		},
+		methods: {
+			setH5Visible() {
+				// #ifdef H5
+				// fix by mehaotian 处理 h5 滚动穿透的问题
+				document.getElementsByTagName('body')[0].style.overflow = 'visible'
+				// #endif
+			},
+			/**
+			 * 公用方法,不显示遮罩层
+			 */
+			closeMask() {
+				this.maskShow = false
+			},
+			// TODO nvue 取消冒泡
+			clear(e) {
+				// #ifndef APP-NVUE
+				e.stopPropagation()
+				// #endif
+				this.clearPropagation = true
+			},
+
+			open(direction) {
+				// fix by mehaotian 处理快速打开关闭的情况
+				if (this.showPopup) {
+					return
+				}
+				let innerType = ['top', 'center', 'bottom', 'left', 'right', 'message', 'dialog', 'share']
+				if (!(direction && innerType.indexOf(direction) !== -1)) {
+					direction = this.mode
+				}
+				if (!this.config[direction]) {
+					return this.$uv.error(`缺少类型:${direction}`);
+				}
+				this[this.config[direction]]()
+				this.$emit('change', {
+					show: true,
+					type: direction
+				})
+			},
+			close(type) {
+				this.showTrans = false
+				this.$emit('change', {
+					show: false,
+					type: this.mode
+				})
+				clearTimeout(this.timer)
+				// // 自定义关闭事件
+				this.timer = setTimeout(() => {
+					this.showPopup = false
+				}, 300)
+			},
+			// TODO 处理冒泡事件,头条的冒泡事件有问题 ,先这样兼容
+			touchstart() {
+				this.clearPropagation = false
+			},
+			onTap() {
+				if (this.clearPropagation) {
+					// fix by mehaotian 兼容 nvue
+					this.clearPropagation = false
+					return
+				}
+				this.$emit('maskClick')
+				if (!this.closeOnClickOverlay) return
+				this.close()
+			},
+			/**
+			 * 顶部弹出样式处理
+			 */
+			top(type) {
+				this.popupClass = this.isDesktop ? 'fixforpc-top' : 'top'
+				this.ani = ['slide-top']
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					left: 0,
+					right: 0,
+					backgroundColor: this.bg
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+				this.$nextTick(() => {
+					if (this.messageChild && this.mode === 'message') {
+						this.messageChild.timerClose()
+					}
+				})
+			},
+			/**
+			 * 底部弹出样式处理
+			 */
+			bottom(type) {
+				this.popupClass = 'bottom'
+				this.ani = ['slide-bottom']
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					left: 0,
+					right: 0,
+					bottom: 0,
+					backgroundColor: this.bg
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			/**
+			 * 中间弹出样式处理
+			 */
+			center(type) {
+				this.popupClass = 'center'
+				this.ani = this.zoom?['zoom-in', 'fade']:['fade'];
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column',
+					/* #endif */
+					bottom: 0,
+					left: 0,
+					right: 0,
+					top: 0,
+					justifyContent: 'center',
+					alignItems: 'center'
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			left(type) {
+				this.popupClass = 'left'
+				this.ani = ['slide-left']
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					left: 0,
+					bottom: 0,
+					top: 0,
+					backgroundColor: this.bg,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column'
+					/* #endif */
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			},
+			right(type) {
+				this.popupClass = 'right'
+				this.ani = ['slide-right']
+				this.transitionStyle = {
+					position: 'fixed',
+					zIndex: this.zIndex,
+					bottom: 0,
+					right: 0,
+					top: 0,
+					backgroundColor: this.bg,
+					/* #ifndef APP-NVUE */
+					display: 'flex',
+					flexDirection: 'column'
+					/* #endif */
+				}
+				// TODO 兼容 type 属性 ,后续会废弃
+				if (type) return
+				this.showPopup = true
+				this.showTrans = true
+			}
+		}
+	}
+</script>
+<style lang="scss" scoped>
+	.uv-popup {
+		position: fixed;
+		/* #ifndef APP-NVUE */
+		z-index: 99;
+
+		/* #endif */
+		&.top,
+		&.left,
+		&.right {
+			/* #ifdef H5 */
+			top: var(--window-top);
+			/* #endif */
+			/* #ifndef H5 */
+			top: 0;
+			/* #endif */
+		}
+
+		.uv-popup__content {
+			/* #ifndef APP-NVUE */
+			display: block;
+			/* #endif */
+			position: relative;
+
+			&.left,
+			&.right {
+				/* #ifdef H5 */
+				padding-top: var(--window-top);
+				/* #endif */
+				/* #ifndef H5 */
+				padding-top: 0;
+				/* #endif */
+				flex: 1;
+			}
+			&__close {
+				position: absolute;
+
+				&--hover {
+					opacity: 0.4;
+				}
+			}
+			
+			&__close--top-left {
+				top: 15px;
+				left: 15px;
+			}
+			
+			&__close--top-right {
+				top: 15px;
+				right: 15px;
+			}
+			
+			&__close--bottom-left {
+				bottom: 15px;
+				left: 15px;
+			}
+			
+			&__close--bottom-right {
+				right: 15px;
+				bottom: 15px;
+			}
+		}
+	}
+
+	.fixforpc-z-index {
+		/* #ifndef APP-NVUE */
+		z-index: 999;
+		/* #endif */
+	}
+
+	.fixforpc-top {
+		top: 0;
+	}
+</style>

+ 92 - 0
uni_modules/uv-popup/package.json

@@ -0,0 +1,92 @@
+{
+  "id": "uv-popup",
+  "displayName": "uv-popup 弹出层  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.3",
+  "description": "uv-popup 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。",
+  "keywords": [
+    "uv-popup",
+    "uvui",
+    "uv-ui",
+    "popup",
+    "弹出层"
+],
+  "repository": "",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+  "dcloudext": {
+    "type": "component-vue",
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+    	"ads": "无",
+    	"data": "插件不采集任何数据",
+    	"permissions": "无"
+    },
+    "npmurl": ""
+  },
+  "uni_modules": {
+    "dependencies": [
+			"uv-ui-tools",
+			"uv-overlay",
+			"uv-transition",
+			"uv-icon",
+			"uv-status-bar",
+			"uv-safe-bottom"
+		],
+    "encrypt": [],
+    "platforms": {
+			"cloud": {
+				"tcb": "y",
+				"aliyun": "y"
+			},
+			"client": {
+				"Vue": {
+					"vue2": "y",
+					"vue3": "y"
+				},
+				"App": {
+					"app-vue": "y",
+					"app-nvue": "y"
+				},
+				"H5-mobile": {
+					"Safari": "y",
+					"Android Browser": "y",
+					"微信浏览器(Android)": "y",
+					"QQ浏览器(Android)": "y"
+				},
+				"H5-pc": {
+					"Chrome": "y",
+					"IE": "y",
+					"Edge": "y",
+					"Firefox": "y",
+					"Safari": "y"
+				},
+				"小程序": {
+					"微信": "y",
+					"阿里": "y",
+					"百度": "y",
+					"字节跳动": "y",
+					"QQ": "y",
+					"钉钉": "u",
+					"快手": "u",
+					"飞书": "u",
+					"京东": "u"
+				},
+				"快应用": {
+					"华为": "u",
+					"联盟": "u"
+				}
+			}
+		}
+  }
+}

+ 11 - 0
uni_modules/uv-popup/readme.md

@@ -0,0 +1,11 @@
+## Popup 弹出层
+
+> **组件名:uv-popup**
+
+弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义。
+
+### <a href="https://www.uvui.cn/components/popup.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+#### 如使用过程中有任何问题,或者您对uv-ui有一些好的建议,欢迎加入 uv-ui 交流群:<a href="https://ext.dcloud.net.cn/plugin?id=12287" target="_blank">uv-ui</a>、<a href="https://www.uvui.cn/components/addQQGroup.html" target="_blank">官方QQ群</a>

+ 7 - 0
uni_modules/uv-status-bar/changelog.md

@@ -0,0 +1,7 @@
+## 1.0.2(2023-06-05)
+1. 兼容渐变背景色
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增uv-status-bar组件

+ 8 - 0
uni_modules/uv-status-bar/components/uv-status-bar/props.js

@@ -0,0 +1,8 @@
+export default {
+    props: {
+        bgColor: {
+            type: String,
+            default: 'transparent'
+        }
+    }
+}

+ 0 - 0
uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue


Daži faili netika attēloti, jo izmaiņu fails ir pārāk liels