Browse Source

炒作时间

chenrui  2 years ago
parent
commit
2c3ecd1286
97 changed files with 9168 additions and 19 deletions
  1. 34 12
      pages/detail/index.vue
  2. 7 0
      uni_modules/uv-datetime-picker/changelog.md
  3. 125 0
      uni_modules/uv-datetime-picker/components/uv-datetime-picker/props.js
  4. 373 0
      uni_modules/uv-datetime-picker/components/uv-datetime-picker/uv-datetime-picker.vue
  5. 88 0
      uni_modules/uv-datetime-picker/package.json
  6. 11 0
      uni_modules/uv-datetime-picker/readme.md
  7. 9 0
      uni_modules/uv-icon/changelog.md
  8. 214 0
      uni_modules/uv-icon/components/uv-icon/icons.js
  9. 90 0
      uni_modules/uv-icon/components/uv-icon/props.js
  10. 663 0
      uni_modules/uv-icon/components/uv-icon/uniicons.css
  11. 46 0
      uni_modules/uv-icon/components/uv-icon/uv-icon.vue
  12. 83 0
      uni_modules/uv-icon/package.json
  13. 11 0
      uni_modules/uv-icon/readme.md
  14. 5 0
      uni_modules/uv-loading-icon/changelog.md
  15. 60 0
      uni_modules/uv-loading-icon/components/uv-loading-icon/props.js
  16. 347 0
      uni_modules/uv-loading-icon/components/uv-loading-icon/uv-loading-icon.vue
  17. 87 0
      uni_modules/uv-loading-icon/package.json
  18. 11 0
      uni_modules/uv-loading-icon/readme.md
  19. 5 0
      uni_modules/uv-overlay/changelog.md
  20. 25 0
      uni_modules/uv-overlay/components/uv-overlay/props.js
  21. 70 0
      uni_modules/uv-overlay/components/uv-overlay/uv-overlay.vue
  22. 88 0
      uni_modules/uv-overlay/package.json
  23. 11 0
      uni_modules/uv-overlay/readme.md
  24. 7 0
      uni_modules/uv-picker/changelog.md
  25. 80 0
      uni_modules/uv-picker/components/uv-picker/props.js
  26. 288 0
      uni_modules/uv-picker/components/uv-picker/uv-picker.vue
  27. 39 0
      uni_modules/uv-picker/components/uv-toolbar/props.js
  28. 109 0
      uni_modules/uv-picker/components/uv-toolbar/uv-toolbar.vue
  29. 89 0
      uni_modules/uv-picker/package.json
  30. 11 0
      uni_modules/uv-picker/readme.md
  31. 5 0
      uni_modules/uv-popup/changelog.md
  32. 80 0
      uni_modules/uv-popup/components/uv-popup/props.js
  33. 307 0
      uni_modules/uv-popup/components/uv-popup/uv-popup.vue
  34. 92 0
      uni_modules/uv-popup/package.json
  35. 11 0
      uni_modules/uv-popup/readme.md
  36. 5 0
      uni_modules/uv-safe-bottom/changelog.md
  37. 67 0
      uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue
  38. 87 0
      uni_modules/uv-safe-bottom/package.json
  39. 11 0
      uni_modules/uv-safe-bottom/readme.md
  40. 5 0
      uni_modules/uv-status-bar/changelog.md
  41. 8 0
      uni_modules/uv-status-bar/components/uv-status-bar/props.js
  42. 48 0
      uni_modules/uv-status-bar/components/uv-status-bar/uv-status-bar.vue
  43. 87 0
      uni_modules/uv-status-bar/package.json
  44. 10 0
      uni_modules/uv-status-bar/readme.md
  45. 7 0
      uni_modules/uv-transition/changelog.md
  46. 68 0
      uni_modules/uv-transition/components/uv-transition/nvue.ani-map.js
  47. 25 0
      uni_modules/uv-transition/components/uv-transition/props.js
  48. 149 0
      uni_modules/uv-transition/components/uv-transition/transition.js
  49. 90 0
      uni_modules/uv-transition/components/uv-transition/uv-transition.vue
  50. 113 0
      uni_modules/uv-transition/components/uv-transition/vue.ani-style.scss
  51. 87 0
      uni_modules/uv-transition/package.json
  52. 11 0
      uni_modules/uv-transition/readme.md
  53. 12 0
      uni_modules/uv-ui-tools/changelog.md
  54. 6 0
      uni_modules/uv-ui-tools/components/uv-ui-tools/uv-ui-tools.vue
  55. 40 0
      uni_modules/uv-ui-tools/index.js
  56. 7 0
      uni_modules/uv-ui-tools/index.scss
  57. 34 0
      uni_modules/uv-ui-tools/libs/config/config.js
  58. 151 0
      uni_modules/uv-ui-tools/libs/css/color.scss
  59. 100 0
      uni_modules/uv-ui-tools/libs/css/common.scss
  60. 20 0
      uni_modules/uv-ui-tools/libs/css/components.scss
  61. 111 0
      uni_modules/uv-ui-tools/libs/css/variable.scss
  62. 27 0
      uni_modules/uv-ui-tools/libs/css/vue.scss
  63. 134 0
      uni_modules/uv-ui-tools/libs/function/colorGradient.js
  64. 29 0
      uni_modules/uv-ui-tools/libs/function/debounce.js
  65. 167 0
      uni_modules/uv-ui-tools/libs/function/digit.js
  66. 733 0
      uni_modules/uv-ui-tools/libs/function/index.js
  67. 75 0
      uni_modules/uv-ui-tools/libs/function/platform.js
  68. 287 0
      uni_modules/uv-ui-tools/libs/function/test.js
  69. 30 0
      uni_modules/uv-ui-tools/libs/function/throttle.js
  70. 97 0
      uni_modules/uv-ui-tools/libs/luch-request/adapters/index.js
  71. 50 0
      uni_modules/uv-ui-tools/libs/luch-request/core/InterceptorManager.js
  72. 198 0
      uni_modules/uv-ui-tools/libs/luch-request/core/Request.js
  73. 20 0
      uni_modules/uv-ui-tools/libs/luch-request/core/buildFullPath.js
  74. 29 0
      uni_modules/uv-ui-tools/libs/luch-request/core/defaults.js
  75. 3 0
      uni_modules/uv-ui-tools/libs/luch-request/core/dispatchRequest.js
  76. 103 0
      uni_modules/uv-ui-tools/libs/luch-request/core/mergeConfig.js
  77. 16 0
      uni_modules/uv-ui-tools/libs/luch-request/core/settle.js
  78. 69 0
      uni_modules/uv-ui-tools/libs/luch-request/helpers/buildURL.js
  79. 14 0
      uni_modules/uv-ui-tools/libs/luch-request/helpers/combineURLs.js
  80. 14 0
      uni_modules/uv-ui-tools/libs/luch-request/helpers/isAbsoluteURL.js
  81. 116 0
      uni_modules/uv-ui-tools/libs/luch-request/index.d.ts
  82. 3 0
      uni_modules/uv-ui-tools/libs/luch-request/index.js
  83. 131 0
      uni_modules/uv-ui-tools/libs/luch-request/utils.js
  84. 264 0
      uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js
  85. 13 0
      uni_modules/uv-ui-tools/libs/mixin/button.js
  86. 152 0
      uni_modules/uv-ui-tools/libs/mixin/mixin.js
  87. 8 0
      uni_modules/uv-ui-tools/libs/mixin/mpMixin.js
  88. 13 0
      uni_modules/uv-ui-tools/libs/mixin/mpShare.js
  89. 25 0
      uni_modules/uv-ui-tools/libs/mixin/openType.js
  90. 59 0
      uni_modules/uv-ui-tools/libs/mixin/touch.js
  91. 218 0
      uni_modules/uv-ui-tools/libs/util/dayjs.js
  92. 124 0
      uni_modules/uv-ui-tools/libs/util/route.js
  93. 81 0
      uni_modules/uv-ui-tools/package.json
  94. 13 0
      uni_modules/uv-ui-tools/readme.md
  95. 43 0
      uni_modules/uv-ui-tools/theme.scss
  96. 29 5
      unpackage/dist/dev/app-plus/app-service.js
  97. 841 2
      unpackage/dist/dev/app-plus/app-view.js

+ 34 - 12
pages/detail/index.vue

@@ -54,9 +54,9 @@
 						</uni-forms>
 						<view class="listup"></view>
 					</view>
-					<view class="list">
+					<view class="list" @click="show = true">
 						<view class="lable">操作时间</view>
-						<uni-easyinput :inputBorder="false" :clearable="false" :styles="style" :disabled="false" v-model="dataheight"
+						<uni-easyinput :inputBorder="false" :clearable="false" :styles="style" :disabled="true" v-model="datacontime"
 							type="text" placeholder="操作时间" />
 						</uni-forms>
 						<view class="listup"></view>
@@ -128,12 +128,19 @@
 			</view>
 		</view>
 		<luanqing-date-picker 
-            ref="datePickerObj" 
-            :isSimple="mode === 'simple'" 
-            :isMultiple="mode === 'multiple'" 
-            @finishSelectDate="finishSelectDate" 
-            :defaultCheckedList="['2023-03-28','2023-03-25']">
-        </luanqing-date-picker>
+				ref="datePickerObj" 
+				:isSimple="mode === 'simple'" 
+				:isMultiple="mode === 'multiple'" 
+				@finishSelectDate="finishSelectDate" 
+				:defaultCheckedList="['2023-03-28','2023-09-25']">
+		</luanqing-date-picker>
+		<uv-datetime-picker
+		  :show="show"
+				v-model="value1"
+				mode="datetime"
+				@confirm ='confirm'
+				@close = 'show = false'
+		></uv-datetime-picker>
 	</view>
 </template>
 <script>
@@ -161,7 +168,8 @@
 					}
 				],
 				dataheight: '10.75rem',
-				datatime: '',
+				datatime: '2023-06-02',
+				datacontime: '',
 				info: [{
 					content: '内容 A'
 				}, {
@@ -172,6 +180,9 @@
 				current: 0,
 				mode: 'round',
 				mode: 'simple',
+				show: false,
+        value1: Number(new Date()),
+				formatter:'yy'
 			}
 		},
 		created(option) {},
@@ -181,9 +192,20 @@
 		mounted() {},
 		components: {},
 		methods: {
-			show(showType){
-				this.mode = showType;
-				this.$refs.datePickerObj.open();
+			confirm (e) {
+				this.show = false
+				console.log(e.value)
+				let timestamp = e.value
+				// 此处时间戳以毫秒为单位
+				let date = new Date(parseInt(timestamp));
+				let Year = date.getFullYear();
+				let Moth = (date.getMonth() + 1 < 10 ? '0' + (date.getMonth() + 1) : date.getMonth() + 1);
+				let Day = (date.getDate() < 10 ? '0' + date.getDate() : date.getDate());
+				let Hour = (date.getHours() < 10 ? '0' + date.getHours() : date.getHours());
+				let Minute = (date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes());
+				let Sechond = (date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds());
+				let  GMT =  Year + '-' + Moth + '-' + Day + '   '+ Hour +':'+ Minute 
+				this.datacontime = GMT
 			},
 			finishSelectDate(e){
 				this.datatime = e[0]

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

@@ -0,0 +1,7 @@
+## 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 时间选择器

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

@@ -0,0 +1,125 @@
+export default {
+	props: {
+		// 是否打开组件
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 是否展示顶部的操作栏
+		showToolbar: {
+			type: Boolean,
+			default: true
+		},
+		// 绑定值
+		// #ifdef VUE2
+		value: {
+			type: [String, Number],
+			default: ''
+		},
+		// #endif
+		// #ifdef VUE3
+		modelValue: {
+			type: [String, Number],
+			default: ''
+		},
+		// #endif
+		// 顶部标题
+		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: false
+		},
+		// 各列的默认索引
+		defaultIndex: {
+			type: Array,
+			default: () => []
+		},
+		...uni.$uv?.props?.datetimePicker
+	}
+}

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

@@ -0,0 +1,373 @@
+<template>
+	<uv-picker
+		ref="picker"
+		:show="show"
+		:closeOnClickOverlay="closeOnClickOverlay"
+		: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}			show				用于控制选择器的弹出与收起 ( 默认 false )
+	 * @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	是否允许点击遮罩关闭选择器  ( 默认 false )
+	 * @property {Array}			defaultIndex		各列的默认索引
+	 * @event {Function} close 关闭选择器时触发
+	 * @event {Function} confirm 点击确定按钮,返回当前选择的值
+	 * @event {Function} change 当选择值变化时触发
+	 * @event {Function} cancel 点击取消按钮
+	 * @example  <uv-datetime-picker :show="show" :value="value1"  mode="datetime" ></uv-datetime-picker>
+	 */
+	export default {
+		name: 'datetime-picker',
+		emits: ['close','cancel','confirm','input','change','update:modelValue'],
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				columns: [],
+				innerDefaultIndex: [],
+				innerFormatter: (type, value) => value
+			}
+		},
+		watch: {
+			show(newValue, oldValue) {
+				if (newValue) {
+					this.getValue();
+					this.updateColumnValue(this.innerValue)
+				}
+			},
+			propsChange() {
+				this.init()
+			}
+		},
+		computed: {
+			// 如果以下这些变量发生了变化,意味着需要重新初始化各列的值
+			propsChange() {
+				return [this.mode, this.maxDate, this.minDate, this.minHour, this.maxHour, this.minMinute, this.maxMinute, this.filter, ]
+			}
+		},
+		mounted() {
+			this.init()
+		},
+		methods: {
+			init() {
+				this.getValue();
+				this.updateColumnValue(this.innerValue)
+			},
+			getValue() {
+				// #ifdef VUE2
+				this.innerValue = this.correctValue(this.value)
+				// #endif
+				// #ifdef VUE3
+				this.innerValue = this.correctValue(this.modelValue)
+				// #endif
+			},
+			// 在微信小程序中,不支持将函数当做props参数,故只能通过ref形式调用
+			setFormatter(e) {
+				this.innerFormatter = e
+			},
+			// 关闭选择器
+			close() {
+				if (this.closeOnClickOverlay) {
+					this.$emit('close')
+				}
+			},
+			// 点击工具栏的取消按钮
+			cancel() {
+				this.$emit('cancel')
+			},
+			// 点击工具栏的确定按钮
+			confirm() {
+				this.$emit('confirm', {
+					value: this.innerValue,
+					mode: this.mode
+				})
+				// #ifdef VUE2
+				this.$emit('input', this.innerValue)
+				// #endif
+				// #ifdef VUE3
+				this.$emit('update:modelValue',this.innerValue)
+				// #endif
+			},
+			//用正则截取输出值,当出现多组数字时,抛出错误
+			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,
+					// #ifndef MP-WEIXIN
+					// 微信小程序不能传递this实例,会因为循环引用而报错
+					picker: this.$refs.picker,
+					// #endif
+					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 时间选择器 全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.2",
+  "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"
+				}
+			}
+		}
+  }
+}

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

@@ -0,0 +1,11 @@
+## 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)
+
+#### 如使用过程中有任何问题,或者您对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-icon/changelog.md

@@ -0,0 +1,9 @@
+## 1.0.3(2023-05-24)
+1. 将线上ttf字体包替换成base64,避免加载时或者网络差时候显示白色方块
+## 1.0.2(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.1(2023-05-10)
+1. 修复小程序中异常显示
+## 1.0.0(2023-05-04)
+新发版

+ 214 - 0
uni_modules/uv-icon/components/uv-icon/icons.js

@@ -0,0 +1,214 @@
+export default {
+    'uvicon-level': '\ue693',
+    'uvicon-column-line': '\ue68e',
+    'uvicon-checkbox-mark': '\ue807',
+    'uvicon-folder': '\ue7f5',
+    'uvicon-movie': '\ue7f6',
+    'uvicon-star-fill': '\ue669',
+    'uvicon-star': '\ue65f',
+    'uvicon-phone-fill': '\ue64f',
+    'uvicon-phone': '\ue622',
+    'uvicon-apple-fill': '\ue881',
+    'uvicon-chrome-circle-fill': '\ue885',
+    'uvicon-backspace': '\ue67b',
+    'uvicon-attach': '\ue632',
+    'uvicon-cut': '\ue948',
+    'uvicon-empty-car': '\ue602',
+    'uvicon-empty-coupon': '\ue682',
+    'uvicon-empty-address': '\ue646',
+    'uvicon-empty-favor': '\ue67c',
+    'uvicon-empty-permission': '\ue686',
+    'uvicon-empty-news': '\ue687',
+    'uvicon-empty-search': '\ue664',
+    'uvicon-github-circle-fill': '\ue887',
+    'uvicon-rmb': '\ue608',
+    'uvicon-person-delete-fill': '\ue66a',
+    'uvicon-reload': '\ue788',
+    'uvicon-order': '\ue68f',
+    'uvicon-server-man': '\ue6bc',
+    'uvicon-search': '\ue62a',
+    'uvicon-fingerprint': '\ue955',
+    'uvicon-more-dot-fill': '\ue630',
+    'uvicon-scan': '\ue662',
+    'uvicon-share-square': '\ue60b',
+    'uvicon-map': '\ue61d',
+    'uvicon-map-fill': '\ue64e',
+    'uvicon-tags': '\ue629',
+    'uvicon-tags-fill': '\ue651',
+    'uvicon-bookmark-fill': '\ue63b',
+    'uvicon-bookmark': '\ue60a',
+    'uvicon-eye': '\ue613',
+    'uvicon-eye-fill': '\ue641',
+    'uvicon-mic': '\ue64a',
+    'uvicon-mic-off': '\ue649',
+    'uvicon-calendar': '\ue66e',
+    'uvicon-calendar-fill': '\ue634',
+    'uvicon-trash': '\ue623',
+    'uvicon-trash-fill': '\ue658',
+    'uvicon-play-left': '\ue66d',
+    'uvicon-play-right': '\ue610',
+    'uvicon-minus': '\ue618',
+    'uvicon-plus': '\ue62d',
+    'uvicon-info': '\ue653',
+    'uvicon-info-circle': '\ue7d2',
+    'uvicon-info-circle-fill': '\ue64b',
+    'uvicon-question': '\ue715',
+    'uvicon-error': '\ue6d3',
+    'uvicon-close': '\ue685',
+    'uvicon-checkmark': '\ue6a8',
+    'uvicon-android-circle-fill': '\ue67e',
+    'uvicon-android-fill': '\ue67d',
+    'uvicon-ie': '\ue87b',
+    'uvicon-IE-circle-fill': '\ue889',
+    'uvicon-google': '\ue87a',
+    'uvicon-google-circle-fill': '\ue88a',
+    'uvicon-setting-fill': '\ue872',
+    'uvicon-setting': '\ue61f',
+    'uvicon-minus-square-fill': '\ue855',
+    'uvicon-plus-square-fill': '\ue856',
+    'uvicon-heart': '\ue7df',
+    'uvicon-heart-fill': '\ue851',
+    'uvicon-camera': '\ue7d7',
+    'uvicon-camera-fill': '\ue870',
+    'uvicon-more-circle': '\ue63e',
+    'uvicon-more-circle-fill': '\ue645',
+    'uvicon-chat': '\ue620',
+    'uvicon-chat-fill': '\ue61e',
+    'uvicon-bag-fill': '\ue617',
+    'uvicon-bag': '\ue619',
+    'uvicon-error-circle-fill': '\ue62c',
+    'uvicon-error-circle': '\ue624',
+    'uvicon-close-circle': '\ue63f',
+    'uvicon-close-circle-fill': '\ue637',
+    'uvicon-checkmark-circle': '\ue63d',
+    'uvicon-checkmark-circle-fill': '\ue635',
+    'uvicon-question-circle-fill': '\ue666',
+    'uvicon-question-circle': '\ue625',
+    'uvicon-share': '\ue631',
+    'uvicon-share-fill': '\ue65e',
+    'uvicon-shopping-cart': '\ue621',
+    'uvicon-shopping-cart-fill': '\ue65d',
+    'uvicon-bell': '\ue609',
+    'uvicon-bell-fill': '\ue640',
+    'uvicon-list': '\ue650',
+    'uvicon-list-dot': '\ue616',
+    'uvicon-zhihu': '\ue6ba',
+    'uvicon-zhihu-circle-fill': '\ue709',
+    'uvicon-zhifubao': '\ue6b9',
+    'uvicon-zhifubao-circle-fill': '\ue6b8',
+    'uvicon-weixin-circle-fill': '\ue6b1',
+    'uvicon-weixin-fill': '\ue6b2',
+    'uvicon-twitter-circle-fill': '\ue6ab',
+    'uvicon-twitter': '\ue6aa',
+    'uvicon-taobao-circle-fill': '\ue6a7',
+    'uvicon-taobao': '\ue6a6',
+    'uvicon-weibo-circle-fill': '\ue6a5',
+    'uvicon-weibo': '\ue6a4',
+    'uvicon-qq-fill': '\ue6a1',
+    'uvicon-qq-circle-fill': '\ue6a0',
+    'uvicon-moments-circel-fill': '\ue69a',
+    'uvicon-moments': '\ue69b',
+    'uvicon-qzone': '\ue695',
+    'uvicon-qzone-circle-fill': '\ue696',
+    'uvicon-baidu-circle-fill': '\ue680',
+    'uvicon-baidu': '\ue681',
+    'uvicon-facebook-circle-fill': '\ue68a',
+    'uvicon-facebook': '\ue689',
+    'uvicon-car': '\ue60c',
+    'uvicon-car-fill': '\ue636',
+    'uvicon-warning-fill': '\ue64d',
+    'uvicon-warning': '\ue694',
+    'uvicon-clock-fill': '\ue638',
+    'uvicon-clock': '\ue60f',
+    'uvicon-edit-pen': '\ue612',
+    'uvicon-edit-pen-fill': '\ue66b',
+    'uvicon-email': '\ue611',
+    'uvicon-email-fill': '\ue642',
+    'uvicon-minus-circle': '\ue61b',
+    'uvicon-minus-circle-fill': '\ue652',
+    'uvicon-plus-circle': '\ue62e',
+    'uvicon-plus-circle-fill': '\ue661',
+    'uvicon-file-text': '\ue663',
+    'uvicon-file-text-fill': '\ue665',
+    'uvicon-pushpin': '\ue7e3',
+    'uvicon-pushpin-fill': '\ue86e',
+    'uvicon-grid': '\ue673',
+    'uvicon-grid-fill': '\ue678',
+    'uvicon-play-circle': '\ue647',
+    'uvicon-play-circle-fill': '\ue655',
+    'uvicon-pause-circle-fill': '\ue654',
+    'uvicon-pause': '\ue8fa',
+    'uvicon-pause-circle': '\ue643',
+    'uvicon-eye-off': '\ue648',
+    'uvicon-eye-off-outline': '\ue62b',
+    'uvicon-gift-fill': '\ue65c',
+    'uvicon-gift': '\ue65b',
+    'uvicon-rmb-circle-fill': '\ue657',
+    'uvicon-rmb-circle': '\ue677',
+    'uvicon-kefu-ermai': '\ue656',
+    'uvicon-server-fill': '\ue751',
+    'uvicon-coupon-fill': '\ue8c4',
+    'uvicon-coupon': '\ue8ae',
+    'uvicon-integral': '\ue704',
+    'uvicon-integral-fill': '\ue703',
+    'uvicon-home-fill': '\ue964',
+    'uvicon-home': '\ue965',
+    'uvicon-hourglass-half-fill': '\ue966',
+    'uvicon-hourglass': '\ue967',
+    'uvicon-account': '\ue628',
+    'uvicon-plus-people-fill': '\ue626',
+    'uvicon-minus-people-fill': '\ue615',
+    'uvicon-account-fill': '\ue614',
+    'uvicon-thumb-down-fill': '\ue726',
+    'uvicon-thumb-down': '\ue727',
+    'uvicon-thumb-up': '\ue733',
+    'uvicon-thumb-up-fill': '\ue72f',
+    'uvicon-lock-fill': '\ue979',
+    'uvicon-lock-open': '\ue973',
+    'uvicon-lock-opened-fill': '\ue974',
+    'uvicon-lock': '\ue97a',
+    'uvicon-red-packet-fill': '\ue690',
+    'uvicon-photo-fill': '\ue98b',
+    'uvicon-photo': '\ue98d',
+    'uvicon-volume-off-fill': '\ue659',
+    'uvicon-volume-off': '\ue644',
+    'uvicon-volume-fill': '\ue670',
+    'uvicon-volume': '\ue633',
+    'uvicon-red-packet': '\ue691',
+    'uvicon-download': '\ue63c',
+    'uvicon-arrow-up-fill': '\ue6b0',
+    'uvicon-arrow-down-fill': '\ue600',
+    'uvicon-play-left-fill': '\ue675',
+    'uvicon-play-right-fill': '\ue676',
+    'uvicon-rewind-left-fill': '\ue679',
+    'uvicon-rewind-right-fill': '\ue67a',
+    'uvicon-arrow-downward': '\ue604',
+    'uvicon-arrow-leftward': '\ue601',
+    'uvicon-arrow-rightward': '\ue603',
+    'uvicon-arrow-upward': '\ue607',
+    'uvicon-arrow-down': '\ue60d',
+    'uvicon-arrow-right': '\ue605',
+    'uvicon-arrow-left': '\ue60e',
+    'uvicon-arrow-up': '\ue606',
+    'uvicon-skip-back-left': '\ue674',
+    'uvicon-skip-forward-right': '\ue672',
+    'uvicon-rewind-right': '\ue66f',
+    'uvicon-rewind-left': '\ue671',
+    'uvicon-arrow-right-double': '\ue68d',
+    'uvicon-arrow-left-double': '\ue68c',
+    'uvicon-wifi-off': '\ue668',
+    'uvicon-wifi': '\ue667',
+    'uvicon-empty-data': '\ue62f',
+    'uvicon-empty-history': '\ue684',
+    'uvicon-empty-list': '\ue68b',
+    'uvicon-empty-page': '\ue627',
+    'uvicon-empty-order': '\ue639',
+    'uvicon-man': '\ue697',
+    'uvicon-woman': '\ue69c',
+    'uvicon-man-add': '\ue61c',
+    'uvicon-man-add-fill': '\ue64c',
+    'uvicon-man-delete': '\ue61a',
+    'uvicon-man-delete-fill': '\ue66a',
+    'uvicon-zh': '\ue70a',
+    'uvicon-en': '\ue692'
+}

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

@@ -0,0 +1,90 @@
+export default {
+	props: {
+		// 图标类名
+		name: {
+			type: String,
+			default: ''
+		},
+		// 图标颜色,可接受主题色
+		color: {
+			type: String,
+			default: '#606266'
+		},
+		// 字体大小,单位px
+		size: {
+			type: [String, Number],
+			default: '16px'
+		},
+		// 是否显示粗体
+		bold: {
+			type: Boolean,
+			default: false
+		},
+		// 点击图标的时候传递事件出去的index(用于区分点击了哪一个)
+		index: {
+			type: [String, Number],
+			default: null
+		},
+		// 触摸图标时的类名
+		hoverClass: {
+			type: String,
+			default: ''
+		},
+		// 自定义扩展前缀,方便用户扩展自己的图标库
+		customPrefix: {
+			type: String,
+			default: 'uvicon'
+		},
+		// 图标右边或者下面的文字
+		label: {
+			type: [String, Number],
+			default: ''
+		},
+		// label的位置,只能右边或者下边
+		labelPos: {
+			type: String,
+			default: 'right'
+		},
+		// label的大小
+		labelSize: {
+			type: [String, Number],
+			default: '15px'
+		},
+		// label的颜色
+		labelColor: {
+			type: String,
+			default: '#606266'
+		},
+		// label与图标的距离
+		space: {
+			type: [String, Number],
+			default: '3px'
+		},
+		// 图片的mode
+		imgMode: {
+			type: String,
+			default: ''
+		},
+		// 用于显示图片小图标时,图片的宽度
+		width: {
+			type: [String, Number],
+			default: ''
+		},
+		// 用于显示图片小图标时,图片的高度
+		height: {
+			type: [String, Number],
+			default: ''
+		},
+		// 用于解决某些情况下,让图标垂直居中的用途
+		top: {
+			type: [String, Number],
+			default: 0
+		},
+		// 是否阻止事件传播
+		stop: {
+			type: Boolean,
+			default: false
+		},
+		...uni.$uv?.props?.icon
+	}
+}

+ 663 - 0
uni_modules/uv-icon/components/uv-icon/uniicons.css

@@ -0,0 +1,663 @@
+.uniui-color:before {
+  content: "\e6cf";
+}
+
+.uniui-wallet:before {
+  content: "\e6b1";
+}
+
+.uniui-settings-filled:before {
+  content: "\e6ce";
+}
+
+.uniui-auth-filled:before {
+  content: "\e6cc";
+}
+
+.uniui-shop-filled:before {
+  content: "\e6cd";
+}
+
+.uniui-staff-filled:before {
+  content: "\e6cb";
+}
+
+.uniui-vip-filled:before {
+  content: "\e6c6";
+}
+
+.uniui-plus-filled:before {
+  content: "\e6c7";
+}
+
+.uniui-folder-add-filled:before {
+  content: "\e6c8";
+}
+
+.uniui-color-filled:before {
+  content: "\e6c9";
+}
+
+.uniui-tune-filled:before {
+  content: "\e6ca";
+}
+
+.uniui-calendar-filled:before {
+  content: "\e6c0";
+}
+
+.uniui-notification-filled:before {
+  content: "\e6c1";
+}
+
+.uniui-wallet-filled:before {
+  content: "\e6c2";
+}
+
+.uniui-medal-filled:before {
+  content: "\e6c3";
+}
+
+.uniui-gift-filled:before {
+  content: "\e6c4";
+}
+
+.uniui-fire-filled:before {
+  content: "\e6c5";
+}
+
+.uniui-refreshempty:before {
+  content: "\e6bf";
+}
+
+.uniui-location-filled:before {
+  content: "\e6af";
+}
+
+.uniui-person-filled:before {
+  content: "\e69d";
+}
+
+.uniui-personadd-filled:before {
+  content: "\e698";
+}
+
+.uniui-back:before {
+  content: "\e6b9";
+}
+
+.uniui-forward:before {
+  content: "\e6ba";
+}
+
+.uniui-arrow-right:before {
+  content: "\e6bb";
+}
+
+.uniui-arrowthinright:before {
+  content: "\e6bb";
+}
+
+.uniui-arrow-left:before {
+  content: "\e6bc";
+}
+
+.uniui-arrowthinleft:before {
+  content: "\e6bc";
+}
+
+.uniui-arrow-up:before {
+  content: "\e6bd";
+}
+
+.uniui-arrowthinup:before {
+  content: "\e6bd";
+}
+
+.uniui-arrow-down:before {
+  content: "\e6be";
+}
+
+.uniui-arrowthindown:before {
+  content: "\e6be";
+}
+
+.uniui-bottom:before {
+  content: "\e6b8";
+}
+
+.uniui-arrowdown:before {
+  content: "\e6b8";
+}
+
+.uniui-right:before {
+  content: "\e6b5";
+}
+
+.uniui-arrowright:before {
+  content: "\e6b5";
+}
+
+.uniui-top:before {
+  content: "\e6b6";
+}
+
+.uniui-arrowup:before {
+  content: "\e6b6";
+}
+
+.uniui-left:before {
+  content: "\e6b7";
+}
+
+.uniui-arrowleft:before {
+  content: "\e6b7";
+}
+
+.uniui-eye:before {
+  content: "\e651";
+}
+
+.uniui-eye-filled:before {
+  content: "\e66a";
+}
+
+.uniui-eye-slash:before {
+  content: "\e6b3";
+}
+
+.uniui-eye-slash-filled:before {
+  content: "\e6b4";
+}
+
+.uniui-info-filled:before {
+  content: "\e649";
+}
+
+.uniui-reload:before {
+  content: "\e6b2";
+}
+
+.uniui-micoff-filled:before {
+  content: "\e6b0";
+}
+
+.uniui-map-pin-ellipse:before {
+  content: "\e6ac";
+}
+
+.uniui-map-pin:before {
+  content: "\e6ad";
+}
+
+.uniui-location:before {
+  content: "\e6ae";
+}
+
+.uniui-starhalf:before {
+  content: "\e683";
+}
+
+.uniui-star:before {
+  content: "\e688";
+}
+
+.uniui-star-filled:before {
+  content: "\e68f";
+}
+
+.uniui-calendar:before {
+  content: "\e6a0";
+}
+
+.uniui-fire:before {
+  content: "\e6a1";
+}
+
+.uniui-medal:before {
+  content: "\e6a2";
+}
+
+.uniui-font:before {
+  content: "\e6a3";
+}
+
+.uniui-gift:before {
+  content: "\e6a4";
+}
+
+.uniui-link:before {
+  content: "\e6a5";
+}
+
+.uniui-notification:before {
+  content: "\e6a6";
+}
+
+.uniui-staff:before {
+  content: "\e6a7";
+}
+
+.uniui-vip:before {
+  content: "\e6a8";
+}
+
+.uniui-folder-add:before {
+  content: "\e6a9";
+}
+
+.uniui-tune:before {
+  content: "\e6aa";
+}
+
+.uniui-auth:before {
+  content: "\e6ab";
+}
+
+.uniui-person:before {
+  content: "\e699";
+}
+
+.uniui-email-filled:before {
+  content: "\e69a";
+}
+
+.uniui-phone-filled:before {
+  content: "\e69b";
+}
+
+.uniui-phone:before {
+  content: "\e69c";
+}
+
+.uniui-email:before {
+  content: "\e69e";
+}
+
+.uniui-personadd:before {
+  content: "\e69f";
+}
+
+.uniui-chatboxes-filled:before {
+  content: "\e692";
+}
+
+.uniui-contact:before {
+  content: "\e693";
+}
+
+.uniui-chatbubble-filled:before {
+  content: "\e694";
+}
+
+.uniui-contact-filled:before {
+  content: "\e695";
+}
+
+.uniui-chatboxes:before {
+  content: "\e696";
+}
+
+.uniui-chatbubble:before {
+  content: "\e697";
+}
+
+.uniui-upload-filled:before {
+  content: "\e68e";
+}
+
+.uniui-upload:before {
+  content: "\e690";
+}
+
+.uniui-weixin:before {
+  content: "\e691";
+}
+
+.uniui-compose:before {
+  content: "\e67f";
+}
+
+.uniui-qq:before {
+  content: "\e680";
+}
+
+.uniui-download-filled:before {
+  content: "\e681";
+}
+
+.uniui-pyq:before {
+  content: "\e682";
+}
+
+.uniui-sound:before {
+  content: "\e684";
+}
+
+.uniui-trash-filled:before {
+  content: "\e685";
+}
+
+.uniui-sound-filled:before {
+  content: "\e686";
+}
+
+.uniui-trash:before {
+  content: "\e687";
+}
+
+.uniui-videocam-filled:before {
+  content: "\e689";
+}
+
+.uniui-spinner-cycle:before {
+  content: "\e68a";
+}
+
+.uniui-weibo:before {
+  content: "\e68b";
+}
+
+.uniui-videocam:before {
+  content: "\e68c";
+}
+
+.uniui-download:before {
+  content: "\e68d";
+}
+
+.uniui-help:before {
+  content: "\e679";
+}
+
+.uniui-navigate-filled:before {
+  content: "\e67a";
+}
+
+.uniui-plusempty:before {
+  content: "\e67b";
+}
+
+.uniui-smallcircle:before {
+  content: "\e67c";
+}
+
+.uniui-minus-filled:before {
+  content: "\e67d";
+}
+
+.uniui-micoff:before {
+  content: "\e67e";
+}
+
+.uniui-closeempty:before {
+  content: "\e66c";
+}
+
+.uniui-clear:before {
+  content: "\e66d";
+}
+
+.uniui-navigate:before {
+  content: "\e66e";
+}
+
+.uniui-minus:before {
+  content: "\e66f";
+}
+
+.uniui-image:before {
+  content: "\e670";
+}
+
+.uniui-mic:before {
+  content: "\e671";
+}
+
+.uniui-paperplane:before {
+  content: "\e672";
+}
+
+.uniui-close:before {
+  content: "\e673";
+}
+
+.uniui-help-filled:before {
+  content: "\e674";
+}
+
+.uniui-paperplane-filled:before {
+  content: "\e675";
+}
+
+.uniui-plus:before {
+  content: "\e676";
+}
+
+.uniui-mic-filled:before {
+  content: "\e677";
+}
+
+.uniui-image-filled:before {
+  content: "\e678";
+}
+
+.uniui-locked-filled:before {
+  content: "\e668";
+}
+
+.uniui-info:before {
+  content: "\e669";
+}
+
+.uniui-locked:before {
+  content: "\e66b";
+}
+
+.uniui-camera-filled:before {
+  content: "\e658";
+}
+
+.uniui-chat-filled:before {
+  content: "\e659";
+}
+
+.uniui-camera:before {
+  content: "\e65a";
+}
+
+.uniui-circle:before {
+  content: "\e65b";
+}
+
+.uniui-checkmarkempty:before {
+  content: "\e65c";
+}
+
+.uniui-chat:before {
+  content: "\e65d";
+}
+
+.uniui-circle-filled:before {
+  content: "\e65e";
+}
+
+.uniui-flag:before {
+  content: "\e65f";
+}
+
+.uniui-flag-filled:before {
+  content: "\e660";
+}
+
+.uniui-gear-filled:before {
+  content: "\e661";
+}
+
+.uniui-home:before {
+  content: "\e662";
+}
+
+.uniui-home-filled:before {
+  content: "\e663";
+}
+
+.uniui-gear:before {
+  content: "\e664";
+}
+
+.uniui-smallcircle-filled:before {
+  content: "\e665";
+}
+
+.uniui-map-filled:before {
+  content: "\e666";
+}
+
+.uniui-map:before {
+  content: "\e667";
+}
+
+.uniui-refresh-filled:before {
+  content: "\e656";
+}
+
+.uniui-refresh:before {
+  content: "\e657";
+}
+
+.uniui-cloud-upload:before {
+  content: "\e645";
+}
+
+.uniui-cloud-download-filled:before {
+  content: "\e646";
+}
+
+.uniui-cloud-download:before {
+  content: "\e647";
+}
+
+.uniui-cloud-upload-filled:before {
+  content: "\e648";
+}
+
+.uniui-redo:before {
+  content: "\e64a";
+}
+
+.uniui-images-filled:before {
+  content: "\e64b";
+}
+
+.uniui-undo-filled:before {
+  content: "\e64c";
+}
+
+.uniui-more:before {
+  content: "\e64d";
+}
+
+.uniui-more-filled:before {
+  content: "\e64e";
+}
+
+.uniui-undo:before {
+  content: "\e64f";
+}
+
+.uniui-images:before {
+  content: "\e650";
+}
+
+.uniui-paperclip:before {
+  content: "\e652";
+}
+
+.uniui-settings:before {
+  content: "\e653";
+}
+
+.uniui-search:before {
+  content: "\e654";
+}
+
+.uniui-redo-filled:before {
+  content: "\e655";
+}
+
+.uniui-list:before {
+  content: "\e644";
+}
+
+.uniui-mail-open-filled:before {
+  content: "\e63a";
+}
+
+.uniui-hand-down-filled:before {
+  content: "\e63c";
+}
+
+.uniui-hand-down:before {
+  content: "\e63d";
+}
+
+.uniui-hand-up-filled:before {
+  content: "\e63e";
+}
+
+.uniui-hand-up:before {
+  content: "\e63f";
+}
+
+.uniui-heart-filled:before {
+  content: "\e641";
+}
+
+.uniui-mail-open:before {
+  content: "\e643";
+}
+
+.uniui-heart:before {
+  content: "\e639";
+}
+
+.uniui-loop:before {
+  content: "\e633";
+}
+
+.uniui-pulldown:before {
+  content: "\e632";
+}
+
+.uniui-scan:before {
+  content: "\e62a";
+}
+
+.uniui-bars:before {
+  content: "\e627";
+}
+
+.uniui-cart-filled:before {
+  content: "\e629";
+}
+
+.uniui-checkbox:before {
+  content: "\e62b";
+}
+
+.uniui-checkbox-filled:before {
+  content: "\e62c";
+}
+
+.uniui-shop:before {
+  content: "\e62f";
+}
+
+.uniui-headphones:before {
+  content: "\e630";
+}
+
+.uniui-cart:before {
+  content: "\e631";
+}

File diff suppressed because it is too large
+ 46 - 0
uni_modules/uv-icon/components/uv-icon/uv-icon.vue


+ 83 - 0
uni_modules/uv-icon/package.json

@@ -0,0 +1,83 @@
+{
+  "id": "uv-icon",
+  "displayName": "uv-icon 图标  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.3",
+  "description": "基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。可自定义颜色、大小。",
+  "keywords": [
+    "uv-ui,uvui,uv-icon,icon,图标,字体图标"
+],
+  "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-icon/readme.md

@@ -0,0 +1,11 @@
+## uv-icon 图标库
+
+> **组件名:uv-icon**
+
+基于字体的图标集,包含了大多数常见场景的图标,支持自定义,支持自定义图片图标等。
+
+### <a href="https://www.uvui.cn/components/icon.html" target="_blank">查看文档</a>
+
+### [完整示例项目下载 | 关注更多组件](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>

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

@@ -0,0 +1,5 @@
+## 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
+	}
+}

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

@@ -0,0 +1,347 @@
+<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 模式有效(默认color['uv-tips-color'])
+	 * @property {String}			textColor		提示文本的颜色(默认color['uv-tips-color'])
+	 * @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'
+				}
+				// return this.mode === 'circle' ? this.inactiveColor ? this.inactiveColor : lightColor : '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.1",
+  "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>

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

@@ -0,0 +1,5 @@
+## 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
+	}
+}

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

@@ -0,0 +1,70 @@
+<template>
+	<uv-transition
+	    :show="show"
+	    custom-class="uv-overlay"
+	    :duration="duration"
+	    :custom-style="overlayStyle"
+	    @click="clickHandler"
+	>
+		<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],
+		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')
+			}
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+     $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;
+	}
+</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.1",
+  "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>

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

@@ -0,0 +1,7 @@
+## 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 选择器

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

@@ -0,0 +1,80 @@
+export default {
+	props: {
+		// 是否展示picker弹窗
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 是否展示顶部的操作栏
+		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'
+		},
+		// 每列中可见选项的数量
+		visibleItemCount: {
+			type: [String, Number],
+			default: 5
+		},
+		// 选项对象中,需要展示的属性键名
+		keyName: {
+			type: String,
+			default: 'text'
+		},
+		// 是否允许点击遮罩关闭选择器
+		closeOnClickOverlay: {
+			type: Boolean,
+			default: false
+		},
+		// 各列的默认索引
+		defaultIndex: {
+			type: Array,
+			default: () => [],
+		},
+		// 是否在手指松开时立即触发 change 事件。若不开启则会在滚动动画结束后触发 change 事件,只在微信2.21.1及以上有效
+		immediateChange: {
+			type: Boolean,
+			default: false
+		},
+		...uni.$uv?.props?.picker
+	}
+}

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

@@ -0,0 +1,288 @@
+<template>
+	<uv-popup
+		:show="show"
+		@close="closeHandler"
+	>
+		<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'
+						}"
+					>{{ 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}			show				是否显示picker弹窗(默认 false )
+ * @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 | 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],
+	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: {
+		// 获取item需要显示的文字,判别为对象还是文本
+		getItemText(item) {
+			if (this.$uv.test.object(item)) {
+				return item[this.keyName]
+			} else {
+				return item
+			}
+		},
+		// 关闭选择器
+		closeHandler() {
+			if (this.closeOnClickOverlay) {
+				this.$emit('close')
+			}
+		},
+		// 点击工具栏的取消按钮
+		cancel() {
+			this.$emit('cancel')
+		},
+		// 点击工具栏的确定按钮
+		confirm() {
+			this.$emit('confirm', {
+				indexs: this.innerIndex,
+				value: this.innerColumns.map((item, index) => item[this.innerIndex[index]]),
+				values: this.innerColumns
+			})
+		},
+		// 选择器某一列的数据发生变化时触发
+		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', {
+				// #ifndef MP-WEIXIN || MP-LARK
+				// 微信小程序不能传递this,会因为循环引用而报错
+				picker: this,
+				// #endif
+				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.2",
+  "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>

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

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

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

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

@@ -0,0 +1,307 @@
+<template>
+	<view class="uv-popup">
+		<uv-overlay
+			:show="show"
+			@click="overlayClick"
+			v-if="overlay"
+			:duration="overlayDuration"
+			:customStyle="overlayStyle"
+			:opacity="overlayOpacity"
+		></uv-overlay>
+		<uv-transition
+			:show="show"
+			:customStyle="transitionStyle"
+			:mode="position"
+			:duration="duration"
+			@afterEnter="afterEnter"
+			@click="clickHandler"
+		>
+			<view
+				class="uv-popup__content"
+				:style="[contentStyle]"
+				@tap.stop="noop"
+			>
+				<uv-status-bar v-if="safeAreaInsetTop"></uv-status-bar>
+				<slot></slot>
+				<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>
+				<uv-safe-bottom v-if="safeAreaInsetBottom"></uv-safe-bottom>
+			</view>
+		</uv-transition>
+	</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';
+
+	/**
+	 * popup 弹窗
+	 * @description 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义
+	 * @tutorial https://www.uvui.cn/components/popup.html
+	 * @property {Boolean}			show				是否展示弹窗 (默认 false )
+	 * @property {Boolean}			overlay				是否显示遮罩 (默认 true )
+	 * @property {String}			mode				弹出方向(默认 'bottom' )
+	 * @property {String | Number}	duration			动画时长,单位ms (默认 300 )
+	 * @property {String | Number}	overlayDuration			遮罩层动画时长,单位ms (默认 350 )
+	 * @property {Boolean}			closeable			是否显示关闭图标(默认 false )
+	 * @property {Object | String}	overlayStyle		自定义遮罩的样式
+	 * @property {String | Number}	overlayOpacity		遮罩透明度,0-1之间(默认 0.5)
+	 * @property {Boolean}			closeOnClickOverlay	点击遮罩是否关闭弹窗 (默认  true )
+	 * @property {String | Number}	zIndex				层级 (默认 10075 )
+	 * @property {Boolean}			safeAreaInsetBottom	是否为iPhoneX留出底部安全距离 (默认 true )
+	 * @property {Boolean}			safeAreaInsetTop	是否留出顶部安全距离(状态栏高度) (默认 false )
+	 * @property {String}			closeIconPos		自定义关闭图标位置(默认 'top-right' )
+	 * @property {String | Number}	round				圆角值(默认 0)
+	 * @property {Boolean}			zoom				当mode=center时 是否开启缩放(默认 true )
+	 * @property {String}			bgColor				弹窗背景色,设置为transparent可去除白色背景
+	 * @property {Object}			customStyle			组件的样式,对象形式
+	 * @event {Function} open 弹出层打开
+	 * @event {Function} close 弹出层收起
+	 * @example <uv-popup v-model="show"><text>出淤泥而不染,濯清涟而不妖</text></uv-popup>
+	 */
+	export default {
+		name: 'uv-popup',
+		emits:['click','close','open'],
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+				overlayDuration: this.duration + 50
+			}
+		},
+		watch: {
+			show(newValue, oldValue) {
+				if (newValue === true) {
+					// #ifdef MP-WEIXIN
+					const children = this.$children
+					this.retryComputedComponentRect(children)
+					// #endif
+				}
+			}
+		},
+		computed: {
+			transitionStyle() {
+				const style = {
+					zIndex: this.zIndex,
+					position: 'fixed',
+					display: 'flex',
+				}
+				style[this.mode] = 0
+				if (this.mode === 'left') {
+					return this.$uv.deepMerge(style, {
+						bottom: 0,
+						top: 0,
+					})
+				} else if (this.mode === 'right') {
+					return this.$uv.deepMerge(style, {
+						bottom: 0,
+						top: 0,
+					})
+				} else if (this.mode === 'top') {
+					return this.$uv.deepMerge(style, {
+						left: 0,
+						right: 0
+					})
+				} else if (this.mode === 'bottom') {
+					return this.$uv.deepMerge(style, {
+						left: 0,
+						right: 0,
+					})
+				} else if (this.mode === 'center') {
+					return this.$uv.deepMerge(style, {
+						alignItems: 'center',
+						'justify-content': 'center',
+						top: 0,
+						left: 0,
+						right: 0,
+						bottom: 0
+					})
+				}
+			},
+			contentStyle() {
+				const style = {}
+				// 通过设备信息的safeAreaInsets值来判断是否需要预留顶部状态栏和底部安全局的位置
+				// 不使用css方案,是因为nvue不支持css的iPhoneX安全区查询属性
+				const {
+					safeAreaInsets
+				} = this.$uv.sys()
+				if (this.mode !== 'center') {
+					style.flex = 1
+				}
+				// 背景色,一般用于设置为transparent,去除默认的白色背景
+				if (this.bgColor) {
+					style.backgroundColor = this.bgColor
+				}
+				if(this.round) {
+					const value = this.$uv.addUnit(this.round)
+					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))
+			},
+			position() {
+				if (this.mode === 'center') {
+					return this.zoom ? 'fade-zoom' : 'fade'
+				}
+				if (this.mode === 'left') {
+					return 'slide-left'
+				}
+				if (this.mode === 'right') {
+					return 'slide-right'
+				}
+				if (this.mode === 'bottom') {
+					return 'slide-up'
+				}
+				if (this.mode === 'top') {
+					return 'slide-down'
+				}
+			},
+		},
+		methods: {
+			// 点击遮罩
+			overlayClick() {
+				if (this.closeOnClickOverlay) {
+					this.$emit('close')
+				}
+			},
+			close(e) {
+				this.$emit('close')
+			},
+			afterEnter() {
+				this.$emit('open')
+			},
+			clickHandler() {
+				// 由于中部弹出时,其uv-transition占据了整个页面相当于遮罩,此时需要发出遮罩点击事件,是否无法通过点击遮罩关闭弹窗
+				if(this.mode === 'center') {
+					this.overlayClick()
+				}
+				this.$emit('click')
+			},
+			// #ifdef MP-WEIXIN
+			retryComputedComponentRect(children) {
+				// 组件内部需要计算节点的组件
+				const names = ['uv-calendar-month', 'uv-album', 'uv-collapse-item', 'uv-dropdown', 'uv-index-item', 'uv-index-list',
+					'uv-line-progress', 'uv-list-item', 'uv-rate', 'uv-read-more', 'uv-row', 'uv-row-notice', 'uv-scroll-list',
+					'uv-skeleton', 'uv-slider', 'uv-steps-item', 'uv-sticky', 'uv-subsection', 'uv-swipe-action-item', 'uv-tabbar',
+					'uv-tabs', 'uv-tooltip'
+				]
+				// 历遍所有的子组件节点
+				for (let i = 0; i < children.length; i++) {
+					const child = children[i]
+					// 拿到子组件的子组件
+					const grandChild = child.$children
+					// 判断如果在需要重新初始化的组件数组中名中,并且存在init方法的话,则执行
+					if (names.includes(child.$options.name) && typeof child?.init === 'function') {
+						// 需要进行一定的延时,因为初始化页面需要时间
+						this.$uv.sleep(50).then(() => {
+							child.init()
+						})
+					}
+					// 如果子组件还有孙组件,进行递归历遍
+					if (grandChild.length) {
+						this.retryComputedComponentRect(grandChild)
+					}
+				}
+			}
+			// #endif
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	$uv-popup-flex:1 !default;
+	$uv-popup-content-background-color: #fff !default;
+
+	.uv-popup {
+		flex: $uv-popup-flex;
+
+		&__content {
+			background-color: $uv-popup-content-background-color;
+			position: relative;
+
+			&--round-top {
+				border-top-left-radius: 0;
+				border-top-right-radius: 0;
+				border-bottom-left-radius: 10px;
+				border-bottom-right-radius: 10px;
+			}
+
+			&--round-left {
+				border-top-left-radius: 0;
+				border-top-right-radius: 10px;
+				border-bottom-left-radius: 0;
+				border-bottom-right-radius: 10px;
+			}
+
+			&--round-right {
+				border-top-left-radius: 10px;
+				border-top-right-radius: 0;
+				border-bottom-left-radius: 10px;
+				border-bottom-right-radius: 0;
+			}
+
+			&--round-bottom {
+				border-top-left-radius: 10px;
+				border-top-right-radius: 10px;
+				border-bottom-left-radius: 0;
+				border-bottom-right-radius: 0;
+			}
+
+			&--round-center {
+				border-top-left-radius: 10px;
+				border-top-right-radius: 10px;
+				border-bottom-left-radius: 10px;
+				border-bottom-right-radius: 10px;
+			}
+
+			&__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;
+			}
+		}
+	}
+</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.1",
+  "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>

+ 5 - 0
uni_modules/uv-safe-bottom/changelog.md

@@ -0,0 +1,5 @@
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+uv-safe-bottom 底部安全区组件

+ 67 - 0
uni_modules/uv-safe-bottom/components/uv-safe-bottom/uv-safe-bottom.vue

@@ -0,0 +1,67 @@
+<template>
+	<view
+		class="uv-safe-bottom"
+		:style="[style]"
+		:class="[!isNvue && 'uv-safe-area-inset-bottom']"
+	>
+	</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'
+	/**
+	 * SafeBottom 底部安全区
+	 * @description 这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
+	 * @tutorial https://www.uvui.cn/components/safeAreaInset.html
+	 * @property {type}		prop_name
+	 * @property {Object}	customStyle	定义需要用到的外部样式
+	 *
+	 * @event {Function()}
+	 * @example <uv-status-bar></uv-status-bar>
+	 */
+	export default {
+		name: "uv-safe-bottom",
+		mixins: [mpMixin, mixin],
+		data() {
+			return {
+				safeAreaBottomHeight: 0,
+				isNvue: false,
+			};
+		},
+		computed: {
+			style() {
+				const style = {};
+				// #ifdef APP-NVUE
+				// nvue下,高度使用js计算填充
+				style.height = this.$uv.addUnit(this.$uv.sys().safeAreaInsets.bottom, 'px');
+				// #endif
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle));
+			},
+		},
+		mounted() {
+			// #ifdef APP-NVUE
+			// 标识为是否nvue
+			this.isNvue = true;
+			// #endif
+		},
+	};
+</script>
+
+<style lang="scss" scoped>
+	.uv-safe-bottom {
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		/* #endif */
+	}
+	/* #ifndef APP-NVUE */
+	// 历遍生成4个方向的底部安全区
+	@each $d in top, right, bottom, left {
+		.uv-safe-area-inset-#{$d} {
+			padding-#{$d}: 0;
+			padding-#{$d}: constant(safe-area-inset-#{$d});  
+			padding-#{$d}: env(safe-area-inset-#{$d});  
+		}
+	}
+	/* #endif */
+</style>

+ 87 - 0
uni_modules/uv-safe-bottom/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-safe-bottom",
+  "displayName": "uv-safe-bottom 底部安全区  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.1",
+  "description": "这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。",
+  "keywords": [
+    "uv-safe-bottom",
+    "uvui",
+    "uv-ui",
+    "bottom",
+    "底部安全区"
+],
+  "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-safe-bottom/readme.md

@@ -0,0 +1,11 @@
+## SafeBottom 底部安全区 
+
+> **组件名:uv-safe-bottom**
+
+这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
+
+### <a href="https://www.uvui.cn/guide/safeAreaInset.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>

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

@@ -0,0 +1,5 @@
+## 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'
+        }
+    }
+}

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

@@ -0,0 +1,48 @@
+<template>
+	<view
+	    :style="[style]"
+	    class="uv-status-bar"
+	>
+		<slot />
+	</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';
+	/**
+	 * StatbusBar 状态栏占位
+	 * @description 本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
+	 * @tutorial https://www.uvui.cn/components/statusBar.html
+	 * @property {String}			bgColor			背景色 (默认 'transparent' )
+	 * @property {String | Object}	customStyle		自定义样式 
+	 * @example <uv-status-bar></uv-status-bar>
+	 */
+	export default {
+		name: 'uv-status-bar',
+		mixins: [mpMixin, mixin, props],
+		data() {
+			return {
+			}
+		},
+		computed: {
+			style() {
+				const style = {}
+				// 状态栏高度,由于某些安卓和微信开发工具无法识别css的顶部状态栏变量,所以使用js获取的方式
+				style.height = this.$uv.addUnit(this.$uv.sys().statusBarHeight, 'px')
+				style.backgroundColor = this.bgColor
+				return this.$uv.deepMerge(style, this.$uv.addStyle(this.customStyle))
+			}
+		},
+	}
+</script>
+
+<style lang="scss" scoped>
+	.uv-status-bar {
+		// nvue会默认100%,如果nvue下,显式写100%的话,会导致宽度不为100%而异常
+		/* #ifndef APP-NVUE */
+		width: 100%;
+		/* #endif */
+	}
+</style>

+ 87 - 0
uni_modules/uv-status-bar/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-status-bar",
+  "displayName": "uv-status-bar 状态栏占位",
+  "version": "1.0.1",
+  "description": "状态栏占位组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。",
+  "keywords": [
+    "uv-status-bar",
+    "uvui",
+    "uv-ui",
+    "status-bar",
+    "状态栏"
+],
+  "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"
+				}
+			}
+		}
+  }
+}

+ 10 - 0
uni_modules/uv-status-bar/readme.md

@@ -0,0 +1,10 @@
+## StatbusBar 状态栏占位
+
+> **组件名:uv-status-bar**
+
+本组件主要用于状态填充,比如在自定导航栏的时候,它会自动适配一个恰当的状态栏高度。
+
+### [完整示例项目下载 | 关注更多组件](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-transition/changelog.md

@@ -0,0 +1,7 @@
+## 1.0.2(2023-05-23)
+1. 百度小程序等平台不支持this.$nextick,修改成延时
+## 1.0.1(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.0(2023-05-10)
+1. 新增动画组件

+ 68 - 0
uni_modules/uv-transition/components/uv-transition/nvue.ani-map.js

@@ -0,0 +1,68 @@
+export default {
+    fade: {
+        enter: { opacity: 0 },
+        'enter-to': { opacity: 1 },
+        leave: { opacity: 1 },
+        'leave-to': { opacity: 0 }
+    },
+    'fade-up': {
+        enter: { opacity: 0, transform: 'translateY(100%)' },
+        'enter-to': { opacity: 1, transform: 'translateY(0)' },
+        leave: { opacity: 1, transform: 'translateY(0)' },
+        'leave-to': { opacity: 0, transform: 'translateY(100%)' }
+    },
+    'fade-down': {
+        enter: { opacity: 0, transform: 'translateY(-100%)' },
+        'enter-to': { opacity: 1, transform: 'translateY(0)' },
+        leave: { opacity: 1, transform: 'translateY(0)' },
+        'leave-to': { opacity: 0, transform: 'translateY(-100%)' }
+    },
+    'fade-left': {
+        enter: { opacity: 0, transform: 'translateX(-100%)' },
+        'enter-to': { opacity: 1, transform: 'translateY(0)' },
+        leave: { opacity: 1, transform: 'translateY(0)' },
+        'leave-to': { opacity: 0, transform: 'translateX(-100%)' }
+    },
+    'fade-right': {
+        enter: { opacity: 0, transform: 'translateX(100%)' },
+        'enter-to': { opacity: 1, transform: 'translateY(0)' },
+        leave: { opacity: 1, transform: 'translateY(0)' },
+        'leave-to': { opacity: 0, transform: 'translateX(100%)' }
+    },
+    'slide-up': {
+        enter: { transform: 'translateY(100%)' },
+        'enter-to': { transform: 'translateY(0)' },
+        leave: { transform: 'translateY(0)' },
+        'leave-to': { transform: 'translateY(100%)' }
+    },
+    'slide-down': {
+        enter: { transform: 'translateY(-100%)' },
+        'enter-to': { transform: 'translateY(0)' },
+        leave: { transform: 'translateY(0)' },
+        'leave-to': { transform: 'translateY(-100%)' }
+    },
+    'slide-left': {
+        enter: { transform: 'translateX(-100%)' },
+        'enter-to': { transform: 'translateY(0)' },
+        leave: { transform: 'translateY(0)' },
+        'leave-to': { transform: 'translateX(-100%)' }
+    },
+    'slide-right': {
+        enter: { transform: 'translateX(100%)' },
+        'enter-to': { transform: 'translateY(0)' },
+        leave: { transform: 'translateY(0)' },
+        'leave-to': { transform: 'translateX(100%)' }
+    },
+    zoom: {
+        enter: { transform: 'scale(0.95)' },
+        'enter-to': { transform: 'scale(1)' },
+        leave: { transform: 'scale(1)' },
+        'leave-to': { transform: 'scale(0.95)' }
+    },
+    'fade-zoom': {
+        enter: { opacity: 0, transform: 'scale(0.95)' },
+        'enter-to': { opacity: 1, transform: 'scale(1)' },
+        leave: { opacity: 1, transform: 'scale(1)' },
+        'leave-to': { opacity: 0, transform: 'scale(0.95)' }
+    }
+}

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

@@ -0,0 +1,25 @@
+export default {
+	props: {
+		// 是否展示组件
+		show: {
+			type: Boolean,
+			default: false
+		},
+		// 使用的动画模式
+		mode: {
+			type: [String, null],
+			default: 'fade'
+		},
+		// 动画的执行时间,单位ms
+		duration: {
+			type: [String, Number],
+			default: 300
+		},
+		// 使用的动画过渡函数
+		timingFunction: {
+			type: String,
+			default: 'ease-out'
+		},
+		...uni.$uv?.props?.transition
+	}
+}

+ 149 - 0
uni_modules/uv-transition/components/uv-transition/transition.js

@@ -0,0 +1,149 @@
+// 定义一个一定时间后自动成功的promise,让调用nextTick方法处,进入下一个then方法
+const nextTick = () => new Promise(resolve => setTimeout(resolve, 1000 / 50))
+// #ifndef APP-NVUE
+// 定义类名,通过给元素动态切换类名,赋予元素一定的css动画样式
+const getClassNames = (name) => ({
+	enter: `uv-${name}-enter uv-${name}-enter-active`,
+	'enter-to': `uv-${name}-enter-to uv-${name}-enter-active`,
+	leave: `uv-${name}-leave uv-${name}-leave-active`,
+	'leave-to': `uv-${name}-leave-to uv-${name}-leave-active`
+})
+// #endif
+// #ifdef APP-NVUE
+// nvue动画模块实现细节抽离在外部文件
+import animationMap from './nvue.ani-map.js'
+// 引入nvue(weex)的animation动画模块,文档见:
+// https://weex.apache.org/zh/docs/modules/animation.html#transition
+const animation = uni.requireNativePlugin('animation')
+const getStyle = (name) => animationMap[name]
+// #endif
+import { sleep } from '@/uni_modules/uv-ui-tools/libs/function/index.js';
+export default {
+	emits: ['click', 'beforeEnter', 'enter', 'afterEnter', 'beforeLeave', 'leave', 'afterLeave'],
+	methods: {
+		// 组件被点击发出事件
+		clickHandler() {
+			this.$emit('click')
+		},
+		// #ifndef APP-NVUE
+		// vue版本的组件进场处理
+		async vueEnter() {
+			// 动画进入时的类名
+			const classNames = getClassNames(this.mode)
+			// 定义状态和发出动画进入前事件
+			this.status = 'enter'
+			this.$emit('beforeEnter')
+			this.inited = true
+			this.display = true
+			this.classes = classNames.enter
+			// this.$nextTick(async () => {
+			await sleep(20)
+			// 标识动画尚未结束
+			this.$emit('enter')
+			this.transitionEnded = false
+			// 组件动画进入后触发的事件
+			this.$emit('afterEnter')
+			// 赋予组件enter-to类名
+			this.classes = classNames['enter-to']
+			// })
+		},
+		// 动画离场处理
+		async vueLeave() {
+			// 如果不是展示状态,无需执行逻辑
+			if (!this.display) return
+			const classNames = getClassNames(this.mode)
+			// 标记离开状态和发出事件
+			this.status = 'leave'
+			this.$emit('beforeLeave')
+			// 获得类名
+			this.classes = classNames.leave
+			await sleep(10)
+			// this.$nextTick(() => {
+			// 动画正在离场的状态
+			this.transitionEnded = false
+			this.$emit('leave')
+			// 组件执行动画,到了执行的执行时间后,执行一些额外处理
+			setTimeout(this.onTransitionEnd, this.duration)
+			this.classes = classNames['leave-to']
+			// })
+		},
+		// #endif
+		// #ifdef APP-NVUE
+		// nvue版本动画进场
+		nvueEnter() {
+			// 获得样式的名称
+			const currentStyle = getStyle(this.mode)
+			// 组件动画状态和发出事件
+			this.status = 'enter'
+			this.$emit('beforeEnter')
+			// 展示生成组件元素
+			this.inited = true
+			this.display = true
+			// 在nvue安卓上,由于渲染速度慢,在弹窗,键盘,日历等组件中,渲染其中的内容需要时间
+			// 导致出现弹窗卡顿,这里让其一开始为透明状态,等一定时间渲染完成后,再让其隐藏起来,再让其按正常逻辑出现
+			this.viewStyle = {
+				opacity: 0
+			}
+			// 等待弹窗内容渲染完成
+			this.$nextTick(() => {
+				// 合并样式
+				this.viewStyle = currentStyle.enter
+				Promise.resolve().then(nextTick).then(() => {
+					// 组件开始进入前的事件
+					this.$emit('enter')
+					// nvue的transition动画模块需要通过ref调用组件,注意此处的ref不同于vue的this.$refs['uv-transition']用法
+					animation.transition(this.$refs['uv-transition'].ref, {
+						styles: currentStyle['enter-to'],
+						duration: this.duration,
+						timingFunction: this.timingFunction,
+						needLayout: false,
+						delay: 0
+					}, () => {
+						// 动画执行完毕,发出事件
+						this.$emit('afterEnter')
+					})
+				}).catch(() => {})
+			})
+		},
+		nvueLeave() {
+			if (!this.display) {
+				return
+			}
+			const currentStyle = getStyle(this.mode)
+			// 定义状态和事件
+			this.status = 'leave'
+			this.$emit('beforeLeave')
+			// 合并样式
+			this.viewStyle = currentStyle.leave
+			// 放到promise中处理执行过程
+			Promise.resolve().then(nextTick) // 等待几十ms
+				.then(() => {
+					this.transitionEnded = false
+					// 动画正在离场的状态
+					this.$emit('leave')
+					animation.transition(this.$refs['uv-transition'].ref, {
+						styles: currentStyle['leave-to'],
+						duration: this.duration,
+						timingFunction: this.timingFunction,
+						needLayout: false,
+						delay: 0
+					}, () => {
+						this.onTransitionEnd()
+					})
+				}).catch(() => {})
+		},
+		// #endif
+		// 完成过渡后触发
+		onTransitionEnd() {
+			// 如果已经是结束的状态,无需再处理
+			if (this.transitionEnded) return
+			this.transitionEnded = true
+			// 发出组件动画执行后的事件
+			this.$emit(this.status === 'leave' ? 'afterLeave' : 'afterEnter')
+			if (!this.show && this.display) {
+				this.display = false
+				this.inited = false
+			}
+		}
+	}
+}

+ 90 - 0
uni_modules/uv-transition/components/uv-transition/uv-transition.vue

@@ -0,0 +1,90 @@
+<template>
+	<view
+		v-if="inited"
+		class="uv-transition"
+		ref="uv-transition"
+		@tap="clickHandler"
+		:class="classes"
+		:style="[mergeStyle]"
+		@touchmove="noop"
+	>
+		<slot />
+	</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';
+// 组件的methods方法,由于内容较长,写在外部文件中通过mixin引入
+import transition from "./transition.js";
+/**
+ * transition  动画组件
+ * @description
+ * @tutorial
+ * @property {String}			show			是否展示组件 (默认 false )
+ * @property {String}			mode			使用的动画模式 (默认 'fade' )
+ * @property {String | Number}	duration		动画的执行时间,单位ms (默认 '300' )
+ * @property {String}			timingFunction	使用的动画过渡函数 (默认 'ease-out' )
+ * @property {Object}			customStyle		自定义样式
+ * @event {Function} before-enter	进入前触发
+ * @event {Function} enter			进入中触发
+ * @event {Function} after-enter	进入后触发
+ * @event {Function} before-leave	离开前触发
+ * @event {Function} leave			离开中触发
+ * @event {Function} after-leave	离开后触发
+ * @example
+ */
+export default {
+	name: 'uv-transition',
+	data() {
+		return {
+			inited: false, // 是否显示/隐藏组件
+			viewStyle: {}, // 组件内部的样式
+			status: '', // 记录组件动画的状态
+			transitionEnded: false, // 组件是否结束的标记
+			display: false, // 组件是否展示
+			classes: '', // 应用的类名
+		}
+	},
+	computed: {
+	    mergeStyle() {
+	        const { viewStyle, customStyle } = this
+	        return {
+	            // #ifndef APP-NVUE
+	            transitionDuration: `${this.duration}ms`,
+	            // display: `${this.display ? '' : 'none'}`,
+				transitionTimingFunction: this.timingFunction,
+	            // #endif
+				// 避免自定义样式影响到动画属性,所以写在viewStyle前面
+	            ...this.$uv.addStyle(customStyle),
+	            ...viewStyle
+	        }
+	    }
+	},
+	mixins: [mpMixin, mixin, transition, props],
+	watch: {
+		show: {
+			handler(newVal) {
+				// vue和nvue分别执行不同的方法
+				// #ifdef APP-NVUE
+				newVal ? this.nvueEnter() : this.nvueLeave()
+				// #endif
+				// #ifndef APP-NVUE
+				newVal ? this.vueEnter() : this.vueLeave()
+				// #endif
+			},
+			// 表示同时监听初始化时的props的show的意思
+			immediate: true
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+@import '@/uni_modules/uv-ui-tools/libs/css/components.scss';
+/* #ifndef APP-NVUE */
+// vue版本动画相关的样式抽离在外部文件
+@import './vue.ani-style.scss';
+/* #endif */
+</style>

+ 113 - 0
uni_modules/uv-transition/components/uv-transition/vue.ani-style.scss

@@ -0,0 +1,113 @@
+/**
+ * vue版本动画内置的动画模式有如下:
+ * fade:淡入
+ * zoom:缩放
+ * fade-zoom:缩放淡入
+ * fade-up:上滑淡入
+ * fade-down:下滑淡入
+ * fade-left:左滑淡入
+ * fade-right:右滑淡入
+ * slide-up:上滑进入
+ * slide-down:下滑进入
+ * slide-left:左滑进入
+ * slide-right:右滑进入
+ */
+
+$uv-zoom-scale: scale(0.95);
+
+.uv-fade-enter-active,
+.uv-fade-leave-active {
+	transition-property: opacity;
+}
+
+.uv-fade-enter,
+.uv-fade-leave-to {
+	opacity: 0
+}
+
+.uv-fade-zoom-enter,
+.uv-fade-zoom-leave-to {
+	transform: $uv-zoom-scale;
+	opacity: 0;
+}
+
+.uv-fade-zoom-enter-active,
+.uv-fade-zoom-leave-active {
+	transition-property: transform, opacity;
+}
+
+.uv-fade-down-enter-active,
+.uv-fade-down-leave-active,
+.uv-fade-left-enter-active,
+.uv-fade-left-leave-active,
+.uv-fade-right-enter-active,
+.uv-fade-right-leave-active,
+.uv-fade-up-enter-active,
+.uv-fade-up-leave-active {
+	transition-property: opacity, transform;
+}
+
+.uv-fade-up-enter,
+.uv-fade-up-leave-to {
+	transform: translate3d(0, 100%, 0);
+	opacity: 0
+}
+
+.uv-fade-down-enter,
+.uv-fade-down-leave-to {
+	transform: translate3d(0, -100%, 0);
+	opacity: 0
+}
+
+.uv-fade-left-enter,
+.uv-fade-left-leave-to {
+	transform: translate3d(-100%, 0, 0);
+	opacity: 0
+}
+
+.uv-fade-right-enter,
+.uv-fade-right-leave-to {
+	transform: translate3d(100%, 0, 0);
+	opacity: 0
+}
+
+.uv-slide-down-enter-active,
+.uv-slide-down-leave-active,
+.uv-slide-left-enter-active,
+.uv-slide-left-leave-active,
+.uv-slide-right-enter-active,
+.uv-slide-right-leave-active,
+.uv-slide-up-enter-active,
+.uv-slide-up-leave-active {
+	transition-property: transform;
+}
+
+.uv-slide-up-enter,
+.uv-slide-up-leave-to {
+	transform: translate3d(0, 100%, 0)
+}
+
+.uv-slide-down-enter,
+.uv-slide-down-leave-to {
+	transform: translate3d(0, -100%, 0)
+}
+
+.uv-slide-left-enter,
+.uv-slide-left-leave-to {
+	transform: translate3d(-100%, 0, 0)
+}
+
+.uv-slide-right-enter,
+.uv-slide-right-leave-to {
+	transform: translate3d(100%, 0, 0)
+}
+
+.uv-zoom-enter-active,
+.uv-zoom-leave-active {
+	transition-property: transform
+}
+
+.uv-zoom-enter,
+.uv-zoom-leave-to {
+	transform: $uv-zoom-scale
+}

+ 87 - 0
uni_modules/uv-transition/package.json

@@ -0,0 +1,87 @@
+{
+  "id": "uv-transition",
+  "displayName": "uv-transition 动画  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.2",
+  "description": "transition 该组件用于组件的动画过渡效果。",
+  "keywords": [
+    "uv-transition",
+    "uvui",
+    "uv-ui",
+    "transition",
+    "动画"
+],
+  "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-transition/readme.md

@@ -0,0 +1,11 @@
+## Transition 动画
+
+> **组件名:uv-transition**
+
+该组件用于组件的动画过渡效果。
+
+### <a href="https://www.uvui.cn/components/transition.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>

+ 12 - 0
uni_modules/uv-ui-tools/changelog.md

@@ -0,0 +1,12 @@
+## 1.0.4(2023-05-23)
+1. 兼容百度小程序修改bem函数
+## 1.0.3(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.2(2023-05-10)
+1. 增加Http请求封装
+2. 优化
+## 1.0.1(2023-05-04)
+1. 修改名称及备注
+## 1.0.0(2023-05-04)
+1. uv-ui工具集首次发布

+ 6 - 0
uni_modules/uv-ui-tools/components/uv-ui-tools/uv-ui-tools.vue

@@ -0,0 +1,6 @@
+<template>
+</template>
+<script>
+</script>
+<style>
+</style>

+ 40 - 0
uni_modules/uv-ui-tools/index.js

@@ -0,0 +1,40 @@
+// 全局挂载引入http相关请求拦截插件
+import Request from './libs/luch-request'
+
+// 路由封装
+import route from './libs/util/route.js'
+// 公共工具函数
+import * as index from './libs/function/index.js'
+// 防抖方法
+import debounce from './libs/function/debounce.js'
+// 节流方法
+import throttle from './libs/function/throttle.js'
+// 规则检验
+import * as test from './libs/function/test.js'
+
+// 颜色渐变相关,colorGradient-颜色渐变,hexToRgb-十六进制颜色转rgb颜色,rgbToHex-rgb转十六进制
+import * as colorGradient from './libs/function/colorGradient.js'
+
+// 配置信息
+import config from './libs/config/config.js'
+// 平台
+import platform from './libs/function/platform'
+
+const $uv = {
+	route,
+	config,
+	test,
+	throttle,
+	date: index.timeFormat, // 另名date
+	...index,
+	colorGradient: colorGradient.colorGradient,
+	hexToRgb: colorGradient.hexToRgb,
+	rgbToHex: colorGradient.rgbToHex,
+	colorToRgba: colorGradient.colorToRgba,
+	http: new Request(),
+	debounce,
+	throttle,
+	platform
+}
+uni.$uv = $uv;
+export default {}

+ 7 - 0
uni_modules/uv-ui-tools/index.scss

@@ -0,0 +1,7 @@
+// 引入公共基础类
+@import "./libs/css/common.scss";
+
+// 非nvue的样式
+/* #ifndef APP-NVUE */
+@import "./libs/css/vue.scss";
+/* #endif */

+ 34 - 0
uni_modules/uv-ui-tools/libs/config/config.js

@@ -0,0 +1,34 @@
+// 此版本发布于2023-05-23
+const version = '1.0.2'
+
+// 开发环境才提示,生产环境不会提示
+if (process.env.NODE_ENV === 'development') {
+	console.log(`\n %c uvui V${version} https://www.uvui.cn/ \n\n`, 'color: #ffffff; background: #3c9cff; padding:5px 0; border-radius: 5px;');
+}
+
+export default {
+    v: version,
+    version,
+    // 主题名称
+    type: [
+        'primary',
+        'success',
+        'info',
+        'error',
+        'warning'
+    ],
+    // 颜色部分,本来可以通过scss的:export导出供js使用,但是奈何nvue不支持
+    color: {
+        'uv-primary': '#2979ff',
+        'uv-warning': '#ff9900',
+        'uv-success': '#19be6b',
+        'uv-error': '#fa3534',
+        'uv-info': '#909399',
+        'uv-main-color': '#303133',
+        'uv-content-color': '#606266',
+        'uv-tips-color': '#909399',
+        'uv-light-color': '#c0c4cc'
+    },
+	// 默认单位,可以通过配置为rpx,那么在用于传入组件大小参数为数值时,就默认为rpx
+	unit: 'px'
+}

+ 151 - 0
uni_modules/uv-ui-tools/libs/css/color.scss

@@ -0,0 +1,151 @@
+$main-color: #303133;
+$content-color: #606266;
+$tips-color: #909193;
+$light-color: #c0c4cc;
+$border-color: #dadbde;
+$bg-color: #f3f4f6;
+$disabled-color: #c8c9cc;
+
+$primary: #3c9cff;
+$primary-dark: #398ade;
+$primary-disabled: #9acafc;
+$primary-light: #ecf5ff;
+
+$warning: #f9ae3d;
+$warning-dark: #f1a532;
+$warning-disabled: #f9d39b;
+$warning-light: #fdf6ec;
+
+$success: #5ac725;
+$success-dark: #53c21d;
+$success-disabled: #a9e08f;
+$success-light: #f5fff0;
+
+$error: #f56c6c;
+$error-dark: #e45656;
+$error-disabled: #f7b2b2;
+$error-light: #fef0f0;
+
+$info: #909399;
+$info-dark: #767a82;
+$info-disabled: #c4c6c9;
+$info-light: #f4f4f5;
+@if variable-exists(uv-main-color) {
+	$main-color: $uv-main-color;
+}
+@if variable-exists(uv-content-color) {
+	$content-color: $uv-content-color;
+}
+@if variable-exists(uv-tips-color) {
+	$tips-color: $uv-tips-color;
+}
+@if variable-exists(uv-light-color) {
+	$light-color: $uv-light-color;
+}
+@if variable-exists(uv-border-color) {
+	$border-color: $uv-border-color;
+}
+@if variable-exists(uv-bg-color) {
+	$bg-color: $uv-bg-color;
+}
+@if variable-exists(uv-disabled-color) {
+	$disabled-color: $uv-disabled-color;
+}
+
+@if variable-exists(uv-primary) {
+	$primary: $uv-primary;
+}
+@if variable-exists(uv-primary-dark) {
+	$primary-dark: $uv-primary-dark;
+}
+@if variable-exists(uv-primary-disabled) {
+	$primary-disabled: $uv-primary-disabled;
+}
+@if variable-exists(uv-primary-light) {
+	$primary-light: $uv-primary-light;
+}
+
+@if variable-exists(uv-warning) {
+	$warning: $uv-warning;
+}
+@if variable-exists(uv-warning-dark) {
+	$warning-dark: $uv-warning-dark;
+}
+@if variable-exists(uv-warning-disabled) {
+	$warning-disabled: $uv-warning-disabled;
+}
+@if variable-exists(uv-warning-light) {
+	$warning-light: $uv-warning-light;
+}
+
+@if variable-exists(uv-success) {
+	$success: $uv-success;
+}
+@if variable-exists(uv-success-dark) {
+	$success-dark: $uv-success-dark;
+}
+@if variable-exists(uv-success-disabled) {
+	$success-disabled: $uv-success-disabled;
+}
+@if variable-exists(uv-success-light) {
+	$success-light: $uv-success-light;
+}
+
+@if variable-exists(uv-error) {
+	$error: $uv-error;
+}
+@if variable-exists(uv-error-dark) {
+	$error-dark: $uv-error-dark;
+}
+@if variable-exists(uv-error-disabled) {
+	$error-disabled: $uv-error-disabled;
+}
+@if variable-exists(uv-error-light) {
+	$error-light: $uv-error-light;
+}
+
+@if variable-exists(uv-info) {
+	$info: $uv-info;
+}
+@if variable-exists(uv-info-dark) {
+	$info-dark: $uv-info-dark;
+}
+@if variable-exists(uv-info-disabled) {
+	$info-disabled: $uv-info-disabled;
+}
+@if variable-exists(uv-info-light) {
+	$info-light: $uv-info-light;
+}
+
+$uv-main-color: $main-color;
+$uv-content-color: $content-color;
+$uv-tips-color: $tips-color;
+$uv-light-color: $light-color;
+$uv-border-color: $border-color;
+$uv-bg-color: $bg-color;
+$uv-disabled-color: $disabled-color;
+
+$uv-primary: $primary;
+$uv-primary-dark: $primary-dark;
+$uv-primary-disabled: $primary-disabled;
+$uv-primary-light: $primary-light;
+
+$uv-warning: $warning;
+$uv-warning-dark: $warning-dark;
+$uv-warning-disabled: $warning-disabled;
+$uv-warning-light: $warning-light;
+
+$uv-success: $success;
+$uv-success-dark: $success-dark;
+$uv-success-disabled: $success-disabled;
+$uv-success-light: $success-light;
+
+$uv-error: $error;
+$uv-error-dark: $error-dark;
+$uv-error-disabled: $error-disabled;
+$uv-error-light: $error-light;
+
+$uv-info: $info;
+$uv-info-dark: $info-dark;
+$uv-info-disabled: $info-disabled;
+$uv-info-light: $info-light;

+ 100 - 0
uni_modules/uv-ui-tools/libs/css/common.scss

@@ -0,0 +1,100 @@
+// 超出行数,自动显示行尾省略号,最多5行
+// 来自uvui的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】
+@for $i from 1 through 5 {
+	.uv-line-#{$i} {
+		/* #ifdef APP-NVUE */
+		// nvue下,可以直接使用lines属性,这是weex特有样式
+		lines: $i;
+		text-overflow: ellipsis;
+		overflow: hidden;
+		flex: 1;
+		/* #endif */
+
+		/* #ifndef APP-NVUE */
+		// vue下,单行和多行显示省略号需要单独处理
+		@if $i == '1' {
+			overflow: hidden;
+			white-space: nowrap;
+			text-overflow: ellipsis;
+		} @else {
+			display: -webkit-box!important;
+			overflow: hidden;
+			text-overflow: ellipsis;
+			word-break: break-all;
+			-webkit-line-clamp: $i;
+			-webkit-box-orient: vertical!important;
+		}
+		/* #endif */
+	}
+}
+$uv-bordercolor: #dadbde;
+@if variable-exists(uv-border-color) {
+	$uv-bordercolor: $uv-border-color;
+}
+
+// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时,
+// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效
+// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important
+// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现
+.uv-border {
+	border-width: 0.5px!important;
+	border-color: $uv-bordercolor!important;
+    border-style: solid;
+}
+
+.uv-border-top {
+	border-top-width: 0.5px!important;
+	border-color: $uv-bordercolor!important;
+    border-top-style: solid;
+}
+
+.uv-border-left {
+	border-left-width: 0.5px!important;
+	border-color: $uv-bordercolor!important;
+    border-left-style: solid;
+}
+
+.uv-border-right {
+	border-right-width: 0.5px!important;
+	border-color: $uv-bordercolor!important;
+    border-right-style: solid;
+}
+
+.uv-border-bottom {
+	border-bottom-width: 0.5px!important;
+	border-color: $uv-bordercolor!important;
+    border-bottom-style: solid;
+}
+
+.uv-border-top-bottom {
+	border-top-width: 0.5px!important;
+	border-bottom-width: 0.5px!important;
+	border-color: $uv-bordercolor!important;
+    border-top-style: solid;
+    border-bottom-style: solid;
+}
+
+// 去除button的所有默认样式,让其表现跟普通的view、text元素一样
+.uv-reset-button {
+	padding: 0;
+	background-color: transparent;
+	/* #ifndef APP-PLUS */
+	font-size: inherit;
+	line-height: inherit;
+	color: inherit;
+	/* #endif */
+	/* #ifdef APP-NVUE */
+	border-width: 0;
+	/* #endif */
+}
+
+/* #ifndef APP-NVUE */
+.uv-reset-button::after {
+   border: none;
+}
+/* #endif */
+
+.uv-hover-class {
+	opacity: 0.7;
+}
+

+ 20 - 0
uni_modules/uv-ui-tools/libs/css/components.scss

@@ -0,0 +1,20 @@
+@mixin flex($direction: row) {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: $direction;
+}
+
+/* #ifndef APP-NVUE */
+// 由于uvui是基于nvue环境进行开发的,此环境中普通元素默认为flex-direction: column;
+// 所以在非nvue中,需要对元素进行重置为flex-direction: column; 否则可能会表现异常
+view, scroll-view, swiper-item {
+	display: flex;
+	flex-direction: column;
+	flex-shrink: 0;
+	flex-grow: 0;
+	flex-basis: auto;
+	align-items: stretch;
+	align-content: flex-start;
+}
+/* #endif */

+ 111 - 0
uni_modules/uv-ui-tools/libs/css/variable.scss

@@ -0,0 +1,111 @@
+// 超出行数,自动显示行尾省略号,最多5行
+// 来自uvui的温馨提示:当您在控制台看到此报错,说明需要在App.vue的style标签加上【lang="scss"】
+@if variable-exists(show-lines) {
+	@for $i from 1 through 5 {
+		.uv-line-#{$i} {
+			/* #ifdef APP-NVUE */
+			// nvue下,可以直接使用lines属性,这是weex特有样式
+			lines: $i;
+			text-overflow: ellipsis;
+			overflow: hidden;
+			flex: 1;
+			/* #endif */
+
+			/* #ifndef APP-NVUE */
+			// vue下,单行和多行显示省略号需要单独处理
+			@if $i == '1' {
+				overflow: hidden;
+				white-space: nowrap;
+				text-overflow: ellipsis;
+			} @else {
+				display: -webkit-box!important;
+				overflow: hidden;
+				text-overflow: ellipsis;
+				word-break: break-all;
+				-webkit-line-clamp: $i;
+				-webkit-box-orient: vertical!important;
+			}
+			/* #endif */
+		}
+	}
+}
+@if variable-exists(show-border) {
+	$uv-bordercolor: #dadbde;
+	@if variable-exists(uv-border-color) {
+		$uv-bordercolor: $uv-border-color;
+	}
+	// 此处加上!important并非随意乱用,而是因为目前*.nvue页面编译到H5时,
+	// App.vue的样式会被uni-app的view元素的自带border属性覆盖,导致无效
+	// 综上,这是uni-app的缺陷导致我们为了多端兼容,而必须要加上!important
+	// 移动端兼容性较好,直接使用0.5px去实现细边框,不使用伪元素形式实现
+	@if variable-exists(show-border-surround) {
+		.uv-border {
+			border-width: 0.5px!important;
+			border-color: $uv-bordercolor!important;
+			border-style: solid;
+		}
+	}
+	@if variable-exists(show-border-top) {
+		.uv-border-top {
+			border-top-width: 0.5px!important;
+			border-color: $uv-bordercolor!important;
+			border-top-style: solid;
+		}
+	}
+	@if variable-exists(show-border-left) {
+		.uv-border-left {
+			border-left-width: 0.5px!important;
+			border-color: $uv-bordercolor!important;
+			border-left-style: solid;
+		}
+	}
+	@if variable-exists(show-border-right) {
+		.uv-border-right {
+			border-right-width: 0.5px!important;
+			border-color: $uv-bordercolor!important;
+			border-right-style: solid;
+		}
+	}
+	@if variable-exists(show-border-bottom) {
+		.uv-border-bottom {
+			border-bottom-width: 0.5px!important;
+			border-color: $uv-bordercolor!important;
+				border-bottom-style: solid;
+		}
+	}
+	@if variable-exists(show-border-top-bottom) {
+		.uv-border-top-bottom {
+			border-top-width: 0.5px!important;
+			border-bottom-width: 0.5px!important;
+			border-color: $uv-bordercolor!important;
+			border-top-style: solid;
+			border-bottom-style: solid;
+		}
+	}
+}
+@if variable-exists(show-reset-button) {
+	// 去除button的所有默认样式,让其表现跟普通的view、text元素一样
+	.uv-reset-button {
+		padding: 0;
+		background-color: transparent;
+		/* #ifndef APP-PLUS */
+		font-size: inherit;
+		line-height: inherit;
+		color: inherit;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		border-width: 0;
+		/* #endif */
+	}
+
+	/* #ifndef APP-NVUE */
+	.uv-reset-button::after {
+		 border: none;
+	}
+	/* #endif */
+}
+@if variable-exists(show-hover) {
+	.uv-hover-class {
+		opacity: 0.7;
+	}
+}

+ 27 - 0
uni_modules/uv-ui-tools/libs/css/vue.scss

@@ -0,0 +1,27 @@
+// 历遍生成4个方向的底部安全区
+@each $d in top, right, bottom, left {
+	.uv-safe-area-inset-#{$d} {
+		padding-#{$d}: 0;
+		padding-#{$d}: constant(safe-area-inset-#{$d});  
+		padding-#{$d}: env(safe-area-inset-#{$d});  
+	}
+}
+
+//提升H5端uni.toast()的层级,避免被uvui的modal等遮盖
+/* #ifdef H5 */
+uni-toast {
+    z-index: 10090;
+}
+uni-toast .uni-toast {
+   z-index: 10090;
+}
+/* #endif */
+
+// 隐藏scroll-view的滚动条
+::-webkit-scrollbar {
+    display: none;  
+    width: 0 !important;  
+    height: 0 !important;  
+    -webkit-appearance: none;  
+    background: transparent;  
+}

+ 134 - 0
uni_modules/uv-ui-tools/libs/function/colorGradient.js

@@ -0,0 +1,134 @@
+/**
+ * 求两个颜色之间的渐变值
+ * @param {string} startColor 开始的颜色
+ * @param {string} endColor 结束的颜色
+ * @param {number} step 颜色等分的份额
+ * */
+function colorGradient(startColor = 'rgb(0, 0, 0)', endColor = 'rgb(255, 255, 255)', step = 10) {
+    const startRGB = hexToRgb(startColor, false) // 转换为rgb数组模式
+    const startR = startRGB[0]
+    const startG = startRGB[1]
+    const startB = startRGB[2]
+
+    const endRGB = hexToRgb(endColor, false)
+    const endR = endRGB[0]
+    const endG = endRGB[1]
+    const endB = endRGB[2]
+
+    const sR = (endR - startR) / step // 总差值
+    const sG = (endG - startG) / step
+    const sB = (endB - startB) / step
+    const colorArr = []
+    for (let i = 0; i < step; i++) {
+        // 计算每一步的hex值
+        let hex = rgbToHex(`rgb(${Math.round((sR * i + startR))},${Math.round((sG * i + startG))},${Math.round((sB
+			* i + startB))})`)
+        // 确保第一个颜色值为startColor的值
+        if (i === 0) hex = rgbToHex(startColor)
+        // 确保最后一个颜色值为endColor的值
+        if (i === step - 1) hex = rgbToHex(endColor)
+        colorArr.push(hex)
+    }
+    return colorArr
+}
+
+// 将hex表示方式转换为rgb表示方式(这里返回rgb数组模式)
+function hexToRgb(sColor, str = true) {
+    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
+    sColor = String(sColor).toLowerCase()
+    if (sColor && reg.test(sColor)) {
+        if (sColor.length === 4) {
+            let sColorNew = '#'
+            for (let i = 1; i < 4; i += 1) {
+                sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
+            }
+            sColor = sColorNew
+        }
+        // 处理六位的颜色值
+        const sColorChange = []
+        for (let i = 1; i < 7; i += 2) {
+            sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))
+        }
+        if (!str) {
+            return sColorChange
+        }
+        return `rgb(${sColorChange[0]},${sColorChange[1]},${sColorChange[2]})`
+    } if (/^(rgb|RGB)/.test(sColor)) {
+        const arr = sColor.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')
+        return arr.map((val) => Number(val))
+    }
+    return sColor
+}
+
+// 将rgb表示方式转换为hex表示方式
+function rgbToHex(rgb) {
+    const _this = rgb
+    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
+    if (/^(rgb|RGB)/.test(_this)) {
+        const aColor = _this.replace(/(?:\(|\)|rgb|RGB)*/g, '').split(',')
+        let strHex = '#'
+        for (let i = 0; i < aColor.length; i++) {
+            let hex = Number(aColor[i]).toString(16)
+            hex = String(hex).length == 1 ? `${0}${hex}` : hex // 保证每个rgb的值为2位
+            if (hex === '0') {
+                hex += hex
+            }
+            strHex += hex
+        }
+        if (strHex.length !== 7) {
+            strHex = _this
+        }
+        return strHex
+    } if (reg.test(_this)) {
+        const aNum = _this.replace(/#/, '').split('')
+        if (aNum.length === 6) {
+            return _this
+        } if (aNum.length === 3) {
+            let numHex = '#'
+            for (let i = 0; i < aNum.length; i += 1) {
+                numHex += (aNum[i] + aNum[i])
+            }
+            return numHex
+        }
+    } else {
+        return _this
+    }
+}
+
+/**
+* JS颜色十六进制转换为rgb或rgba,返回的格式为 rgba(255,255,255,0.5)字符串
+* sHex为传入的十六进制的色值
+* alpha为rgba的透明度
+*/
+function colorToRgba(color, alpha) {
+    color = rgbToHex(color)
+    // 十六进制颜色值的正则表达式
+    const reg = /^#([0-9a-fA-f]{3}|[0-9a-fA-f]{6})$/
+    /* 16进制颜色转为RGB格式 */
+    let sColor = String(color).toLowerCase()
+    if (sColor && reg.test(sColor)) {
+        if (sColor.length === 4) {
+            let sColorNew = '#'
+            for (let i = 1; i < 4; i += 1) {
+                sColorNew += sColor.slice(i, i + 1).concat(sColor.slice(i, i + 1))
+            }
+            sColor = sColorNew
+        }
+        // 处理六位的颜色值
+        const sColorChange = []
+        for (let i = 1; i < 7; i += 2) {
+            sColorChange.push(parseInt(`0x${sColor.slice(i, i + 2)}`))
+        }
+        // return sColorChange.join(',')
+        return `rgba(${sColorChange.join(',')},${alpha})`
+    }
+
+    return sColor
+}
+
+export {
+    colorGradient,
+    hexToRgb,
+    rgbToHex,
+    colorToRgba
+}

+ 29 - 0
uni_modules/uv-ui-tools/libs/function/debounce.js

@@ -0,0 +1,29 @@
+let timeout = null
+
+/**
+ * 防抖原理:一定时间内,只有最后一次操作,再过wait毫秒后才执行函数
+ *
+ * @param {Function} func 要执行的回调函数
+ * @param {Number} wait 延时的时间
+ * @param {Boolean} immediate 是否立即执行
+ * @return null
+ */
+function debounce(func, wait = 500, immediate = false) {
+    // 清除定时器
+    if (timeout !== null) clearTimeout(timeout)
+    // 立即执行,此类情况一般用不到
+    if (immediate) {
+        const callNow = !timeout
+        timeout = setTimeout(() => {
+            timeout = null
+        }, wait)
+        if (callNow) typeof func === 'function' && func()
+    } else {
+        // 设置定时器,当最后一次操作后,timeout不会再被清除,所以在延时wait毫秒后执行func回调方法
+        timeout = setTimeout(() => {
+            typeof func === 'function' && func()
+        }, wait)
+    }
+}
+
+export default debounce

+ 167 - 0
uni_modules/uv-ui-tools/libs/function/digit.js

@@ -0,0 +1,167 @@
+let _boundaryCheckingState = true; // 是否进行越界检查的全局开关
+
+/**
+ * 把错误的数据转正
+ * @private
+ * @example strip(0.09999999999999998)=0.1
+ */
+function strip(num, precision = 15) {
+  return +parseFloat(Number(num).toPrecision(precision));
+}
+
+/**
+ * Return digits length of a number
+ * @private
+ * @param {*number} num Input number
+ */
+function digitLength(num) {
+  // Get digit length of e
+  const eSplit = num.toString().split(/[eE]/);
+  const len = (eSplit[0].split('.')[1] || '').length - +(eSplit[1] || 0);
+  return len > 0 ? len : 0;
+}
+
+/**
+ * 把小数转成整数,如果是小数则放大成整数
+ * @private
+ * @param {*number} num 输入数
+ */
+function float2Fixed(num) {
+  if (num.toString().indexOf('e') === -1) {
+    return Number(num.toString().replace('.', ''));
+  }
+  const dLen = digitLength(num);
+  return dLen > 0 ? strip(Number(num) * Math.pow(10, dLen)) : Number(num);
+}
+
+/**
+ * 检测数字是否越界,如果越界给出提示
+ * @private
+ * @param {*number} num 输入数
+ */
+function checkBoundary(num) {
+  if (_boundaryCheckingState) {
+    if (num > Number.MAX_SAFE_INTEGER || num < Number.MIN_SAFE_INTEGER) {
+      console.warn(`${num} 超出了精度限制,结果可能不正确`);
+    }
+  }
+}
+
+/**
+ * 把递归操作扁平迭代化
+ * @param {number[]} arr 要操作的数字数组
+ * @param {function} operation 迭代操作
+ * @private
+ */
+function iteratorOperation(arr, operation) {
+  const [num1, num2, ...others] = arr;
+  let res = operation(num1, num2);
+
+  others.forEach((num) => {
+    res = operation(res, num);
+  });
+
+  return res;
+}
+
+/**
+ * 高精度乘法
+ * @export
+ */
+export function times(...nums) {
+  if (nums.length > 2) {
+    return iteratorOperation(nums, times);
+  }
+
+  const [num1, num2] = nums;
+  const num1Changed = float2Fixed(num1);
+  const num2Changed = float2Fixed(num2);
+  const baseNum = digitLength(num1) + digitLength(num2);
+  const leftValue = num1Changed * num2Changed;
+
+  checkBoundary(leftValue);
+
+  return leftValue / Math.pow(10, baseNum);
+}
+
+/**
+ * 高精度加法
+ * @export
+ */
+export function plus(...nums) {
+  if (nums.length > 2) {
+    return iteratorOperation(nums, plus);
+  }
+
+  const [num1, num2] = nums;
+  // 取最大的小数位
+  const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
+  // 把小数都转为整数然后再计算
+  return (times(num1, baseNum) + times(num2, baseNum)) / baseNum;
+}
+
+/**
+ * 高精度减法
+ * @export
+ */
+export function minus(...nums) {
+  if (nums.length > 2) {
+    return iteratorOperation(nums, minus);
+  }
+
+  const [num1, num2] = nums;
+  const baseNum = Math.pow(10, Math.max(digitLength(num1), digitLength(num2)));
+  return (times(num1, baseNum) - times(num2, baseNum)) / baseNum;
+}
+
+/**
+ * 高精度除法
+ * @export
+ */
+export function divide(...nums) {
+  if (nums.length > 2) {
+    return iteratorOperation(nums, divide);
+  }
+
+  const [num1, num2] = nums;
+  const num1Changed = float2Fixed(num1);
+  const num2Changed = float2Fixed(num2);
+  checkBoundary(num1Changed);
+  checkBoundary(num2Changed);
+  // 重要,这里必须用strip进行修正
+  return times(num1Changed / num2Changed, strip(Math.pow(10, digitLength(num2) - digitLength(num1))));
+}
+
+/**
+ * 四舍五入
+ * @export
+ */
+export function round(num, ratio) {
+  const base = Math.pow(10, ratio);
+  let result = divide(Math.round(Math.abs(times(num, base))), base);
+  if (num < 0 && result !== 0) {
+    result = times(result, -1);
+  }
+  // 位数不足则补0
+  return result;
+}
+
+/**
+ * 是否进行边界检查,默认开启
+ * @param flag 标记开关,true 为开启,false 为关闭,默认为 true
+ * @export
+ */
+export function enableBoundaryChecking(flag = true) {
+  _boundaryCheckingState = flag;
+}
+
+
+export default {
+  times,
+  plus,
+  minus,
+  divide,
+  round,
+  enableBoundaryChecking,
+};
+

+ 733 - 0
uni_modules/uv-ui-tools/libs/function/index.js

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

+ 75 - 0
uni_modules/uv-ui-tools/libs/function/platform.js

@@ -0,0 +1,75 @@
+/**
+ * 注意:
+ * 此部分内容,在vue-cli模式下,需要在vue.config.js加入如下内容才有效:
+ * module.exports = {
+ *     transpileDependencies: ['uview-v2']
+ * }
+ */
+
+let platform = 'none'
+
+// #ifdef VUE3
+platform = 'vue3'
+// #endif
+
+// #ifdef VUE2
+platform = 'vue2'
+// #endif
+
+// #ifdef APP-PLUS
+platform = 'plus'
+// #endif
+
+// #ifdef APP-NVUE
+platform = 'nvue'
+// #endif
+
+// #ifdef H5
+platform = 'h5'
+// #endif
+
+// #ifdef MP-WEIXIN
+platform = 'weixin'
+// #endif
+
+// #ifdef MP-ALIPAY
+platform = 'alipay'
+// #endif
+
+// #ifdef MP-BAIDU
+platform = 'baidu'
+// #endif
+
+// #ifdef MP-TOUTIAO
+platform = 'toutiao'
+// #endif
+
+// #ifdef MP-QQ
+platform = 'qq'
+// #endif
+
+// #ifdef MP-KUAISHOU
+platform = 'kuaishou'
+// #endif
+
+// #ifdef MP-360
+platform = '360'
+// #endif
+
+// #ifdef MP
+platform = 'mp'
+// #endif
+
+// #ifdef QUICKAPP-WEBVIEW
+platform = 'quickapp-webview'
+// #endif
+
+// #ifdef QUICKAPP-WEBVIEW-HUAWEI
+platform = 'quickapp-webview-huawei'
+// #endif
+
+// #ifdef QUICKAPP-WEBVIEW-UNION
+platform = 'quckapp-webview-union'
+// #endif
+
+export default platform

+ 287 - 0
uni_modules/uv-ui-tools/libs/function/test.js

@@ -0,0 +1,287 @@
+/**
+ * 验证电子邮箱格式
+ */
+function email(value) {
+    return /^\w+((-\w+)|(\.\w+))*\@[A-Za-z0-9]+((\.|-)[A-Za-z0-9]+)*\.[A-Za-z0-9]+$/.test(value)
+}
+
+/**
+ * 验证手机格式
+ */
+function mobile(value) {
+    return /^1([3589]\d|4[5-9]|6[1-2,4-7]|7[0-8])\d{8}$/.test(value)
+}
+
+/**
+ * 验证URL格式
+ */
+function url(value) {
+    return /^((https|http|ftp|rtsp|mms):\/\/)(([0-9a-zA-Z_!~*'().&=+$%-]+: )?[0-9a-zA-Z_!~*'().&=+$%-]+@)?(([0-9]{1,3}.){3}[0-9]{1,3}|([0-9a-zA-Z_!~*'()-]+.)*([0-9a-zA-Z][0-9a-zA-Z-]{0,61})?[0-9a-zA-Z].[a-zA-Z]{2,6})(:[0-9]{1,4})?((\/?)|(\/[0-9a-zA-Z_!~*'().;?:@&=+$,%#-]+)+\/?)$/
+        .test(value)
+}
+
+/**
+ * 验证日期格式
+ */
+function date(value) {
+    if (!value) return false
+    // 判断是否数值或者字符串数值(意味着为时间戳),转为数值,否则new Date无法识别字符串时间戳
+    if (number(value)) value = +value
+    return !/Invalid|NaN/.test(new Date(value).toString())
+}
+
+/**
+ * 验证ISO类型的日期格式
+ */
+function dateISO(value) {
+    return /^\d{4}[\/\-](0?[1-9]|1[012])[\/\-](0?[1-9]|[12][0-9]|3[01])$/.test(value)
+}
+
+/**
+ * 验证十进制数字
+ */
+function number(value) {
+    return /^[\+-]?(\d+\.?\d*|\.\d+|\d\.\d+e\+\d+)$/.test(value)
+}
+
+/**
+ * 验证字符串
+ */
+function string(value) {
+    return typeof value === 'string'
+}
+
+/**
+ * 验证整数
+ */
+function digits(value) {
+    return /^\d+$/.test(value)
+}
+
+/**
+ * 验证身份证号码
+ */
+function idCard(value) {
+    return /^[1-9]\d{5}[1-9]\d{3}((0\d)|(1[0-2]))(([0|1|2]\d)|3[0-1])\d{3}([0-9]|X)$/.test(
+        value
+    )
+}
+
+/**
+ * 是否车牌号
+ */
+function carNo(value) {
+    // 新能源车牌
+    const xreg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}(([0-9]{5}[DF]$)|([DF][A-HJ-NP-Z0-9][0-9]{4}$))/
+    // 旧车牌
+    const creg = /^[京津沪渝冀豫云辽黑湘皖鲁新苏浙赣鄂桂甘晋蒙陕吉闽贵粤青藏川宁琼使领A-Z]{1}[A-Z]{1}[A-HJ-NP-Z0-9]{4}[A-HJ-NP-Z0-9挂学警港澳]{1}$/
+    if (value.length === 7) {
+        return creg.test(value)
+    } if (value.length === 8) {
+        return xreg.test(value)
+    }
+    return false
+}
+
+/**
+ * 金额,只允许2位小数
+ */
+function amount(value) {
+    // 金额,只允许保留两位小数
+    return /^[1-9]\d*(,\d{3})*(\.\d{1,2})?$|^0\.\d{1,2}$/.test(value)
+}
+
+/**
+ * 中文
+ */
+function chinese(value) {
+    const reg = /^[\u4e00-\u9fa5]+$/gi
+    return reg.test(value)
+}
+
+/**
+ * 只能输入字母
+ */
+function letter(value) {
+    return /^[a-zA-Z]*$/.test(value)
+}
+
+/**
+ * 只能是字母或者数字
+ */
+function enOrNum(value) {
+    // 英文或者数字
+    const reg = /^[0-9a-zA-Z]*$/g
+    return reg.test(value)
+}
+
+/**
+ * 验证是否包含某个值
+ */
+function contains(value, param) {
+    return value.indexOf(param) >= 0
+}
+
+/**
+ * 验证一个值范围[min, max]
+ */
+function range(value, param) {
+    return value >= param[0] && value <= param[1]
+}
+
+/**
+ * 验证一个长度范围[min, max]
+ */
+function rangeLength(value, param) {
+    return value.length >= param[0] && value.length <= param[1]
+}
+
+/**
+ * 是否固定电话
+ */
+function landline(value) {
+    const reg = /^\d{3,4}-\d{7,8}(-\d{3,4})?$/
+    return reg.test(value)
+}
+
+/**
+ * 判断是否为空
+ */
+function empty(value) {
+    switch (typeof value) {
+    case 'undefined':
+        return true
+    case 'string':
+        if (value.replace(/(^[ \t\n\r]*)|([ \t\n\r]*$)/g, '').length == 0) return true
+        break
+    case 'boolean':
+        if (!value) return true
+        break
+    case 'number':
+        if (value === 0 || isNaN(value)) return true
+        break
+    case 'object':
+        if (value === null || value.length === 0) return true
+        for (const i in value) {
+            return false
+        }
+        return true
+    }
+    return false
+}
+
+/**
+ * 是否json字符串
+ */
+function jsonString(value) {
+    if (typeof value === 'string') {
+        try {
+            const obj = JSON.parse(value)
+            if (typeof obj === 'object' && obj) {
+                return true
+            }
+            return false
+        } catch (e) {
+            return false
+        }
+    }
+    return false
+}
+
+/**
+ * 是否数组
+ */
+function array(value) {
+    if (typeof Array.isArray === 'function') {
+        return Array.isArray(value)
+    }
+    return Object.prototype.toString.call(value) === '[object Array]'
+}
+
+/**
+ * 是否对象
+ */
+function object(value) {
+    return Object.prototype.toString.call(value) === '[object Object]'
+}
+
+/**
+ * 是否短信验证码
+ */
+function code(value, len = 6) {
+    return new RegExp(`^\\d{${len}}$`).test(value)
+}
+
+/**
+ * 是否函数方法
+ * @param {Object} value
+ */
+function func(value) {
+    return typeof value === 'function'
+}
+
+/**
+ * 是否promise对象
+ * @param {Object} value
+ */
+function promise(value) {
+    return object(value) && func(value.then) && func(value.catch)
+}
+
+/** 是否图片格式
+ * @param {Object} value
+ */
+function image(value) {
+    const newValue = value.split('?')[0]
+    const IMAGE_REGEXP = /\.(jpeg|jpg|gif|png|svg|webp|jfif|bmp|dpg)/i
+    return IMAGE_REGEXP.test(newValue)
+}
+
+/**
+ * 是否视频格式
+ * @param {Object} value
+ */
+function video(value) {
+    const VIDEO_REGEXP = /\.(mp4|mpg|mpeg|dat|asf|avi|rm|rmvb|mov|wmv|flv|mkv|m3u8)/i
+    return VIDEO_REGEXP.test(value)
+}
+
+/**
+ * 是否为正则对象
+ * @param {Object}
+ * @return {Boolean}
+ */
+function regExp(o) {
+    return o && Object.prototype.toString.call(o) === '[object RegExp]'
+}
+
+export {
+    email,
+    mobile,
+    url,
+    date,
+    dateISO,
+    number,
+    digits,
+    idCard,
+    carNo,
+    amount,
+    chinese,
+    letter,
+    enOrNum,
+    contains,
+    range,
+    rangeLength,
+    empty,
+    jsonString,
+    landline,
+    object,
+    array,
+    code,
+    func,
+    promise,
+    video,
+    image,
+    regExp,
+    string
+}

+ 30 - 0
uni_modules/uv-ui-tools/libs/function/throttle.js

@@ -0,0 +1,30 @@
+let timer; let
+    flag
+/**
+ * 节流原理:在一定时间内,只能触发一次
+ *
+ * @param {Function} func 要执行的回调函数
+ * @param {Number} wait 延时的时间
+ * @param {Boolean} immediate 是否立即执行
+ * @return null
+ */
+function throttle(func, wait = 500, immediate = true) {
+    if (immediate) {
+        if (!flag) {
+            flag = true
+            // 如果是立即执行,则在wait毫秒内开始时执行
+            typeof func === 'function' && func()
+            timer = setTimeout(() => {
+                flag = false
+            }, wait)
+        }
+    } else if (!flag) {
+        flag = true
+        // 如果是非立即执行,则在wait毫秒内的结束处执行
+        timer = setTimeout(() => {
+            flag = false
+            typeof func === 'function' && func()
+        }, wait)
+    }
+}
+export default throttle

+ 97 - 0
uni_modules/uv-ui-tools/libs/luch-request/adapters/index.js

@@ -0,0 +1,97 @@
+import buildURL from '../helpers/buildURL'
+import buildFullPath from '../core/buildFullPath'
+import settle from '../core/settle'
+import { isUndefined } from '../utils'
+
+/**
+ * 返回可选值存在的配置
+ * @param {Array} keys - 可选值数组
+ * @param {Object} config2 - 配置
+ * @return {{}} - 存在的配置项
+ */
+const mergeKeys = (keys, config2) => {
+    const config = {}
+    keys.forEach((prop) => {
+        if (!isUndefined(config2[prop])) {
+            config[prop] = config2[prop]
+        }
+    })
+    return config
+}
+export default (config) => new Promise((resolve, reject) => {
+    const fullPath = buildURL(buildFullPath(config.baseURL, config.url), config.params)
+    const _config = {
+        url: fullPath,
+        header: config.header,
+        complete: (response) => {
+            config.fullPath = fullPath
+            response.config = config
+            try {
+                // 对可能字符串不是json 的情况容错
+                if (typeof response.data === 'string') {
+                    response.data = JSON.parse(response.data)
+                }
+                // eslint-disable-next-line no-empty
+            } catch (e) {
+            }
+            settle(resolve, reject, response)
+        }
+    }
+    let requestTask
+    if (config.method === 'UPLOAD') {
+        delete _config.header['content-type']
+        delete _config.header['Content-Type']
+        const otherConfig = {
+        // #ifdef MP-ALIPAY
+            fileType: config.fileType,
+            // #endif
+            filePath: config.filePath,
+            name: config.name
+        }
+        const optionalKeys = [
+        // #ifdef APP-PLUS || H5
+            'files',
+            // #endif
+            // #ifdef H5
+            'file',
+            // #endif
+            // #ifdef H5 || APP-PLUS
+            'timeout',
+            // #endif
+            'formData'
+        ]
+        requestTask = uni.uploadFile({ ..._config, ...otherConfig, ...mergeKeys(optionalKeys, config) })
+    } else if (config.method === 'DOWNLOAD') {
+        // #ifdef H5 || APP-PLUS
+        if (!isUndefined(config.timeout)) {
+            _config.timeout = config.timeout
+        }
+        // #endif
+        requestTask = uni.downloadFile(_config)
+    } else {
+        const optionalKeys = [
+            'data',
+            'method',
+            // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+            'timeout',
+            // #endif
+            'dataType',
+            // #ifndef MP-ALIPAY
+            'responseType',
+            // #endif
+            // #ifdef APP-PLUS
+            'sslVerify',
+            // #endif
+            // #ifdef H5
+            'withCredentials',
+            // #endif
+            // #ifdef APP-PLUS
+            'firstIpv4'
+        // #endif
+        ]
+        requestTask = uni.request({ ..._config, ...mergeKeys(optionalKeys, config) })
+    }
+    if (config.getTask) {
+        config.getTask(requestTask, config)
+    }
+})

+ 50 - 0
uni_modules/uv-ui-tools/libs/luch-request/core/InterceptorManager.js

@@ -0,0 +1,50 @@
+'use strict'
+
+function InterceptorManager() {
+    this.handlers = []
+}
+
+/**
+ * Add a new interceptor to the stack
+ *
+ * @param {Function} fulfilled The function to handle `then` for a `Promise`
+ * @param {Function} rejected The function to handle `reject` for a `Promise`
+ *
+ * @return {Number} An ID used to remove interceptor later
+ */
+InterceptorManager.prototype.use = function use(fulfilled, rejected) {
+    this.handlers.push({
+        fulfilled,
+        rejected
+    })
+    return this.handlers.length - 1
+}
+
+/**
+ * Remove an interceptor from the stack
+ *
+ * @param {Number} id The ID that was returned by `use`
+ */
+InterceptorManager.prototype.eject = function eject(id) {
+    if (this.handlers[id]) {
+        this.handlers[id] = null
+    }
+}
+
+/**
+ * Iterate over all the registered interceptors
+ *
+ * This method is particularly useful for skipping over any
+ * interceptors that may have become `null` calling `eject`.
+ *
+ * @param {Function} fn The function to call for each interceptor
+ */
+InterceptorManager.prototype.forEach = function forEach(fn) {
+    this.handlers.forEach((h) => {
+        if (h !== null) {
+            fn(h)
+        }
+    })
+}
+
+export default InterceptorManager

+ 198 - 0
uni_modules/uv-ui-tools/libs/luch-request/core/Request.js

@@ -0,0 +1,198 @@
+/**
+ * @Class Request
+ * @description luch-request http请求插件
+ * @version 3.0.7
+ * @Author lu-ch
+ * @Date 2021-09-04
+ * @Email webwork.s@qq.com
+ * 文档: https://www.quanzhan.co/luch-request/
+ * github: https://github.com/lei-mu/luch-request
+ * DCloud: http://ext.dcloud.net.cn/plugin?id=392
+ * HBuilderX: beat-3.0.4 alpha-3.0.4
+ */
+
+import dispatchRequest from './dispatchRequest'
+import InterceptorManager from './InterceptorManager'
+import mergeConfig from './mergeConfig'
+import defaults from './defaults'
+import { isPlainObject } from '../utils'
+import clone from '../utils/clone'
+
+export default class Request {
+    /**
+   * @param {Object} arg - 全局配置
+   * @param {String} arg.baseURL - 全局根路径
+   * @param {Object} arg.header - 全局header
+   * @param {String} arg.method = [GET|POST|PUT|DELETE|CONNECT|HEAD|OPTIONS|TRACE] - 全局默认请求方式
+   * @param {String} arg.dataType = [json] - 全局默认的dataType
+   * @param {String} arg.responseType = [text|arraybuffer] - 全局默认的responseType。支付宝小程序不支持
+   * @param {Object} arg.custom - 全局默认的自定义参数
+   * @param {Number} arg.timeout - 全局默认的超时时间,单位 ms。默认60000。H5(HBuilderX 2.9.9+)、APP(HBuilderX 2.9.9+)、微信小程序(2.10.0)、支付宝小程序
+   * @param {Boolean} arg.sslVerify - 全局默认的是否验证 ssl 证书。默认true.仅App安卓端支持(HBuilderX 2.3.3+)
+   * @param {Boolean} arg.withCredentials - 全局默认的跨域请求时是否携带凭证(cookies)。默认false。仅H5支持(HBuilderX 2.6.15+)
+   * @param {Boolean} arg.firstIpv4 - 全DNS解析时优先使用ipv4。默认false。仅 App-Android 支持 (HBuilderX 2.8.0+)
+   * @param {Function(statusCode):Boolean} arg.validateStatus - 全局默认的自定义验证器。默认statusCode >= 200 && statusCode < 300
+   */
+    constructor(arg = {}) {
+        if (!isPlainObject(arg)) {
+            arg = {}
+            console.warn('设置全局参数必须接收一个Object')
+        }
+        this.config = clone({ ...defaults, ...arg })
+        this.interceptors = {
+            request: new InterceptorManager(),
+            response: new InterceptorManager()
+        }
+    }
+
+    /**
+   * @Function
+   * @param {Request~setConfigCallback} f - 设置全局默认配置
+   */
+    setConfig(f) {
+        this.config = f(this.config)
+    }
+
+    middleware(config) {
+        config = mergeConfig(this.config, config)
+        const chain = [dispatchRequest, undefined]
+        let promise = Promise.resolve(config)
+
+        this.interceptors.request.forEach((interceptor) => {
+            chain.unshift(interceptor.fulfilled, interceptor.rejected)
+        })
+
+        this.interceptors.response.forEach((interceptor) => {
+            chain.push(interceptor.fulfilled, interceptor.rejected)
+        })
+
+        while (chain.length) {
+            promise = promise.then(chain.shift(), chain.shift())
+        }
+
+        return promise
+    }
+
+    /**
+   * @Function
+   * @param {Object} config - 请求配置项
+   * @prop {String} options.url - 请求路径
+   * @prop {Object} options.data - 请求参数
+   * @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
+   * @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse
+   * @prop {Object} [options.header = config.header] - 请求header
+   * @prop {Object} [options.method = config.method] - 请求方法
+   * @returns {Promise<unknown>}
+   */
+    request(config = {}) {
+        return this.middleware(config)
+    }
+
+    get(url, options = {}) {
+        return this.middleware({
+            url,
+            method: 'GET',
+            ...options
+        })
+    }
+
+    post(url, data, options = {}) {
+        return this.middleware({
+            url,
+            data,
+            method: 'POST',
+            ...options
+        })
+    }
+
+    // #ifndef MP-ALIPAY
+    put(url, data, options = {}) {
+        return this.middleware({
+            url,
+            data,
+            method: 'PUT',
+            ...options
+        })
+    }
+
+    // #endif
+
+    // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+    delete(url, data, options = {}) {
+        return this.middleware({
+            url,
+            data,
+            method: 'DELETE',
+            ...options
+        })
+    }
+
+    // #endif
+
+    // #ifdef H5 || MP-WEIXIN
+    connect(url, data, options = {}) {
+        return this.middleware({
+            url,
+            data,
+            method: 'CONNECT',
+            ...options
+        })
+    }
+
+    // #endif
+
+    // #ifdef  H5 || MP-WEIXIN || MP-BAIDU
+    head(url, data, options = {}) {
+        return this.middleware({
+            url,
+            data,
+            method: 'HEAD',
+            ...options
+        })
+    }
+
+    // #endif
+
+    // #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
+    options(url, data, options = {}) {
+        return this.middleware({
+            url,
+            data,
+            method: 'OPTIONS',
+            ...options
+        })
+    }
+
+    // #endif
+
+    // #ifdef H5 || MP-WEIXIN
+    trace(url, data, options = {}) {
+        return this.middleware({
+            url,
+            data,
+            method: 'TRACE',
+            ...options
+        })
+    }
+
+    // #endif
+
+    upload(url, config = {}) {
+        config.url = url
+        config.method = 'UPLOAD'
+        return this.middleware(config)
+    }
+
+    download(url, config = {}) {
+        config.url = url
+        config.method = 'DOWNLOAD'
+        return this.middleware(config)
+    }
+}
+
+/**
+ * setConfig回调
+ * @return {Object} - 返回操作后的config
+ * @callback Request~setConfigCallback
+ * @param {Object} config - 全局默认config
+ */

+ 20 - 0
uni_modules/uv-ui-tools/libs/luch-request/core/buildFullPath.js

@@ -0,0 +1,20 @@
+'use strict'
+
+import isAbsoluteURL from '../helpers/isAbsoluteURL'
+import combineURLs from '../helpers/combineURLs'
+
+/**
+ * Creates a new URL by combining the baseURL with the requestedURL,
+ * only when the requestedURL is not already an absolute URL.
+ * If the requestURL is absolute, this function returns the requestedURL untouched.
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} requestedURL Absolute or relative URL to combine
+ * @returns {string} The combined full path
+ */
+export default function buildFullPath(baseURL, requestedURL) {
+    if (baseURL && !isAbsoluteURL(requestedURL)) {
+        return combineURLs(baseURL, requestedURL)
+    }
+    return requestedURL
+}

+ 29 - 0
uni_modules/uv-ui-tools/libs/luch-request/core/defaults.js

@@ -0,0 +1,29 @@
+/**
+ * 默认的全局配置
+ */
+
+export default {
+    baseURL: '',
+    header: {},
+    method: 'GET',
+    dataType: 'json',
+    // #ifndef MP-ALIPAY
+    responseType: 'text',
+    // #endif
+    custom: {},
+    // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+    timeout: 60000,
+    // #endif
+    // #ifdef APP-PLUS
+    sslVerify: true,
+    // #endif
+    // #ifdef H5
+    withCredentials: false,
+    // #endif
+    // #ifdef APP-PLUS
+    firstIpv4: false,
+    // #endif
+    validateStatus: function validateStatus(status) {
+        return status >= 200 && status < 300
+    }
+}

+ 3 - 0
uni_modules/uv-ui-tools/libs/luch-request/core/dispatchRequest.js

@@ -0,0 +1,3 @@
+import adapter from '../adapters/index'
+
+export default (config) => adapter(config)

+ 103 - 0
uni_modules/uv-ui-tools/libs/luch-request/core/mergeConfig.js

@@ -0,0 +1,103 @@
+import { deepMerge, isUndefined } from '../utils'
+
+/**
+ * 合并局部配置优先的配置,如果局部有该配置项则用局部,如果全局有该配置项则用全局
+ * @param {Array} keys - 配置项
+ * @param {Object} globalsConfig - 当前的全局配置
+ * @param {Object} config2 - 局部配置
+ * @return {{}}
+ */
+const mergeKeys = (keys, globalsConfig, config2) => {
+    const config = {}
+    keys.forEach((prop) => {
+        if (!isUndefined(config2[prop])) {
+            config[prop] = config2[prop]
+        } else if (!isUndefined(globalsConfig[prop])) {
+            config[prop] = globalsConfig[prop]
+        }
+    })
+    return config
+}
+/**
+ *
+ * @param globalsConfig - 当前实例的全局配置
+ * @param config2 - 当前的局部配置
+ * @return - 合并后的配置
+ */
+export default (globalsConfig, config2 = {}) => {
+    const method = config2.method || globalsConfig.method || 'GET'
+    let config = {
+        baseURL: globalsConfig.baseURL || '',
+        method,
+        url: config2.url || '',
+        params: config2.params || {},
+        custom: { ...(globalsConfig.custom || {}), ...(config2.custom || {}) },
+        header: deepMerge(globalsConfig.header || {}, config2.header || {})
+    }
+    const defaultToConfig2Keys = ['getTask', 'validateStatus']
+    config = { ...config, ...mergeKeys(defaultToConfig2Keys, globalsConfig, config2) }
+
+    // eslint-disable-next-line no-empty
+    if (method === 'DOWNLOAD') {
+    // #ifdef H5 || APP-PLUS
+        if (!isUndefined(config2.timeout)) {
+            config.timeout = config2.timeout
+        } else if (!isUndefined(globalsConfig.timeout)) {
+            config.timeout = globalsConfig.timeout
+        }
+    // #endif
+    } else if (method === 'UPLOAD') {
+        delete config.header['content-type']
+        delete config.header['Content-Type']
+        const uploadKeys = [
+            // #ifdef APP-PLUS || H5
+            'files',
+            // #endif
+            // #ifdef MP-ALIPAY
+            'fileType',
+            // #endif
+            // #ifdef H5
+            'file',
+            // #endif
+            'filePath',
+            'name',
+            // #ifdef H5 || APP-PLUS
+            'timeout',
+            // #endif
+            'formData'
+        ]
+        uploadKeys.forEach((prop) => {
+            if (!isUndefined(config2[prop])) {
+                config[prop] = config2[prop]
+            }
+        })
+        // #ifdef H5 || APP-PLUS
+        if (isUndefined(config.timeout) && !isUndefined(globalsConfig.timeout)) {
+            config.timeout = globalsConfig.timeout
+        }
+    // #endif
+    } else {
+        const defaultsKeys = [
+            'data',
+            // #ifdef H5 || APP-PLUS || MP-ALIPAY || MP-WEIXIN
+            'timeout',
+            // #endif
+            'dataType',
+            // #ifndef MP-ALIPAY
+            'responseType',
+            // #endif
+            // #ifdef APP-PLUS
+            'sslVerify',
+            // #endif
+            // #ifdef H5
+            'withCredentials',
+            // #endif
+            // #ifdef APP-PLUS
+            'firstIpv4'
+            // #endif
+        ]
+        config = { ...config, ...mergeKeys(defaultsKeys, globalsConfig, config2) }
+    }
+
+    return config
+}

+ 16 - 0
uni_modules/uv-ui-tools/libs/luch-request/core/settle.js

@@ -0,0 +1,16 @@
+/**
+ * Resolve or reject a Promise based on response status.
+ *
+ * @param {Function} resolve A function that resolves the promise.
+ * @param {Function} reject A function that rejects the promise.
+ * @param {object} response The response.
+ */
+export default function settle(resolve, reject, response) {
+    const { validateStatus } = response.config
+    const status = response.statusCode
+    if (status && (!validateStatus || validateStatus(status))) {
+        resolve(response)
+    } else {
+        reject(response)
+    }
+}

+ 69 - 0
uni_modules/uv-ui-tools/libs/luch-request/helpers/buildURL.js

@@ -0,0 +1,69 @@
+'use strict'
+
+import * as utils from '../utils'
+
+function encode(val) {
+    return encodeURIComponent(val)
+        .replace(/%40/gi, '@')
+        .replace(/%3A/gi, ':')
+        .replace(/%24/g, '$')
+        .replace(/%2C/gi, ',')
+        .replace(/%20/g, '+')
+        .replace(/%5B/gi, '[')
+        .replace(/%5D/gi, ']')
+}
+
+/**
+ * Build a URL by appending params to the end
+ *
+ * @param {string} url The base of the url (e.g., http://www.google.com)
+ * @param {object} [params] The params to be appended
+ * @returns {string} The formatted url
+ */
+export default function buildURL(url, params) {
+    /* eslint no-param-reassign:0 */
+    if (!params) {
+        return url
+    }
+
+    let serializedParams
+    if (utils.isURLSearchParams(params)) {
+        serializedParams = params.toString()
+    } else {
+        const parts = []
+
+        utils.forEach(params, (val, key) => {
+            if (val === null || typeof val === 'undefined') {
+                return
+            }
+
+            if (utils.isArray(val)) {
+                key = `${key}[]`
+            } else {
+                val = [val]
+            }
+
+            utils.forEach(val, (v) => {
+                if (utils.isDate(v)) {
+                    v = v.toISOString()
+                } else if (utils.isObject(v)) {
+                    v = JSON.stringify(v)
+                }
+                parts.push(`${encode(key)}=${encode(v)}`)
+            })
+        })
+
+        serializedParams = parts.join('&')
+    }
+
+    if (serializedParams) {
+        const hashmarkIndex = url.indexOf('#')
+        if (hashmarkIndex !== -1) {
+            url = url.slice(0, hashmarkIndex)
+        }
+
+        url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams
+    }
+
+    return url
+}

+ 14 - 0
uni_modules/uv-ui-tools/libs/luch-request/helpers/combineURLs.js

@@ -0,0 +1,14 @@
+'use strict'
+
+/**
+ * Creates a new URL by combining the specified URLs
+ *
+ * @param {string} baseURL The base URL
+ * @param {string} relativeURL The relative URL
+ * @returns {string} The combined URL
+ */
+export default function combineURLs(baseURL, relativeURL) {
+    return relativeURL
+        ? `${baseURL.replace(/\/+$/, '')}/${relativeURL.replace(/^\/+/, '')}`
+        : baseURL
+}

+ 14 - 0
uni_modules/uv-ui-tools/libs/luch-request/helpers/isAbsoluteURL.js

@@ -0,0 +1,14 @@
+'use strict'
+
+/**
+ * Determines whether the specified URL is absolute
+ *
+ * @param {string} url The URL to test
+ * @returns {boolean} True if the specified URL is absolute, otherwise false
+ */
+export default function isAbsoluteURL(url) {
+    // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL).
+    // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed
+    // by any combination of letters, digits, plus, period, or hyphen.
+    return /^([a-z][a-z\d+\-.]*:)?\/\//i.test(url)
+}

+ 116 - 0
uni_modules/uv-ui-tools/libs/luch-request/index.d.ts

@@ -0,0 +1,116 @@
+type AnyObject = Record<string | number | symbol, any>
+type HttpPromise<T> = Promise<HttpResponse<T>>;
+type Tasks = UniApp.RequestTask | UniApp.UploadTask | UniApp.DownloadTask
+export interface RequestTask {
+  abort: () => void;
+  offHeadersReceived: () => void;
+  onHeadersReceived: () => void;
+}
+export interface HttpRequestConfig<T = Tasks> {
+  /** 请求基地址 */
+  baseURL?: string;
+  /** 请求服务器接口地址 */
+  url?: string;
+
+  /** 请求查询参数,自动拼接为查询字符串 */
+  params?: AnyObject;
+  /** 请求体参数 */
+  data?: AnyObject;
+
+  /** 文件对应的 key */
+  name?: string;
+  /** HTTP 请求中其他额外的 form data */
+  formData?: AnyObject;
+  /** 要上传文件资源的路径。 */
+  filePath?: string;
+  /** 需要上传的文件列表。使用 files 时,filePath 和 name 不生效,App、H5( 2.6.15+) */
+  files?: Array<{
+    name?: string;
+    file?: File;
+    uri: string;
+  }>;
+  /** 要上传的文件对象,仅H5(2.6.15+)支持 */
+  file?: File;
+
+  /** 请求头信息 */
+  header?: AnyObject;
+  /** 请求方式 */
+  method?: "GET" | "POST" | "PUT" | "DELETE" | "CONNECT" | "HEAD" | "OPTIONS" | "TRACE" | "UPLOAD" | "DOWNLOAD";
+  /** 如果设为 json,会尝试对返回的数据做一次 JSON.parse */
+  dataType?: string;
+  /** 设置响应的数据类型,支付宝小程序不支持 */
+  responseType?: "text" | "arraybuffer";
+  /** 自定义参数 */
+  custom?: AnyObject;
+  /** 超时时间,仅微信小程序(2.10.0)、支付宝小程序支持 */
+  timeout?: number;
+  /** DNS解析时优先使用ipv4,仅 App-Android 支持 (HBuilderX 2.8.0+) */
+  firstIpv4?: boolean;
+  /** 验证 ssl 证书 仅5+App安卓端支持(HBuilderX 2.3.3+) */
+  sslVerify?: boolean;
+  /** 跨域请求时是否携带凭证(cookies)仅H5支持(HBuilderX 2.6.15+) */
+  withCredentials?: boolean;
+
+  /** 返回当前请求的task, options。请勿在此处修改options。 */
+  getTask?: (task: T, options: HttpRequestConfig<T>) => void;
+  /**  全局自定义验证器 */
+  validateStatus?: (statusCode: number) => boolean | void;
+}
+export interface HttpResponse<T = any> {
+  config: HttpRequestConfig;
+  statusCode: number;
+  cookies: Array<string>;
+  data: T;
+  errMsg: string;
+  header: AnyObject;
+}
+export interface HttpUploadResponse<T = any> {
+  config: HttpRequestConfig;
+  statusCode: number;
+  data: T;
+  errMsg: string;
+}
+export interface HttpDownloadResponse extends HttpResponse {
+  tempFilePath: string;
+}
+export interface HttpError {
+  config: HttpRequestConfig;
+  statusCode?: number;
+  cookies?: Array<string>;
+  data?: any;
+  errMsg: string;
+  header?: AnyObject;
+}
+export interface HttpInterceptorManager<V, E = V> {
+  use(
+    onFulfilled?: (config: V) => Promise<V> | V,
+    onRejected?: (config: E) => Promise<E> | E
+  ): void;
+  eject(id: number): void;
+}
+export abstract class HttpRequestAbstract {
+  constructor(config?: HttpRequestConfig);
+  config: HttpRequestConfig;
+  interceptors: {
+    request: HttpInterceptorManager<HttpRequestConfig, HttpRequestConfig>;
+    response: HttpInterceptorManager<HttpResponse, HttpError>;
+  }
+  middleware<T = any>(config: HttpRequestConfig): HttpPromise<T>;
+  request<T = any>(config: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  get<T = any>(url: string, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  upload<T = any>(url: string, config?: HttpRequestConfig<UniApp.UploadTask>): HttpPromise<T>;
+  delete<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  head<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  post<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  put<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  connect<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  options<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+  trace<T = any>(url: string, data?: AnyObject, config?: HttpRequestConfig<UniApp.RequestTask>): HttpPromise<T>;
+
+  download(url: string, config?: HttpRequestConfig<UniApp.DownloadTask>): Promise<HttpDownloadResponse>;
+
+  setConfig(onSend: (config: HttpRequestConfig) => HttpRequestConfig): void;
+}
+
+declare class HttpRequest extends HttpRequestAbstract { }
+export default HttpRequest;

+ 3 - 0
uni_modules/uv-ui-tools/libs/luch-request/index.js

@@ -0,0 +1,3 @@
+import Request from './core/Request'
+
+export default Request

+ 131 - 0
uni_modules/uv-ui-tools/libs/luch-request/utils.js

@@ -0,0 +1,131 @@
+'use strict'
+
+// utils is a library of generic helper functions non-specific to axios
+
+const { toString } = Object.prototype
+
+/**
+ * Determine if a value is an Array
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Array, otherwise false
+ */
+export function isArray(val) {
+    return toString.call(val) === '[object Array]'
+}
+
+/**
+ * Determine if a value is an Object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Object, otherwise false
+ */
+export function isObject(val) {
+    return val !== null && typeof val === 'object'
+}
+
+/**
+ * Determine if a value is a Date
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a Date, otherwise false
+ */
+export function isDate(val) {
+    return toString.call(val) === '[object Date]'
+}
+
+/**
+ * Determine if a value is a URLSearchParams object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a URLSearchParams object, otherwise false
+ */
+export function isURLSearchParams(val) {
+    return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams
+}
+
+/**
+ * Iterate over an Array or an Object invoking a function for each item.
+ *
+ * If `obj` is an Array callback will be called passing
+ * the value, index, and complete array for each item.
+ *
+ * If 'obj' is an Object callback will be called passing
+ * the value, key, and complete object for each property.
+ *
+ * @param {Object|Array} obj The object to iterate
+ * @param {Function} fn The callback to invoke for each item
+ */
+export function forEach(obj, fn) {
+    // Don't bother if no value provided
+    if (obj === null || typeof obj === 'undefined') {
+        return
+    }
+
+    // Force an array if not already something iterable
+    if (typeof obj !== 'object') {
+    /* eslint no-param-reassign:0 */
+        obj = [obj]
+    }
+
+    if (isArray(obj)) {
+    // Iterate over array values
+        for (let i = 0, l = obj.length; i < l; i++) {
+            fn.call(null, obj[i], i, obj)
+        }
+    } else {
+    // Iterate over object keys
+        for (const key in obj) {
+            if (Object.prototype.hasOwnProperty.call(obj, key)) {
+                fn.call(null, obj[key], key, obj)
+            }
+        }
+    }
+}
+
+/**
+ * 是否为boolean 值
+ * @param val
+ * @returns {boolean}
+ */
+export function isBoolean(val) {
+    return typeof val === 'boolean'
+}
+
+/**
+ * 是否为真正的对象{} new Object
+ * @param {any} obj - 检测的对象
+ * @returns {boolean}
+ */
+export function isPlainObject(obj) {
+    return Object.prototype.toString.call(obj) === '[object Object]'
+}
+
+/**
+ * Function equal to merge with the difference being that no reference
+ * to original objects is kept.
+ *
+ * @see merge
+ * @param {Object} obj1 Object to merge
+ * @returns {Object} Result of all merge properties
+ */
+export function deepMerge(/* obj1, obj2, obj3, ... */) {
+    const result = {}
+    function assignValue(val, key) {
+        if (typeof result[key] === 'object' && typeof val === 'object') {
+            result[key] = deepMerge(result[key], val)
+        } else if (typeof val === 'object') {
+            result[key] = deepMerge({}, val)
+        } else {
+            result[key] = val
+        }
+    }
+    for (let i = 0, l = arguments.length; i < l; i++) {
+        forEach(arguments[i], assignValue)
+    }
+    return result
+}
+
+export function isUndefined(val) {
+    return typeof val === 'undefined'
+}

+ 264 - 0
uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js

@@ -0,0 +1,264 @@
+/* eslint-disable */
+var clone = (function() {
+  'use strict';
+
+  function _instanceof(obj, type) {
+    return type != null && obj instanceof type;
+  }
+
+  var nativeMap;
+  try {
+    nativeMap = Map;
+  } catch(_) {
+    // maybe a reference error because no `Map`. Give it a dummy value that no
+    // value will ever be an instanceof.
+    nativeMap = function() {};
+  }
+
+  var nativeSet;
+  try {
+    nativeSet = Set;
+  } catch(_) {
+    nativeSet = function() {};
+  }
+
+  var nativePromise;
+  try {
+    nativePromise = Promise;
+  } catch(_) {
+    nativePromise = function() {};
+  }
+
+  /**
+   * Clones (copies) an Object using deep copying.
+   *
+   * This function supports circular references by default, but if you are certain
+   * there are no circular references in your object, you can save some CPU time
+   * by calling clone(obj, false).
+   *
+   * Caution: if `circular` is false and `parent` contains circular references,
+   * your program may enter an infinite loop and crash.
+   *
+   * @param `parent` - the object to be cloned
+   * @param `circular` - set to true if the object to be cloned may contain
+   *    circular references. (optional - true by default)
+   * @param `depth` - set to a number if the object is only to be cloned to
+   *    a particular depth. (optional - defaults to Infinity)
+   * @param `prototype` - sets the prototype to be used when cloning an object.
+   *    (optional - defaults to parent prototype).
+   * @param `includeNonEnumerable` - set to true if the non-enumerable properties
+   *    should be cloned as well. Non-enumerable properties on the prototype
+   *    chain will be ignored. (optional - false by default)
+   */
+  function clone(parent, circular, depth, prototype, includeNonEnumerable) {
+    if (typeof circular === 'object') {
+      depth = circular.depth;
+      prototype = circular.prototype;
+      includeNonEnumerable = circular.includeNonEnumerable;
+      circular = circular.circular;
+    }
+    // maintain two arrays for circular references, where corresponding parents
+    // and children have the same index
+    var allParents = [];
+    var allChildren = [];
+
+    var useBuffer = typeof Buffer != 'undefined';
+
+    if (typeof circular == 'undefined')
+      circular = true;
+
+    if (typeof depth == 'undefined')
+      depth = Infinity;
+
+    // recurse this function so we don't reset allParents and allChildren
+    function _clone(parent, depth) {
+      // cloning null always returns null
+      if (parent === null)
+        return null;
+
+      if (depth === 0)
+        return parent;
+
+      var child;
+      var proto;
+      if (typeof parent != 'object') {
+        return parent;
+      }
+
+      if (_instanceof(parent, nativeMap)) {
+        child = new nativeMap();
+      } else if (_instanceof(parent, nativeSet)) {
+        child = new nativeSet();
+      } else if (_instanceof(parent, nativePromise)) {
+        child = new nativePromise(function (resolve, reject) {
+          parent.then(function(value) {
+            resolve(_clone(value, depth - 1));
+          }, function(err) {
+            reject(_clone(err, depth - 1));
+          });
+        });
+      } else if (clone.__isArray(parent)) {
+        child = [];
+      } else if (clone.__isRegExp(parent)) {
+        child = new RegExp(parent.source, __getRegExpFlags(parent));
+        if (parent.lastIndex) child.lastIndex = parent.lastIndex;
+      } else if (clone.__isDate(parent)) {
+        child = new Date(parent.getTime());
+      } else if (useBuffer && Buffer.isBuffer(parent)) {
+        if (Buffer.from) {
+          // Node.js >= 5.10.0
+          child = Buffer.from(parent);
+        } else {
+          // Older Node.js versions
+          child = new Buffer(parent.length);
+          parent.copy(child);
+        }
+        return child;
+      } else if (_instanceof(parent, Error)) {
+        child = Object.create(parent);
+      } else {
+        if (typeof prototype == 'undefined') {
+          proto = Object.getPrototypeOf(parent);
+          child = Object.create(proto);
+        }
+        else {
+          child = Object.create(prototype);
+          proto = prototype;
+        }
+      }
+
+      if (circular) {
+        var index = allParents.indexOf(parent);
+
+        if (index != -1) {
+          return allChildren[index];
+        }
+        allParents.push(parent);
+        allChildren.push(child);
+      }
+
+      if (_instanceof(parent, nativeMap)) {
+        parent.forEach(function(value, key) {
+          var keyChild = _clone(key, depth - 1);
+          var valueChild = _clone(value, depth - 1);
+          child.set(keyChild, valueChild);
+        });
+      }
+      if (_instanceof(parent, nativeSet)) {
+        parent.forEach(function(value) {
+          var entryChild = _clone(value, depth - 1);
+          child.add(entryChild);
+        });
+      }
+
+      for (var i in parent) {
+        var attrs = Object.getOwnPropertyDescriptor(parent, i);
+        if (attrs) {
+          child[i] = _clone(parent[i], depth - 1);
+        }
+
+        try {
+          var objProperty = Object.getOwnPropertyDescriptor(parent, i);
+          if (objProperty.set === 'undefined') {
+            // no setter defined. Skip cloning this property
+            continue;
+          }
+          child[i] = _clone(parent[i], depth - 1);
+        } catch(e){
+          if (e instanceof TypeError) {
+            // when in strict mode, TypeError will be thrown if child[i] property only has a getter
+            // we can't do anything about this, other than inform the user that this property cannot be set.
+            continue
+          } else if (e instanceof ReferenceError) {
+            //this may happen in non strict mode
+            continue
+          }
+        }
+
+      }
+
+      if (Object.getOwnPropertySymbols) {
+        var symbols = Object.getOwnPropertySymbols(parent);
+        for (var i = 0; i < symbols.length; i++) {
+          // Don't need to worry about cloning a symbol because it is a primitive,
+          // like a number or string.
+          var symbol = symbols[i];
+          var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
+          if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
+            continue;
+          }
+          child[symbol] = _clone(parent[symbol], depth - 1);
+          Object.defineProperty(child, symbol, descriptor);
+        }
+      }
+
+      if (includeNonEnumerable) {
+        var allPropertyNames = Object.getOwnPropertyNames(parent);
+        for (var i = 0; i < allPropertyNames.length; i++) {
+          var propertyName = allPropertyNames[i];
+          var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
+          if (descriptor && descriptor.enumerable) {
+            continue;
+          }
+          child[propertyName] = _clone(parent[propertyName], depth - 1);
+          Object.defineProperty(child, propertyName, descriptor);
+        }
+      }
+
+      return child;
+    }
+
+    return _clone(parent, depth);
+  }
+
+  /**
+   * Simple flat clone using prototype, accepts only objects, usefull for property
+   * override on FLAT configuration object (no nested props).
+   *
+   * USE WITH CAUTION! This may not behave as you wish if you do not know how this
+   * works.
+   */
+  clone.clonePrototype = function clonePrototype(parent) {
+    if (parent === null)
+      return null;
+
+    var c = function () {};
+    c.prototype = parent;
+    return new c();
+  };
+
+// private utility functions
+
+  function __objToStr(o) {
+    return Object.prototype.toString.call(o);
+  }
+  clone.__objToStr = __objToStr;
+
+  function __isDate(o) {
+    return typeof o === 'object' && __objToStr(o) === '[object Date]';
+  }
+  clone.__isDate = __isDate;
+
+  function __isArray(o) {
+    return typeof o === 'object' && __objToStr(o) === '[object Array]';
+  }
+  clone.__isArray = __isArray;
+
+  function __isRegExp(o) {
+    return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
+  }
+  clone.__isRegExp = __isRegExp;
+
+  function __getRegExpFlags(re) {
+    var flags = '';
+    if (re.global) flags += 'g';
+    if (re.ignoreCase) flags += 'i';
+    if (re.multiline) flags += 'm';
+    return flags;
+  }
+  clone.__getRegExpFlags = __getRegExpFlags;
+
+  return clone;
+})();
+
+export default clone

+ 13 - 0
uni_modules/uv-ui-tools/libs/mixin/button.js

@@ -0,0 +1,13 @@
+export default {
+    props: {
+        lang: String,
+        sessionFrom: String,
+        sendMessageTitle: String,
+        sendMessagePath: String,
+        sendMessageImg: String,
+        showMessageCard: Boolean,
+        appParameter: String,
+        formType: String,
+        openType: String
+    }
+}

+ 152 - 0
uni_modules/uv-ui-tools/libs/mixin/mixin.js

@@ -0,0 +1,152 @@
+import * as index from '../function/index.js';
+import * as test from '../function/test.js';
+export default {
+	// 定义每个组件都可能需要用到的外部样式以及类名
+	props: {
+		// 每个组件都有的父组件传递的样式,可以为字符串或者对象形式
+		customStyle: {
+			type: [Object, String],
+			default: () => ({})
+		},
+		customClass: {
+			type: String,
+			default: ''
+		},
+		// 跳转的页面路径
+		url: {
+			type: String,
+			default: ''
+		},
+		// 页面跳转的类型
+		linkType: {
+			type: String,
+			default: 'navigateTo'
+		}
+	},
+	data() {
+		return {}
+	},
+	onLoad() {
+		// getRect挂载到$uv上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出
+		this.$uv.getRect = this.$uvGetRect
+	},
+	created() {
+		// 组件当中,只有created声明周期,为了能在组件使用,故也在created中将方法挂载到$uv
+		this.$uv.getRect = this.$uvGetRect
+	},
+	computed: {
+		$uv() {
+			return {
+				...index,
+				test
+			}
+		},
+		/**
+		 * 生成bem规则类名
+		 * 由于微信小程序,H5,nvue之间绑定class的差异,无法通过:class="[bem()]"的形式进行同用
+		 * 故采用如下折中做法,最后返回的是数组(一般平台)或字符串(支付宝和字节跳动平台),类似['a', 'b', 'c']或'a b c'的形式
+		 * @param {String} name 组件名称
+		 * @param {Array} fixed 一直会存在的类名
+		 * @param {Array} change 会根据变量值为true或者false而出现或者隐藏的类名
+		 * @returns {Array|string}
+		 */
+		bem() {
+			return function(name, fixed, change) {
+				// 类名前缀
+				const prefix = `uv-${name}--`
+				const classes = {}
+				if (fixed) {
+					fixed.map((item) => {
+						// 这里的类名,会一直存在
+						classes[prefix + this[item]] = true
+					})
+				}
+				if (change) {
+					change.map((item) => {
+						// 这里的类名,会根据this[item]的值为true或者false,而进行添加或者移除某一个类
+						this[item] ? (classes[prefix + item] = this[item]) : (delete classes[prefix + item])
+					})
+				}
+				return Object.keys(classes)
+					// 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+					// #ifdef MP-ALIPAY || MP-TOUTIAO || MP-LARK || MP-BAIDU
+					.join(' ')
+				// #endif
+			}
+		}
+	},
+	methods: {
+		// 跳转某一个页面
+		openPage(urlKey = 'url') {
+			const url = this[urlKey]
+			if (url) {
+				// 执行类似uni.navigateTo的方法
+				uni[this.linkType]({
+					url
+				})
+			}
+		},
+		// 查询节点信息
+		// 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21)
+		// 解决办法为在组件根部再套一个没有任何作用的view元素
+		$uvGetRect(selector, all) {
+			return new Promise((resolve) => {
+				uni.createSelectorQuery()
+					.in(this)[all ? 'selectAll' : 'select'](selector)
+					.boundingClientRect((rect) => {
+						if (all && Array.isArray(rect) && rect.length) {
+							resolve(rect)
+						}
+						if (!all && rect) {
+							resolve(rect)
+						}
+					})
+					.exec()
+			})
+		},
+		getParentData(parentName = '') {
+			// 避免在created中去定义parent变量
+			if (!this.parent) this.parent = {}
+			// 这里的本质原理是,通过获取父组件实例(也即类似uv-radio的父组件uv-radio-group的this)
+			// 将父组件this中对应的参数,赋值给本组件(uv-radio的this)的parentData对象中对应的属性
+			// 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化
+			// 此处并不会自动更新子组件的数据,而是依赖父组件uv-radio-group去监听data的变化,手动调用更新子组件的方法去重新获取
+			this.parent = this.$uv.$parent.call(this, parentName)
+			if (this.parent.children) {
+				// 如果父组件的children不存在本组件的实例,才将本实例添加到父组件的children中
+				this.parent.children.indexOf(this) === -1 && this.parent.children.push(this)
+			}
+			if (this.parent && this.parentData) {
+				// 历遍parentData中的属性,将parent中的同名属性赋值给parentData
+				Object.keys(this.parentData).map((key) => {
+					this.parentData[key] = this.parent[key]
+				})
+			}
+		},
+		// 阻止事件冒泡
+		preventEvent(e) {
+			e && typeof(e.stopPropagation) === 'function' && e.stopPropagation()
+		},
+		// 空操作
+		noop(e) {
+			this.preventEvent(e)
+		}
+	},
+	onReachBottom() {
+		uni.$emit('uvOnReachBottom')
+	},
+	beforeDestroy() {
+		// 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
+		// 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
+		if (this.parent && test.array(this.parent.children)) {
+			// 组件销毁时,移除父组件中的children数组中对应的实例
+			const childrenList = this.parent.children
+			childrenList.map((child, index) => {
+				// 如果相等,则移除
+				if (child === this) {
+					childrenList.splice(index, 1)
+				}
+			})
+		}
+	}
+}

+ 8 - 0
uni_modules/uv-ui-tools/libs/mixin/mpMixin.js

@@ -0,0 +1,8 @@
+export default {
+    // #ifdef MP-WEIXIN
+    // 将自定义节点设置成虚拟的(去掉自定义组件包裹层),更加接近Vue组件的表现,能更好的使用flex属性
+    options: {
+        virtualHost: true
+    }
+    // #endif
+}

+ 13 - 0
uni_modules/uv-ui-tools/libs/mixin/mpShare.js

@@ -0,0 +1,13 @@
+export default {
+	onLoad() {
+	    // 设置默认的转发参数
+	    uni.$uv.mpShare = {
+	        title: '', // 默认为小程序名称
+	        path: '', // 默认为当前页面路径
+	        imageUrl: '' // 默认为当前页面的截图
+	    }
+	},
+	onShareAppMessage() {
+	    return uni.$uv.mpShare
+	}
+}

+ 25 - 0
uni_modules/uv-ui-tools/libs/mixin/openType.js

@@ -0,0 +1,25 @@
+export default {
+    props: {
+        openType: String
+    },
+    methods: {
+        onGetUserInfo(event) {
+            this.$emit('getuserinfo', event.detail)
+        },
+        onContact(event) {
+            this.$emit('contact', event.detail)
+        },
+        onGetPhoneNumber(event) {
+            this.$emit('getphonenumber', event.detail)
+        },
+        onError(event) {
+            this.$emit('error', event.detail)
+        },
+        onLaunchApp(event) {
+            this.$emit('launchapp', event.detail)
+        },
+        onOpenSetting(event) {
+            this.$emit('opensetting', event.detail)
+        }
+    }
+}

+ 59 - 0
uni_modules/uv-ui-tools/libs/mixin/touch.js

@@ -0,0 +1,59 @@
+const MIN_DISTANCE = 10
+
+function getDirection(x, y) {
+    if (x > y && x > MIN_DISTANCE) {
+        return 'horizontal'
+    }
+    if (y > x && y > MIN_DISTANCE) {
+        return 'vertical'
+    }
+    return ''
+}
+
+export default {
+    methods: {
+        getTouchPoint(e) {
+            if (!e) {
+                return {
+                    x: 0,
+                    y: 0
+                }
+            } if (e.touches && e.touches[0]) {
+                return {
+                    x: e.touches[0].pageX,
+                    y: e.touches[0].pageY
+                }
+            } if (e.changedTouches && e.changedTouches[0]) {
+                return {
+                    x: e.changedTouches[0].pageX,
+                    y: e.changedTouches[0].pageY
+                }
+            }
+            return {
+                x: e.clientX || 0,
+                y: e.clientY || 0
+            }
+        },
+        resetTouchStatus() {
+            this.direction = ''
+            this.deltaX = 0
+            this.deltaY = 0
+            this.offsetX = 0
+            this.offsetY = 0
+        },
+        touchStart(event) {
+            this.resetTouchStatus()
+            const touch = this.getTouchPoint(event)
+            this.startX = touch.x
+            this.startY = touch.y
+        },
+        touchMove(event) {
+            const touch = this.getTouchPoint(event)
+            this.deltaX = touch.x - this.startX
+            this.deltaY = touch.y - this.startY
+            this.offsetX = Math.abs(this.deltaX)
+            this.offsetY = Math.abs(this.deltaY)
+            this.direction =				this.direction || getDirection(this.offsetX, this.offsetY)
+        }
+    }
+}

+ 218 - 0
uni_modules/uv-ui-tools/libs/util/dayjs.js

@@ -0,0 +1,218 @@
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __commonJS = (cb, mod) => function __require() {
+  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
+};
+
+// C:/Users/LP/Downloads/uvui-plus_3.1.27_example/node_modules/dayjs/dayjs.min.js
+var require_dayjs_min = __commonJS({
+  "C:/Users/LP/Downloads/uvui-plus_3.1.27_example/node_modules/dayjs/dayjs.min.js"(exports, module) {
+    !function(t, e) {
+      "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e();
+    }(exports, function() {
+      "use strict";
+      var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", f = "month", h = "quarter", c = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) {
+        var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100;
+        return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]";
+      } }, m = function(t2, e2, n2) {
+        var r2 = String(t2);
+        return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2;
+      }, v = { s: m, z: function(t2) {
+        var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60;
+        return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0");
+      }, m: function t2(e2, n2) {
+        if (e2.date() < n2.date())
+          return -t2(n2, e2);
+        var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, f), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), f);
+        return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0);
+      }, a: function(t2) {
+        return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2);
+      }, p: function(t2) {
+        return { M: f, y: c, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: h }[t2] || String(t2 || "").toLowerCase().replace(/s$/, "");
+      }, u: function(t2) {
+        return void 0 === t2;
+      } }, g = "en", D = {};
+      D[g] = M;
+      var p = function(t2) {
+        return t2 instanceof _;
+      }, S = function t2(e2, n2, r2) {
+        var i2;
+        if (!e2)
+          return g;
+        if ("string" == typeof e2) {
+          var s2 = e2.toLowerCase();
+          D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2);
+          var u2 = e2.split("-");
+          if (!i2 && u2.length > 1)
+            return t2(u2[0]);
+        } else {
+          var a2 = e2.name;
+          D[a2] = e2, i2 = a2;
+        }
+        return !r2 && i2 && (g = i2), i2 || !r2 && g;
+      }, w = function(t2, e2) {
+        if (p(t2))
+          return t2.clone();
+        var n2 = "object" == typeof e2 ? e2 : {};
+        return n2.date = t2, n2.args = arguments, new _(n2);
+      }, O = v;
+      O.l = S, O.i = p, O.w = function(t2, e2) {
+        return w(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset });
+      };
+      var _ = function() {
+        function M2(t2) {
+          this.$L = S(t2.locale, null, true), this.parse(t2);
+        }
+        var m2 = M2.prototype;
+        return m2.parse = function(t2) {
+          this.$d = function(t3) {
+            var e2 = t3.date, n2 = t3.utc;
+            if (null === e2)
+              return new Date(NaN);
+            if (O.u(e2))
+              return new Date();
+            if (e2 instanceof Date)
+              return new Date(e2);
+            if ("string" == typeof e2 && !/Z$/i.test(e2)) {
+              var r2 = e2.match($);
+              if (r2) {
+                var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3);
+                return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2);
+              }
+            }
+            return new Date(e2);
+          }(t2), this.$x = t2.x || {}, this.init();
+        }, m2.init = function() {
+          var t2 = this.$d;
+          this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds();
+        }, m2.$utils = function() {
+          return O;
+        }, m2.isValid = function() {
+          return !(this.$d.toString() === l);
+        }, m2.isSame = function(t2, e2) {
+          var n2 = w(t2);
+          return this.startOf(e2) <= n2 && n2 <= this.endOf(e2);
+        }, m2.isAfter = function(t2, e2) {
+          return w(t2) < this.startOf(e2);
+        }, m2.isBefore = function(t2, e2) {
+          return this.endOf(e2) < w(t2);
+        }, m2.$g = function(t2, e2, n2) {
+          return O.u(t2) ? this[e2] : this.set(n2, t2);
+        }, m2.unix = function() {
+          return Math.floor(this.valueOf() / 1e3);
+        }, m2.valueOf = function() {
+          return this.$d.getTime();
+        }, m2.startOf = function(t2, e2) {
+          var n2 = this, r2 = !!O.u(e2) || e2, h2 = O.p(t2), l2 = function(t3, e3) {
+            var i2 = O.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2);
+            return r2 ? i2 : i2.endOf(a);
+          }, $2 = function(t3, e3) {
+            return O.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2);
+          }, y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : "");
+          switch (h2) {
+            case c:
+              return r2 ? l2(1, 0) : l2(31, 11);
+            case f:
+              return r2 ? l2(1, M3) : l2(0, M3 + 1);
+            case o:
+              var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2;
+              return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3);
+            case a:
+            case d:
+              return $2(v2 + "Hours", 0);
+            case u:
+              return $2(v2 + "Minutes", 1);
+            case s:
+              return $2(v2 + "Seconds", 2);
+            case i:
+              return $2(v2 + "Milliseconds", 3);
+            default:
+              return this.clone();
+          }
+        }, m2.endOf = function(t2) {
+          return this.startOf(t2, false);
+        }, m2.$set = function(t2, e2) {
+          var n2, o2 = O.p(t2), h2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = h2 + "Date", n2[d] = h2 + "Date", n2[f] = h2 + "Month", n2[c] = h2 + "FullYear", n2[u] = h2 + "Hours", n2[s] = h2 + "Minutes", n2[i] = h2 + "Seconds", n2[r] = h2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2;
+          if (o2 === f || o2 === c) {
+            var y2 = this.clone().set(d, 1);
+            y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d;
+          } else
+            l2 && this.$d[l2]($2);
+          return this.init(), this;
+        }, m2.set = function(t2, e2) {
+          return this.clone().$set(t2, e2);
+        }, m2.get = function(t2) {
+          return this[O.p(t2)]();
+        }, m2.add = function(r2, h2) {
+          var d2, l2 = this;
+          r2 = Number(r2);
+          var $2 = O.p(h2), y2 = function(t2) {
+            var e2 = w(l2);
+            return O.w(e2.date(e2.date() + Math.round(t2 * r2)), l2);
+          };
+          if ($2 === f)
+            return this.set(f, this.$M + r2);
+          if ($2 === c)
+            return this.set(c, this.$y + r2);
+          if ($2 === a)
+            return y2(1);
+          if ($2 === o)
+            return y2(7);
+          var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3;
+          return O.w(m3, this);
+        }, m2.subtract = function(t2, e2) {
+          return this.add(-1 * t2, e2);
+        }, m2.format = function(t2) {
+          var e2 = this, n2 = this.$locale();
+          if (!this.isValid())
+            return n2.invalidDate || l;
+          var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = O.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, f2 = n2.months, h2 = function(t3, n3, i3, s3) {
+            return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3);
+          }, c2 = function(t3) {
+            return O.s(s2 % 12 || 12, t3, "0");
+          }, d2 = n2.meridiem || function(t3, e3, n3) {
+            var r3 = t3 < 12 ? "AM" : "PM";
+            return n3 ? r3.toLowerCase() : r3;
+          }, $2 = { YY: String(this.$y).slice(-2), YYYY: this.$y, M: a2 + 1, MM: O.s(a2 + 1, 2, "0"), MMM: h2(n2.monthsShort, a2, f2, 3), MMMM: h2(f2, a2), D: this.$D, DD: O.s(this.$D, 2, "0"), d: String(this.$W), dd: h2(n2.weekdaysMin, this.$W, o2, 2), ddd: h2(n2.weekdaysShort, this.$W, o2, 3), dddd: o2[this.$W], H: String(s2), HH: O.s(s2, 2, "0"), h: c2(1), hh: c2(2), a: d2(s2, u2, true), A: d2(s2, u2, false), m: String(u2), mm: O.s(u2, 2, "0"), s: String(this.$s), ss: O.s(this.$s, 2, "0"), SSS: O.s(this.$ms, 3, "0"), Z: i2 };
+          return r2.replace(y, function(t3, e3) {
+            return e3 || $2[t3] || i2.replace(":", "");
+          });
+        }, m2.utcOffset = function() {
+          return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
+        }, m2.diff = function(r2, d2, l2) {
+          var $2, y2 = O.p(d2), M3 = w(r2), m3 = (M3.utcOffset() - this.utcOffset()) * e, v2 = this - M3, g2 = O.m(this, M3);
+          return g2 = ($2 = {}, $2[c] = g2 / 12, $2[f] = g2, $2[h] = g2 / 3, $2[o] = (v2 - m3) / 6048e5, $2[a] = (v2 - m3) / 864e5, $2[u] = v2 / n, $2[s] = v2 / e, $2[i] = v2 / t, $2)[y2] || v2, l2 ? g2 : O.a(g2);
+        }, m2.daysInMonth = function() {
+          return this.endOf(f).$D;
+        }, m2.$locale = function() {
+          return D[this.$L];
+        }, m2.locale = function(t2, e2) {
+          if (!t2)
+            return this.$L;
+          var n2 = this.clone(), r2 = S(t2, e2, true);
+          return r2 && (n2.$L = r2), n2;
+        }, m2.clone = function() {
+          return O.w(this.$d, this);
+        }, m2.toDate = function() {
+          return new Date(this.valueOf());
+        }, m2.toJSON = function() {
+          return this.isValid() ? this.toISOString() : null;
+        }, m2.toISOString = function() {
+          return this.$d.toISOString();
+        }, m2.toString = function() {
+          return this.$d.toUTCString();
+        }, M2;
+      }(), T = _.prototype;
+      return w.prototype = T, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", f], ["$y", c], ["$D", d]].forEach(function(t2) {
+        T[t2[1]] = function(e2) {
+          return this.$g(e2, t2[0], t2[1]);
+        };
+      }), w.extend = function(t2, e2) {
+        return t2.$i || (t2(e2, _, w), t2.$i = true), w;
+      }, w.locale = S, w.isDayjs = p, w.unix = function(t2) {
+        return w(1e3 * t2);
+      }, w.en = D[g], w.Ls = D, w.p = {}, w;
+    });
+  }
+});
+export default require_dayjs_min();
+//# sourceMappingURL=dayjs.js.map

+ 124 - 0
uni_modules/uv-ui-tools/libs/util/route.js

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

+ 81 - 0
uni_modules/uv-ui-tools/package.json

@@ -0,0 +1,81 @@
+{
+  "id": "uv-ui-tools",
+  "displayName": "uv-ui-tools 工具集  全面兼容小程序、nvue、vue2、vue3等多端",
+  "version": "1.0.4",
+  "description": "uv-ui-tools,集成工具库,强大的Http请求封装,清晰的文档说明,开箱即用。方便使用,可以全局使用",
+  "keywords": [
+    "uv-ui-tools,uv-ui,uv,ui,uview"
+],
+  "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": [],
+    "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",
+          "钉钉": "y",
+          "快手": "y",
+          "飞书": "y",
+          "京东": "y"
+        },
+        "快应用": {
+          "华为": "y",
+          "联盟": "y"
+        }
+      }
+    }
+  }
+}

+ 13 - 0
uni_modules/uv-ui-tools/readme.md

@@ -0,0 +1,13 @@
+## uv-ui-tools 工具集
+
+> **组件名:uv-ui-tools**
+
+uv-ui 工具集成,包括网络Http请求、便捷工具、节流防抖、对象操作、时间格式化、路由跳转、全局唯一标识符、规则校验等等。
+
+需要在自己的项目中使用请参考[扩展配置](https://www.uvui.cn/components/setting.html)。
+
+### <a href="https://www.uvui.cn/js/intro.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>

+ 43 - 0
uni_modules/uv-ui-tools/theme.scss

@@ -0,0 +1,43 @@
+// 此文件为uvUI的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于
+// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
+// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
+
+$uv-main-color: #303133;
+$uv-content-color: #606266;
+$uv-tips-color: #909193;
+$uv-light-color: #c0c4cc;
+$uv-border-color: #dadbde;
+$uv-bg-color: #f3f4f6;
+$uv-disabled-color: #c8c9cc;
+
+$uv-primary: #3c9cff;
+$uv-primary-dark: #398ade;
+$uv-primary-disabled: #9acafc;
+$uv-primary-light: #ecf5ff;
+
+$uv-warning: #f9ae3d;
+$uv-warning-dark: #f1a532;
+$uv-warning-disabled: #f9d39b;
+$uv-warning-light: #fdf6ec;
+
+$uv-success: #5ac725;
+$uv-success-dark: #53c21d;
+$uv-success-disabled: #a9e08f;
+$uv-success-light: #f5fff0;
+
+$uv-error: #f56c6c;
+$uv-error-dark: #e45656;
+$uv-error-disabled: #f7b2b2;
+$uv-error-light: #fef0f0;
+
+$uv-info: #909399;
+$uv-info-dark: #767a82;
+$uv-info-disabled: #c4c6c9;
+$uv-info-light: #f4f4f5;
+
+@mixin flex($direction: row) {
+	/* #ifndef APP-NVUE */
+	display: flex;
+	/* #endif */
+	flex-direction: $direction;
+}

File diff suppressed because it is too large
+ 29 - 5
unpackage/dist/dev/app-plus/app-service.js


File diff suppressed because it is too large
+ 841 - 2
unpackage/dist/dev/app-plus/app-view.js


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