hi 1 hónapja
commit
6b1b324ecd
100 módosított fájl, 14562 hozzáadás és 0 törlés
  1. 2 0
      .gitignore
  2. 31 0
      App.vue
  3. 27 0
      androidPrivacy.json
  4. 7 0
      assets/svg-icons/back.svg
  5. 285 0
      common/css/public.scss
  6. 178 0
      common/css/tools.scss
  7. 35 0
      common/index.js
  8. 25 0
      common/js/appFileShare.js
  9. 126 0
      common/js/dateTime.js
  10. 487 0
      common/js/public.js
  11. 13 0
      common/mixins/index.js
  12. 37 0
      common/mixins/tools.js
  13. 6 0
      common/request/apis/index.js
  14. 86 0
      common/request/index.js
  15. 402 0
      common/request/md5.js
  16. 76 0
      common/router/index.js
  17. 15 0
      common/store/index.js
  18. 94 0
      common/store/modules/update.js
  19. 319 0
      common/store/modules/user.js
  20. 109 0
      common/utils/app-update-check.js
  21. 179 0
      common/utils/sdk-h5-weixin.js
  22. 585 0
      common/utils/sdk-h5.js
  23. 256 0
      common/utils/tools.js
  24. BIN
      components/.DS_Store
  25. 483 0
      components/upload-file/upload-file.vue
  26. 289 0
      components/upload-image/upload-image.vue
  27. 329 0
      components/yk-authpup/yk-authpup.vue
  28. 17 0
      env.js
  29. 21 0
      index.html
  30. 113 0
      main.js
  31. 222 0
      manifest.json
  32. 596 0
      package-lock.json
  33. 25 0
      package.json
  34. 45 0
      pages.json
  35. 335 0
      pages/index/app-update.vue
  36. 48 0
      pages/index/index.vue
  37. 51 0
      static/svg-icons-lib.js
  38. 24 0
      svgo.config.js
  39. 78 0
      uni.scss
  40. 201 0
      uni_modules/Sansnn-uQRCode/LICENSE.md
  41. 207 0
      uni_modules/Sansnn-uQRCode/README.md
  42. 12 0
      uni_modules/Sansnn-uQRCode/changelog.md
  43. 1 0
      uni_modules/Sansnn-uQRCode/common/cache.js
  44. 41 0
      uni_modules/Sansnn-uQRCode/common/queue.js
  45. 3 0
      uni_modules/Sansnn-uQRCode/common/types/cache.d.ts
  46. 4 0
      uni_modules/Sansnn-uQRCode/common/types/queue.d.ts
  47. 25 0
      uni_modules/Sansnn-uQRCode/components/u-qrcode/u-qrcode.vue
  48. 25 0
      uni_modules/Sansnn-uQRCode/components/uqrcode/uqrcode.vue
  49. 241 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/bridge/bridge-weex.js
  50. 18 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-2d/FillStyleLinearGradient.js
  51. 8 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-2d/FillStylePattern.js
  52. 17 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-2d/FillStyleRadialGradient.js
  53. 666 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-2d/RenderingContext.js
  54. 11 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/ActiveInfo.js
  55. 21 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Buffer.js
  56. 21 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Framebuffer.js
  57. 298 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/GLenum.js
  58. 142 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/GLmethod.js
  59. 23 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/GLtype.js
  60. 21 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Program.js
  61. 21 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Renderbuffer.js
  62. 1191 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/RenderingContext.js
  63. 22 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Shader.js
  64. 11 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/ShaderPrecisionFormat.js
  65. 22 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Texture.js
  66. 22 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/UniformLocation.js
  67. 3 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/classUtils.js
  68. 74 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/env/canvas.js
  69. 96 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/env/image.js
  70. 24 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/env/tool.js
  71. 39 0
      uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/index.js
  72. 33 0
      uni_modules/Sansnn-uQRCode/js_sdk/uqrcode/uqrcode.js
  73. 80 0
      uni_modules/Sansnn-uQRCode/package.json
  74. 114 0
      uni_modules/lsj-upload/changelog.md
  75. 403 0
      uni_modules/lsj-upload/components/lsj-upload/LsjFile.js
  76. 323 0
      uni_modules/lsj-upload/components/lsj-upload/lsj-upload.vue
  77. 5 0
      uni_modules/lsj-upload/hybrid/html/js/vue.min.js
  78. 199 0
      uni_modules/lsj-upload/hybrid/html/uploadFile.html
  79. 78 0
      uni_modules/lsj-upload/package.json
  80. 353 0
      uni_modules/lsj-upload/readme.md
  81. 19 0
      uni_modules/song-data-picker/changelog.md
  82. 45 0
      uni_modules/song-data-picker/components/song-data-picker/keypress.js
  83. 135 0
      uni_modules/song-data-picker/components/song-data-picker/searchMixin.js
  84. 579 0
      uni_modules/song-data-picker/components/song-data-picker/song-data-picker.vue
  85. 622 0
      uni_modules/song-data-picker/components/song-data-pickerview/song-data-picker.js
  86. 354 0
      uni_modules/song-data-picker/components/song-data-pickerview/song-data-pickerview.vue
  87. 90 0
      uni_modules/song-data-picker/package.json
  88. 147 0
      uni_modules/song-data-picker/readme.md
  89. 21 0
      uni_modules/uview-ui/LICENSE
  90. 66 0
      uni_modules/uview-ui/README.md
  91. 362 0
      uni_modules/uview-ui/changelog.md
  92. 81 0
      uni_modules/uview-ui/components/u--form/u--form.vue
  93. 47 0
      uni_modules/uview-ui/components/u--image/u--image.vue
  94. 76 0
      uni_modules/uview-ui/components/u--input/u--input.vue
  95. 44 0
      uni_modules/uview-ui/components/u--text/u--text.vue
  96. 48 0
      uni_modules/uview-ui/components/u--textarea/u--textarea.vue
  97. 54 0
      uni_modules/uview-ui/components/u-action-sheet/props.js
  98. 278 0
      uni_modules/uview-ui/components/u-action-sheet/u-action-sheet.vue
  99. 59 0
      uni_modules/uview-ui/components/u-album/props.js
  100. 260 0
      uni_modules/uview-ui/components/u-album/u-album.vue

+ 2 - 0
.gitignore

@@ -0,0 +1,2 @@
+/erp-pda-app.txt
+/node_modules

+ 31 - 0
App.vue

@@ -0,0 +1,31 @@
+<script>
+export default {
+	onLaunch(options) {
+		
+	
+		// 检测客户端更新
+		this.$store.dispatch('update/update', 0)
+	},
+	onShow() {
+		// getApp().globalData.uploadUrl = this.$UPLOAD_URL
+		// getApp().globalData.img_url = this.$IMG_URL
+	},
+	onHide() {
+	
+	},
+	// globalData: {
+	// 	uploadUrl: '',
+	// 	img_url: ''
+	// },
+	methods: {
+	}
+}
+</script>
+
+<style lang="scss">
+/*每个页面公共css */
+@import '@/uni_modules/uview-ui/index.scss';
+/* #ifndef APP-NVUE */
+@import '@/common/css/public.scss';
+/* #endif */
+</style>

+ 27 - 0
androidPrivacy.json

@@ -0,0 +1,27 @@
+{
+    "version" : "1.0.0",
+    "prompt" : "template",
+    "title" : "用户协议和隐私政策",
+    "message" : "  请你务必审慎阅读、充分理解“用户协议”和“隐私政策”各条款,包括但不限于:为了更好的向你提供服务,我们需要收集你的设备标识、操作日志等信息用于分析、优化应用性能。<br/>  你可阅读<a href=\"https://fnsh5.qunlinfapai.com/pages/app/agreement/agreement?type=0\">《用户协议》</a>和<a href=\"https://fnsh5.qunlinfapai.com/pages/app/agreement/agreement?type=1\">《隐私政策》</a>了解详细信息。如果你同意,请点击下面按钮开始接受我们的服务。",
+    "buttonAccept" : "同意并接受",
+    "buttonRefuse" : "暂不同意",
+    "second" : {
+        "title" : "确认提示",
+        "message" : "  进入应用前,你需先同意<a href=\"https://fnsh5.qunlinfapai.com/pages/app/agreement/agreement?type=0\">《用户协议》</a>和<a href=\"https://fnsh5.qunlinfapai.com/pages/app/agreement/agreement?type=1\">《隐私政策》</a>,否则将退出应用。",
+        "buttonAccept" : "同意并继续",
+        "buttonRefuse" : "退出应用"
+    },
+    "styles" : {
+        "backgroundColor" : "#ffffff",
+        "borderRadius" : "5px",
+        "title" : {
+            "color" : "#333333"
+        },
+        "buttonAccept" : {
+            "color" : "#000000"
+        },
+        "buttonRefuse" : {
+            "color" : "#666666"
+        }
+    }
+}

+ 7 - 0
assets/svg-icons/back.svg

@@ -0,0 +1,7 @@
+<svg 
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="48px" height="50px">
+<path fill-rule="evenodd"  fill="rgb(50, 50, 50)"
+ d="M48.000,30.000 C48.000,41.046 39.046,50.000 28.000,50.000 L20.000,50.000 C9.977,50.000 1.699,42.619 0.248,33.000 L-0.000,33.000 L-0.000,3.500 C-0.000,1.567 1.567,0.000 3.500,0.000 C5.433,0.000 7.000,1.567 7.000,3.500 L7.000,30.000 L7.055,30.000 C7.608,37.634 13.453,43.000 21.500,43.000 L24.500,43.000 C33.060,43.000 41.000,39.060 41.000,30.500 C41.000,22.800 36.186,18.043 28.991,17.162 L30.994,19.165 C32.328,20.499 32.328,22.661 30.994,23.994 C29.661,25.328 27.499,25.328 26.165,23.994 L18.006,15.835 C17.309,15.138 16.986,14.216 17.017,13.304 C16.603,12.082 16.869,10.681 17.843,9.707 L26.207,1.343 C27.574,-0.024 29.790,-0.024 31.157,1.343 C32.524,2.710 32.524,4.926 31.157,6.293 L27.450,10.000 L28.000,10.000 C39.046,10.000 48.000,18.954 48.000,30.000 Z"/>
+</svg>

+ 285 - 0
common/css/public.scss

@@ -0,0 +1,285 @@
+view,
+input,
+textarea,
+block,
+navigator,
+swiper,
+swiper-item,
+form,
+scroll-view,
+picker,
+picker-view,
+checkbox-group,
+checkbox,
+radio {
+	box-sizing: border-box;
+}
+
+body,
+page{
+	background-color: #FAFAFC;
+	font-size: 28rpx;
+	color: #333333;
+}
+
+/* flex布局 */
+.flex_box {
+	display: -webkit-flex;
+	display: flex;
+	align-items: center;
+}
+
+.flex_row_between {
+	justify-content: space-between;
+}
+
+.flex_row_center {
+	justify-content: center;
+}
+
+.flex_row_around {
+	justify-content: space-around;
+}
+
+.flex_row_end {
+	justify-content: flex-end;
+}
+
+.flex_direction {
+	flex-direction: column;
+}
+
+.flex_col_top {
+	align-items: flex-start;
+}
+
+.flex_col_bottom {
+	align-items: flex-end;
+}
+
+.flex_wrap {
+	flex-wrap: wrap;
+}
+
+.flex_shrink {
+	flex-shrink: 0;
+}
+
+/* 空列表 */
+.empty_box {
+	padding: 50rpx 0;
+}
+
+.empty_btn {
+	height: 150rpx;
+}
+
+image,
+img {
+	vertical-align: bottom;
+}
+
+/* modal框溢出滚动 */
+.modal_scroll {
+	max-height: 720rpx;
+	overflow-y: scroll;
+}
+
+.form_record_box {
+	padding: 24rpx 0 0;
+}
+/* 音节动画---------------------------------- */
+
+.text-center {
+	text-align: center;
+}
+
+/* 三角指针 */
+.prompt-layer::after {
+	content: '';
+	display: block;
+	border: 6px solid rgba(0, 0, 0, 0);
+	border-top-color: rgba(255, 211, 0, 1);
+	position: absolute;
+	bottom: -10px;
+	left: 50%;
+	transform: translateX(-50%);
+}
+
+.prompt-layer-1 {
+	font-size: 12px;
+	width: 128px;
+	text-align: center;
+	display: flex;
+	flex-direction: column;
+	align-items: center;
+	justify-content: center;
+}
+
+
+.prompt-layer-1 .p {
+	color: #000000;
+}
+
+.prompt-layer-1 .span {
+	color: rgba(0, 0, 0, 0.6);
+}
+
+/* 语音音阶------------- */
+.prompt-loader {
+	width: 96px;
+	height: 20px;
+	display: flex;
+	align-items: center;
+	justify-content: space-between;
+	margin-bottom: 6px;
+}
+
+.prompt-loader .em {
+	display: block;
+	background: #333333;
+	width: 1px;
+	height: 10%;
+	margin-right: 2.5px;
+	float: left;
+}
+
+.prompt-loader .em:last-child {
+	margin-right: 0px;
+}
+
+.prompt-loader .em:nth-child(1) {
+	animation: load 2.5s 1.4s infinite linear;
+}
+
+.prompt-loader .em:nth-child(2) {
+	animation: load 2.5s 1.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(3) {
+	animation: load 2.5s 1s infinite linear;
+}
+
+.prompt-loader .em:nth-child(4) {
+	animation: load 2.5s 0.8s infinite linear;
+}
+
+.prompt-loader .em:nth-child(5) {
+	animation: load 2.5s 0.6s infinite linear;
+}
+
+.prompt-loader .em:nth-child(6) {
+	animation: load 2.5s 0.4s infinite linear;
+}
+
+.prompt-loader .em:nth-child(7) {
+	animation: load 2.5s 0.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(8) {
+	animation: load 2.5s 0s infinite linear;
+}
+
+.prompt-loader .em:nth-child(9) {
+	animation: load 2.5s 0.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(10) {
+	animation: load 2.5s 0.4s infinite linear;
+}
+
+.prompt-loader .em:nth-child(11) {
+	animation: load 2.5s 0.6s infinite linear;
+}
+
+.prompt-loader .em:nth-child(12) {
+	animation: load 2.5s 0.8s infinite linear;
+}
+
+.prompt-loader .em:nth-child(13) {
+	animation: load 2.5s 1s infinite linear;
+}
+
+.prompt-loader .em:nth-child(14) {
+	animation: load 2.5s 1.2s infinite linear;
+}
+
+.prompt-loader .em:nth-child(15) {
+	animation: load 2.5s 1.4s infinite linear;
+}
+
+@keyframes load {
+	0% {
+		height: 10%;
+	}
+
+	50% {
+		height: 100%;
+	}
+
+	100% {
+		height: 10%;
+	}
+}
+
+/* 语音音阶-------------------- */
+.prompt-layer-2 {
+	top: -40px;
+}
+
+.prompt-layer-2 .text {
+	color: rgba(0, 0, 0, 1);
+	font-size: 12px;
+}
+
+/*上传关闭按钮样式*/
+.u-upload{
+	.u-upload__wrap{
+		.u-upload__deletable{
+			width: 40rpx;
+			height: 40rpx;
+			.u-upload__deletable__icon{
+				.u-icon{
+					.u-icon__icon{
+						font-size: 30rpx!important;
+						line-height: 30rpx!important;
+					}
+				}
+			}
+		}
+	}
+}
+// 搜索组件关闭按钮
+.u-search .u-search__content .u-search__content__close{
+	width: 52rpx;
+	height: 52rpx;
+}
+
+.color-primary {
+	color: #FD910C;
+}
+// 禁止滚动
+.noScroll {
+	overflow: hidden;
+}
+
+// 自定义导航栏样式
+.back-text {
+	font-size: 40rpx;
+	font-weight: bold;
+	color: #323232;
+	margin-left: 32rpx;
+}
+// 输入框
+.form_input {
+	position: relative;
+	width: 100%;
+}
+.form_input .form_input_read {
+	position: absolute;
+	top: 0;
+	left: 0;
+	width: 100%;
+	height: 100%;
+	z-index: 9;
+}

+ 178 - 0
common/css/tools.scss

@@ -0,0 +1,178 @@
+page,
+	body {
+		width: 100%;
+		height: 100%;
+		background: #FAFAFC;
+	}
+
+	.calculator_box {
+		width: 100%;
+		padding: 24rpx;
+		
+		&_tips {
+			font-family: PingFang SC, PingFang SC;
+			font-weight: 400;
+			font-size: 28rpx;
+			color: #333333;
+			margin-bottom: 24rpx;
+		}
+		
+		&_wrap {
+			margin: 32rpx 0 48rpx;
+			display: flex;
+			align-items: center;
+		}
+		&_card {
+			background: #FFFFFF;
+			border-radius: 16rpx;
+			padding: 0 24rpx;
+			
+			&+.calculator_box_card {
+				margin-top: 24rpx;
+			}
+			
+			&_item {
+				min-height: 108rpx;
+				padding: 32rpx 0;
+
+				&+.calculator_box_card_item {
+					border-bottom: 2rpx solid #FAFAFC;
+				}
+				
+				&_amount {
+					font-family: PingFang SC, PingFang SC;
+					font-weight: 400;
+					font-size: 24rpx;
+					color: #999999;
+					margin-top: 10rpx;
+				}
+
+				&_label {
+					position: relative;
+					height: 44rpx;
+					line-height: 44rpx;
+					padding-left: 28rpx;
+					font-family: PingFang SC, PingFang SC;
+					font-weight: 400;
+					font-size: 32rpx;
+					color: #333333;
+
+					&::after {
+						content: '';
+						position: absolute;
+						top: 0%;
+						left: 0;
+						transform: translate(-50%, 50%);
+						width: 6rpx;
+						height: 24rpx;
+						background: #FD910C;
+						border-radius: 4rpx;
+					}
+				}
+				
+				&_input {
+					display: flex;
+					align-items: center;
+					height: 60rpx;
+					padding: 0 24rpx;
+					background: #FAFAFC;
+					border-radius: 8rpx;
+					margin-top: 20rpx;
+					
+					&-unit {
+						font-family: PingFang SC, PingFang SC;
+						font-weight: 400;
+						font-size: 28rpx;
+						color: #000000;
+						margin-left: 24rpx;
+					}
+				}
+
+				&_select {
+					flex: 1;
+					display: flex;
+					align-items: center;
+					height: 44rpx;
+
+					&-text {
+						flex: 1;
+						font-family: PingFang SC, PingFang SC;
+						font-weight: 400;
+						font-size: 26rpx;
+						color: #333333;
+						text-align: right;
+
+						&--placeholder {
+							color: #999999;
+						}
+					}
+
+					&-arrow {
+						margin-left: 20rpx;
+					}
+				}
+			}
+		}
+		
+		
+		&_detail {
+			&_total {
+				font-family: PingFang SC, PingFang SC;
+				font-weight: 500;
+				font-size: 38rpx;
+				color: #333333;
+				margin: 60rpx 0 106rpx;
+			}
+			
+			&_wrap {
+				margin-top: 60rpx;
+			}
+			
+			&_item {
+				display: flex;
+				align-items: center;
+				justify-content: space-between;
+				
+				&+.calculator_box_detail_item {
+					margin-top: 24rpx;
+					font-family: PingFang SC, PingFang SC;
+					font-weight: 400;
+					font-size: 28rpx;
+					color: #333333;
+				}
+			}
+		}
+		
+		&_loading {
+			position: absolute;
+			top: 0;
+			left: 0;
+			right: 0;
+			bottom: 0;
+			display: flex;
+			align-items: center;
+			justify-content: center;
+			background-size: 8rpx 8rpx;
+			backdrop-filter: saturate(50%) blur(3rpx);
+			background-image: radial-gradient(transparent 2rpx, #fff 2rpx);
+		}
+	}
+	
+	.cont-title {
+		position: relative;
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 500;
+		font-size: 38rpx;
+		color: #FD910C;
+	}
+	
+	.cont-subtitle {
+		position: absolute;
+		left: 50%;
+		bottom: -32rpx;
+		transform: translate(-50%, 50%);
+		font-family: PingFang SC, PingFang SC;
+		font-weight: 400;
+		font-size: 24rpx;
+		color: #333333;
+	}

+ 35 - 0
common/index.js

@@ -0,0 +1,35 @@
+// 挂载变量
+import {
+	BASE_URL,
+	UPLOAD_URL,
+	UPLOAD_BASE,
+	SOCKET_URL,
+	IMG_URL,
+	imgurl,
+	H5_URL,
+	WORD_URL,LAWYER_URL,LAWYER_URL_FG,
+} from '../env.js';
+import store from '@/common/store';
+import tools from '@/common/utils/tools'
+// mixin混入
+import mixin from '@/common/mixins';
+
+const install = Vue => {
+	Vue.prototype.$BASE_URL = BASE_URL;
+	Vue.prototype.$SOCKET_URL = SOCKET_URL;
+	Vue.prototype.$UPLOAD_URL = UPLOAD_URL;
+	Vue.prototype.$UPLOAD_BASE = UPLOAD_BASE;
+	Vue.prototype.$IMG_URL = IMG_URL;
+	Vue.prototype.$imgurl = imgurl;
+	Vue.prototype.$H5_URL = H5_URL;
+	Vue.prototype.$WORD_URL = WORD_URL;
+	Vue.prototype.$LAWYER_URL = LAWYER_URL;
+	Vue.prototype.$LAWYER_URL_FG = LAWYER_URL_FG;
+	// 挂载工具函数
+	Vue.prototype.$tools = tools;
+	Vue.mixin(mixin);
+}
+
+export default {
+	install
+}

+ 25 - 0
common/js/appFileShare.js

@@ -0,0 +1,25 @@
+	const FileShare= uni.requireNativePlugin('life-FileShare');//分享文件
+	export const anyShareFile = (e) =>{
+		console.log(e,'e')
+		if(!e.url) return 'url缺失';	
+		let url = e.url;
+		let fileType = e.fileType || 'SYSTEM';
+		let name = e.name || '文件';
+		let aa = name+"."+ url.slice(url.lastIndexOf(".") + 1).toLowerCase();//获取该文件类型
+		//下面使用这个下载方式,亲测,如果用uniapp自身的ios中文乱码,下面可以重命名不会乱码,分享就是FileShare调用
+		    var dtask = plus.downloader.createDownload(url,  {filename:"_doc/pdf/"+aa}, function(d, status){
+		                        // 下载完成
+		                if(status == 200){                          
+		                    FileShare.render({
+		                        type:fileType,//QQ为QQ,微信为WX,系统默认是SYSTEM,不填写默认SYSTEM
+		                        filePath:plus.io.convertLocalFileSystemURL(d.filename),
+		                }, result => {
+							
+		                }
+		                );
+		            } else {
+		                console.log("Download failed: " + status); 
+		            }  
+		    });
+		dtask.start();
+	}

+ 126 - 0
common/js/dateTime.js

@@ -0,0 +1,126 @@
+export default {
+	//首页时间转化
+	dateTime(time) {
+		if (('' + time).length === 10) {
+			time = parseInt(time) * 1000
+		} else {
+			time = +time
+		}
+		let old = new Date(time);
+		let now = new Date();
+		//获取old具体时间
+		let d = old.getTime();
+		let h = old.getHours();
+		let m = old.getMinutes();
+		let Y = old.getFullYear();
+		let M = old.getMonth() + 1;
+		let D = old.getDate();
+		//获取now具体时间
+		let nd = now.getTime();
+		let nh = now.getHours();
+		let n = now.getMinutes();
+		let nY = now.getFullYear();
+		let nM = now.getMonth() + 1;
+		let nD = now.getDate();
+
+		//当天的时间
+		if (D === nD && M === nM && Y === nY) {
+			if (h < 10) {
+				h = '0' + h;
+			}
+			if (m < 10) {
+				m = '0' + m;
+			}
+			return h + ':' + m;
+		}
+		//昨天时间
+		if (D + 1 === nD && M === nM && Y === nY) {
+			if (h < 10) {
+				h = '0' + h;
+			}
+			if (m < 10) {
+				m = '0' + m;
+			}
+			return '昨天 ' + h + ':' + m;
+		} else {
+			//大于两天
+			return Y + '/' + M + '/' + D;
+		}
+
+	},
+	//聊天时,发送时间处理
+	dateTime1(time) {
+		if (('' + time).length === 10) {
+			time = parseInt(time) * 1000
+		} else {
+			time = +time
+		}
+		let old = new Date(time);
+		let now = new Date();
+		//获取old具体时间
+		let d = old.getTime();
+		let h = old.getHours();
+		let m = old.getMinutes();
+		let Y = old.getFullYear();
+		let M = old.getMonth() + 1;
+		let D = old.getDate();
+		//获取now具体时间
+		let nd = now.getTime();
+		let nh = now.getHours();
+		let n = now.getMinutes();
+		let nY = now.getFullYear();
+		let nM = now.getMonth() + 1;
+		let nD = now.getDate();
+
+		//当天的时间
+		if (D === nD && M === nM && Y === nY) {
+			if (h < 10) {
+				h = '0' + h;
+			}
+			if (m < 10) {
+				m = '0' + m;
+			}
+			return h + ':' + m;
+		}
+		//昨天时间
+		if (D + 1 === nD && M === nM && Y === nY) {
+			if (h < 10) {
+				h = '0' + h;
+			}
+			if (m < 10) {
+				m = '0' + m;
+			}
+			return '昨天 ' + h + ':' + m;
+		} else if (Y == nY) {
+			//今年
+			if (h < 10) {
+				h = '0' + h;
+			}
+			if (m < 10) {
+				m = '0' + m;
+			}
+			return M + '月' + D + '日 ' + h + ':' + m
+		} else {
+			//大于今年
+			if (h < 10) {
+				h = '0' + h;
+			}
+			if (m < 10) {
+				m = '0' + m;
+			}
+			return Y + '年' + M + '月' + D + '日 ' + h + ':' + m
+		}
+	},
+	// 间隔时间差
+	spaceTime(old, now) {
+		old = new Date(old);
+		now = new Date(now);
+		var told = old.getTime();
+		var tnow = now.getTime();
+		if (told > (tnow + 1000 * 60 * 5)) {
+			return now;
+		} else {
+			return '';
+		}
+	}
+}

+ 487 - 0
common/js/public.js

@@ -0,0 +1,487 @@
+import store from '@/common/store/index.js'
+import {
+	UPLOAD_URL
+} from '../../env.js';
+// 获取时间
+export const getTime = (t) => {
+	t = Math.ceil(t)
+	let minute = parseInt(t / 60) === 0 ? '00' : parseInt(t / 60) < 10 ? '0' + parseInt(t / 60) : parseInt(t / 60)
+	let second = t % 60 < 10 ? '0' + t % 60 : t % 60
+	let this_time = minute + ':' + second
+	return this_time
+}
+
+// 指定位置插入字符串
+export const insertStr = (source, start, newStr) => {
+	return source.slice(0, start) + newStr + source.slice(start)
+}
+
+// 去客服/律师聊天
+export const goChat = (type, id, orderid) => {
+	uni.$u.route({
+		url: '/pages/app/AI/chat',
+		params: {
+			type: type,
+			id: id ? id : '',
+			orderid: orderid ? orderid : ''
+		}
+	});
+}
+
+// 查看文件
+export const openFile = (url) => {
+	if (url) {
+		console.log(url)
+		// #ifdef MP-WEIXIN
+		const fileManage = uni.getFileSystemManager()
+		// #endif
+		const type = url.slice(url.lastIndexOf('.') + 1).toLowerCase()
+		const document = ['doc', 'xls', 'ppt', 'pdf', 'docx', 'xlsx', 'pptx']
+		const images = ['png', 'jpg', 'jpeg', 'gif']
+		const videos = ['mp4', '3gp', 'm3u8']
+		const audios = ['MP3', 'WAV', 'FLAV', 'AAC']
+		if (images.indexOf(type) > -1) {
+			uni.previewImage({
+				urls: [url]
+			})
+		} else if (videos.indexOf(type) > -1) {
+			// #ifdef MP-WEIXIN
+			wx.previewMedia({
+				sources: [{
+					url: url,
+					type: 'video'
+				}]
+			})
+			// #endif
+		} else {
+			uni.showLoading({
+				mask: true,
+				title: '加载中'
+			})
+			uni.downloadFile({
+				url: url,
+				success(rel) {
+					if (document.indexOf(type) > -1) {
+						console.log(rel)
+						uni.openDocument({
+							filePath: rel.tempFilePath,
+							showMenu: true,
+							complete(re) {
+								console.log(re)
+							}
+						})
+					} else {
+						// #ifdef MP-WEIXIN
+						fileManage.saveFile({
+							tempFilePath: rel.tempFilePath,
+							success(res) {
+								uni.hideLoading()
+								uni.showToast({
+									icon: 'success',
+									mask: true,
+									title: '保存成功'
+								})
+							},
+							fail() {
+								uni.hideLoading()
+								uni.$u.toast('保存失败,请重试')
+							}
+						})
+						// #endif
+						// #ifdef APP
+						uni.saveFile({
+							tempFilePath: rel.tempFilePath,
+							success(res) {
+								uni.hideLoading()
+								uni.showToast({
+									icon: 'success',
+									mask: true,
+									title: '保存成功'
+								})
+							},
+							fail() {
+								uni.hideLoading()
+								uni.$u.toast('保存失败,请重试')
+							}
+						})
+						// #endif
+					}
+				},
+				fail() {
+					uni.hideLoading()
+					uni.$u.toast('保存失败,请重试')
+				},
+				complete() {
+					uni.hideLoading()
+				}
+			});
+		}
+	}
+}
+
+// 保存文件
+export const downloadFile = (url) => {
+	if (url) {
+		// #ifdef MP-WEIXIN
+		const fileManage = uni.getFileSystemManager()
+		// #endif
+		uni.showLoading({
+			mask: true,
+			title: '保存中'
+		})
+		uni.downloadFile({
+			url: url,
+			success(rel) {
+				const type = url.slice(url.lastIndexOf('.') + 1).toLowerCase()
+				const document = ['doc', 'xls', 'ppt', 'pdf', 'docx', 'xlsx', 'pptx']
+				const images = ['png', 'jpg', 'jpeg', 'gif']
+				const videos = ['mp4', '3gp', 'm3u8']
+				const audios = ['MP3', 'WAV', 'FLAV', 'AAC']
+				if (images.indexOf(type) > -1) {
+					uni.saveImageToPhotosAlbum({
+						filePath: rel.tempFilePath,
+						success() {
+							uni.hideLoading()
+							uni.showToast({
+								icon: 'success',
+								mask: true,
+								title: '保存成功'
+							})
+						},
+						fail() {
+							uni.hideLoading()
+							uni.$u.toast('保存失败,请重试')
+						}
+					});
+				} else if (videos.indexOf(type) > -1) {
+					uni.saveVideoToPhotosAlbum({
+						filePath: rel.tempFilePath,
+						success() {
+							uni.hideLoading()
+							uni.showToast({
+								icon: 'success',
+								mask: true,
+								title: '保存成功'
+							})
+						},
+						fail() {
+							uni.hideLoading()
+							uni.$u.toast('保存失败,请重试')
+						}
+					});
+				} else {
+					if (document.indexOf(type) > -1) {
+						uni.hideLoading()
+						uni.openDocument({
+							filePath: rel.tempFilePath,
+							showMenu: true
+						})
+					} else {
+						// #ifdef MP-WEIXIN
+						fileManage.saveFile({
+							tempFilePath: rel.tempFilePath,
+							success(res) {
+								uni.hideLoading()
+								uni.showToast({
+									icon: 'success',
+									mask: true,
+									title: '保存成功'
+								})
+							},
+							fail() {
+								uni.hideLoading()
+								uni.$u.toast('保存失败,请重试')
+							}
+						})
+						// #endif
+						// #ifdef APP
+						uni.saveFile({
+							tempFilePath: rel.tempFilePath,
+							success(res) {
+								uni.hideLoading()
+								uni.showToast({
+									icon: 'success',
+									mask: true,
+									title: '保存成功'
+								})
+							},
+							fail() {
+								uni.hideLoading()
+								uni.$u.toast('保存失败,请重试')
+							}
+						})
+						// #endif
+					}
+				}
+			},
+			fail() {
+				uni.hideLoading()
+				uni.$u.toast('保存失败,请重试')
+			}
+		});
+	}
+}
+
+// 格式化文字展示文本域格式文字
+export const textFormat = (val) => {
+	if (val) {
+		let newString = val.replace(/\n/g, '_@').replace(/\r/g, '_#');
+		newString = newString.replace(/_#_@/g, '<br/>'); // IE7-8
+		newString = newString.replace(/_@/g, '<br/>'); // IE9、FF、chrome
+		newString = newString.replace(/\s/g, '&nbsp;'); // 空格处理
+		return newString;
+	}
+}
+
+/**
+ * 乘法
+ * @param arg1
+ * @param arg2
+ * @returns {Number}
+ */
+export const accMul = (arg1, arg2) => {
+	var m = 0,
+		s1 = arg1.toString(),
+		s2 = arg2.toString();
+	try {
+		m += s1.split(".")[1].length
+	} catch (e) {}
+	try {
+		m += s2.split(".")[1].length
+	} catch (e) {}
+	return Number(s1.replace(".", "")) * Number(s2.replace(".", "")) / Math.pow(10, m)
+}
+/**
+ * 除法
+ * @param arg1
+ * @param arg2
+ * @returns {Number}
+ */
+export const accDiv = (arg1, arg2) => {
+	var t1 = 0,
+		t2 = 0,
+		r1, r2;
+	try {
+		t1 = arg1.toString().split(".")[1].length
+	} catch (e) {}
+	try {
+		t2 = arg2.toString().split(".")[1].length
+	} catch (e) {}
+	r1 = Number(arg1.toString().replace(".", ""))
+	r2 = Number(arg2.toString().replace(".", ""))
+	return (r1 / r2) * Math.pow(10, t2 - t1);
+}
+/**
+ * 加法
+ * @param arg1
+ * @param arg2
+ * @returns {Number}
+ */
+export const accAdd = (arg1, arg2) => {
+	var r1, r2, m, c;
+	try {
+		r1 = arg1.toString().split(".")[1].length
+	} catch (e) {
+		r1 = 0
+	}
+	try {
+		r2 = arg2.toString().split(".")[1].length
+	} catch (e) {
+		r2 = 0
+	}
+	c = Math.abs(r1 - r2);
+	m = Math.pow(10, Math.max(r1, r2))
+	if (c > 0) {
+		var cm = Math.pow(10, c);
+		if (r1 > r2) {
+			arg1 = Number(arg1.toString().replace(".", ""));
+			arg2 = Number(arg2.toString().replace(".", "")) * cm;
+		} else {
+			arg1 = Number(arg1.toString().replace(".", "")) * cm;
+			arg2 = Number(arg2.toString().replace(".", ""));
+		}
+	} else {
+		arg1 = Number(arg1.toString().replace(".", ""));
+		arg2 = Number(arg2.toString().replace(".", ""));
+	}
+	return (arg1 + arg2) / m
+}
+/**
+ * 减法
+ * @param arg1
+ * @param arg2
+ * @returns
+ */
+export const accSub = (arg1, arg2) => {
+	var r1, r2, m, n;
+	try {
+		r1 = arg1.toString().split(".")[1].length
+	} catch (e) {
+		r1 = 0
+	}
+	try {
+		r2 = arg2.toString().split(".")[1].length
+	} catch (e) {
+		r2 = 0
+	}
+	m = Math.pow(10, Math.max(r1, r2));
+	//last modify by deeka
+	//动态控制精度长度
+	n = (r1 >= r2) ? r1 : r2;
+	return ((arg1 * m - arg2 * m) / m).toFixed(n);
+}
+
+export const add0 = (m) => {
+	return m < 10 ? '0' + m : m
+}
+
+export const format = (e) => {
+	var time = new Date(e);
+	var y = time.getFullYear();
+	var m = time.getMonth() + 1;
+	var d = time.getDate();
+	return y + '-' + add0(m) + '-' + add0(d);
+}
+/**
+ * 格式化时间 月-日
+ */
+export const formatMD = (e) => {
+	var time = new Date(e);
+	var m = time.getMonth() + 1;
+	var d = time.getDate();
+	return add0(m) + '-' + add0(d);
+}
+/**
+ * 保存聊天记录
+ */
+export const setChat = (data, id) => {
+	uni.getStorage({
+		key: 'yzychat:message_' + uni.getStorageSync('userInfo').id + '_' + id,
+		success: function(res) {
+			// 储存所有消息
+			// let arr = res.data;
+			// 优化,只储存 100条数据,超过100条,通过接口获取历史记录
+			let arr = res.data.slice(-96);
+			arr.push(data);
+			uni.setStorageSync('yzychat:message_' + uni.getStorageSync('userInfo').id + '_' + id,
+				arr);
+		},
+		fail: function(res) {
+			uni.setStorageSync('yzychat:message_' + uni.getStorageSync('userInfo').id + '_' + id,
+				[data]);
+		}
+	});
+	// 返回原始数据
+	return data;
+}
+
+/** * 文件大小 字节转换单位 * @param size * @returns {string|*} */
+export const filterSize = (size) => {
+	if (!size) return '';
+	if (size < pow1024(1)) return size + ' B';
+	if (size < pow1024(2)) return (size / pow1024(1)).toFixed(2) + ' KB';
+	if (size < pow1024(3)) return (size / pow1024(2)).toFixed(2) + ' MB';
+	if (size < pow1024(4)) return (size / pow1024(3)).toFixed(2) + ' GB';
+	return (size / pow1024(4)).toFixed(2) + ' TB'
+}
+
+export const pow1024 = (num) => {
+	return Math.pow(1024, num)
+}
+
+// 计算字符串包含某个字符的数量
+export const countChar = (str, char) => {
+	var parts = str.split(char); // 根据指定字符分隔字符串为多个部分
+	return parts.length - 1; // 返回分隔后的部分数组长度-1(因为最后一个部分不包含指定字符)
+}
+
+export const setCache = (page, type, value) => {
+	const datas = {
+		...store.getters.cacheDatas
+	}
+	datas[page][type] = value
+	store.commit('setCacheDatas', datas)
+}
+
+export const exportWord = (html) => {
+	// #ifdef H5
+	html = html.replace(/<h2>/g, '').replace(/<\/h2>/g, '')
+	html = html
+		.replace(/<br>/g, '</p><p>')
+		.replace(
+			'<body>',
+			'<body><style>h1{text-align: center;margin-bottom: 2em;}p{text-align: justify;line-height: 1.5;text-indent: 2em;margin-bottom: 2em;}ol{text-align: justify;line-height: 1.5;padding-left: 2em;margin-bottom: 2em;}</style>'
+		)
+	return new Promise((resolve, reject) => {
+		const blob = new Blob(['\ufeff', html], {
+			type: 'application/docx'
+		});
+		uni.uploadFile({
+			url: UPLOAD_URL,
+			file: blob,
+			header: {
+				token: `${store.getters.token}`
+			},
+			success: (res) => {
+				if (res.data) {
+					let data = JSON.parse(res.data)
+					if (data.code === 1) {
+						resolve(data.data)
+					} else {
+						uni.showToast({
+							title: '网络不稳定,请重试',
+							icon: 'none'
+						})
+						reject()
+					}
+				} else {
+					uni.showToast({
+						title: '网络不稳定,请重试',
+						icon: 'none'
+					})
+					reject()
+				}
+			},
+			fail(err) {
+				console.log(err)
+				uni.showToast({
+					title: '网络不稳定,请重试',
+					icon: 'none'
+				})
+				reject()
+			}
+		})
+	});
+	// #endif
+}
+/**
+ * Parse the time to string
+ * @param {(Object|string|number)} time
+ * @param {string} cFormat
+ * @returns {string | null}
+ */
+export function parseDate(time, cFormat) {
+  if (arguments.length === 0 || !time) {
+    return null
+  }
+  const format = cFormat || '{y}-{m}-{d} {h}:{i}'
+  const date = new Date(time)
+  const formatObj = {
+    y: date.getFullYear(),
+    m: date.getMonth() + 1,
+    d: date.getDate(),
+    h: date.getHours(),
+    i: date.getMinutes(),
+    s: date.getSeconds(),
+    a: date.getDay()
+  }
+  const time_str = format.replace(/{([ymdhisa])+}/g, (result, key) => {
+    const value = formatObj[key]
+    // Note: getDay() returns 0 on Sunday
+    if (key === 'a') {
+      return ['日', '一', '二', '三', '四', '五', '六'][value]
+    }
+    return value.toString().padStart(2, '0')
+  })
+  return time_str
+}

+ 13 - 0
common/mixins/index.js

@@ -0,0 +1,13 @@
+
+
+import store from '@/common/store'
+import http from '@/common/request'
+import {
+	router
+} from '@/common/router'
+
+export default {
+	onLoad(options) {
+	
+	}
+}

+ 37 - 0
common/mixins/tools.js

@@ -0,0 +1,37 @@
+
+export default {
+	data: () => {
+		return {
+			pageHeight: 0,
+			copyInfo: {
+				form: {},
+				info: {}
+			}
+		}
+	},
+	onReady () {
+		this.$nextTick(() => {
+			// 获取滚动区域的高度
+			uni.createSelectorQuery().select('#page').boundingClientRect((rect) => {
+				this.pageHeight = rect.height
+			}).exec()
+		})
+	},
+	methods: {
+		toBottom () {
+			uni.pageScrollTo({
+				scrollTop: this.pageHeight,
+				duration: 200 // 可根据需求设置滚动动画时长
+			})
+		},
+		copy (value) {
+			console.log(value)
+			uni.setClipboardData({
+				data: value,
+				success: function () {
+					console.log('复制成功');
+				}
+			});
+		}
+	}
+}

+ 6 - 0
common/request/apis/index.js

@@ -0,0 +1,6 @@
+// 
+export const bannerList = (params, config = {}) => uni.$u.http.get(`/1`, {params, config, custom: { auth: false }})
+// 
+export const Openid = (params, config = {custom: { auth: true }}) => uni.$u.http.post(`/2`, params, config)
+
+

+ 86 - 0
common/request/index.js

@@ -0,0 +1,86 @@
+import store from '@/common/store/index.js'
+
+module.exports = (vm) => {
+	// 环境判断
+	uni.$u.http.setConfig((config) => {
+		/* config 为默认全局配置*/
+		config.header = {
+			'Content-Type': 'application/x-www-form-urlencoded',
+			'Access-Control-Allow-Headers': '*'
+		}
+		config.baseURL = vm.$BASE_URL; /* 根域名 */
+		// config.baseURL = vm.$LAWYER_URL; /* 根域名 */
+		config.withCredentials = true;
+		return config
+	})
+	// 请求拦截部分,如配置,每次请求前都会执行
+	uni.$u.http.interceptors.request.use(async (config) => {
+		// 引用token
+		let token = uni.getStorageSync('token');
+		if(config.custom&&config.custom.auth&&!token){
+			uni.$u.toast('请登录后再操作')
+			setTimeout(()=>{
+				vm.$Router.push('/pages/account/login/login')
+			},1000)
+			return new Promise(() => { })
+		}
+		if(config.custom&&config.custom.timeout){
+			config.timeout = config.custom.timeout
+		}
+		if(config.method === 'GET'){
+			config.header = {
+				'Content-Type': 'application/json;charset=UTF-8',
+			}
+		}else{
+			config.header = {
+				'Content-Type': 'application/x-www-form-urlencoded',
+			}
+		}
+		if(config.url.includes('structured-law')){
+			// config.baseURL = vm.$LAWYER_URL_FG
+			config.header = {
+				'Content-Type': 'application/x-www-form-urlencoded',
+			}
+		}
+		let data = config.method==="GET" ? config.params : config.data;
+		if(token) data.token = token;
+		return config;
+	}, config => { // 可使用async await 做异步操作
+		return Promise.reject(config)
+	})
+	// 响应拦截,如配置,每次请求结束都会执行本方法
+	uni.$u.http.interceptors.response.use(async (response) => {
+		const data = response.data;
+		const custom = response.config.custom;
+		if (data.code <= 0) {
+			uni.hideLoading()
+			if (!custom.toast) {
+				uni.$u.toast(data.msg);
+				console.log(custom)
+			}
+			// 如果需要catch返回,则进行reject
+			if (custom?.catch) {
+				return Promise.reject(data)
+			} else {
+				// 否则返回一个pending中的promise,请求不会进入catch中
+				return new Promise(() => { })
+			}
+		}
+		return !data ? {} : data;
+	},async (response) => {
+		console.log(response)
+		uni.hideLoading()
+		/*  对响应错误做点什么 (statusCode !== 200)*/
+		if (response.statusCode == 401) {
+			uni.$u.toast('登录已过期,请重新登录')
+			store.dispatch('userLogout')
+		}else if (response.statusCode == 500) {
+			uni.$u.toast('网络开了点小差,请重试');
+		} else if (response.statusCode == 404) {
+			return;
+		} else {
+			uni.$u.toast(response.data.msg);
+		}
+		return new Promise(() => { });
+	})
+}

+ 402 - 0
common/request/md5.js

@@ -0,0 +1,402 @@
+/*
+ * JavaScript MD5
+ * https://github.com/blueimp/JavaScript-MD5
+ *
+ * Copyright 2011, Sebastian Tschan
+ * https://blueimp.net
+ *
+ * Licensed under the MIT license:
+ * https://opensource.org/licenses/MIT
+ *
+ * Based on
+ * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
+ * Digest Algorithm, as defined in RFC 1321.
+ * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
+ * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
+ * Distributed under the BSD License
+ * See http://pajhome.org.uk/crypt/md5 for more info.
+ */
+
+/* global define */
+
+/* eslint-disable strict */
+
+;(function ($) {
+  'use strict'
+
+  /**
+   * Add integers, wrapping at 2^32.
+   * This uses 16-bit operations internally to work around bugs in interpreters.
+   *
+   * @param {number} x First integer
+   * @param {number} y Second integer
+   * @returns {number} Sum
+   */
+  function safeAdd(x, y) {
+    var lsw = (x & 0xffff) + (y & 0xffff)
+    var msw = (x >> 16) + (y >> 16) + (lsw >> 16)
+    return (msw << 16) | (lsw & 0xffff)
+  }
+
+  /**
+   * Bitwise rotate a 32-bit number to the left.
+   *
+   * @param {number} num 32-bit number
+   * @param {number} cnt Rotation count
+   * @returns {number} Rotated number
+   */
+  function bitRotateLeft(num, cnt) {
+    return (num << cnt) | (num >>> (32 - cnt))
+  }
+
+  /**
+   * Basic operation the algorithm uses.
+   *
+   * @param {number} q q
+   * @param {number} a a
+   * @param {number} b b
+   * @param {number} x x
+   * @param {number} s s
+   * @param {number} t t
+   * @returns {number} Result
+   */
+  function md5cmn(q, a, b, x, s, t) {
+    return safeAdd(bitRotateLeft(safeAdd(safeAdd(a, q), safeAdd(x, t)), s), b)
+  }
+  /**
+   * Basic operation the algorithm uses.
+   *
+   * @param {number} a a
+   * @param {number} b b
+   * @param {number} c c
+   * @param {number} d d
+   * @param {number} x x
+   * @param {number} s s
+   * @param {number} t t
+   * @returns {number} Result
+   */
+  function md5ff(a, b, c, d, x, s, t) {
+    return md5cmn((b & c) | (~b & d), a, b, x, s, t)
+  }
+  /**
+   * Basic operation the algorithm uses.
+   *
+   * @param {number} a a
+   * @param {number} b b
+   * @param {number} c c
+   * @param {number} d d
+   * @param {number} x x
+   * @param {number} s s
+   * @param {number} t t
+   * @returns {number} Result
+   */
+  function md5gg(a, b, c, d, x, s, t) {
+    return md5cmn((b & d) | (c & ~d), a, b, x, s, t)
+  }
+  /**
+   * Basic operation the algorithm uses.
+   *
+   * @param {number} a a
+   * @param {number} b b
+   * @param {number} c c
+   * @param {number} d d
+   * @param {number} x x
+   * @param {number} s s
+   * @param {number} t t
+   * @returns {number} Result
+   */
+  function md5hh(a, b, c, d, x, s, t) {
+    return md5cmn(b ^ c ^ d, a, b, x, s, t)
+  }
+  /**
+   * Basic operation the algorithm uses.
+   *
+   * @param {number} a a
+   * @param {number} b b
+   * @param {number} c c
+   * @param {number} d d
+   * @param {number} x x
+   * @param {number} s s
+   * @param {number} t t
+   * @returns {number} Result
+   */
+  function md5ii(a, b, c, d, x, s, t) {
+    return md5cmn(c ^ (b | ~d), a, b, x, s, t)
+  }
+
+  /**
+   * Calculate the MD5 of an array of little-endian words, and a bit length.
+   *
+   * @param {Array} x Array of little-endian words
+   * @param {number} len Bit length
+   * @returns {Array<number>} MD5 Array
+   */
+  function binlMD5(x, len) {
+    /* append padding */
+    x[len >> 5] |= 0x80 << len % 32
+    x[(((len + 64) >>> 9) << 4) + 14] = len
+
+    var i
+    var olda
+    var oldb
+    var oldc
+    var oldd
+    var a = 1732584193
+    var b = -271733879
+    var c = -1732584194
+    var d = 271733878
+
+    for (i = 0; i < x.length; i += 16) {
+      olda = a
+      oldb = b
+      oldc = c
+      oldd = d
+
+      a = md5ff(a, b, c, d, x[i], 7, -680876936)
+      d = md5ff(d, a, b, c, x[i + 1], 12, -389564586)
+      c = md5ff(c, d, a, b, x[i + 2], 17, 606105819)
+      b = md5ff(b, c, d, a, x[i + 3], 22, -1044525330)
+      a = md5ff(a, b, c, d, x[i + 4], 7, -176418897)
+      d = md5ff(d, a, b, c, x[i + 5], 12, 1200080426)
+      c = md5ff(c, d, a, b, x[i + 6], 17, -1473231341)
+      b = md5ff(b, c, d, a, x[i + 7], 22, -45705983)
+      a = md5ff(a, b, c, d, x[i + 8], 7, 1770035416)
+      d = md5ff(d, a, b, c, x[i + 9], 12, -1958414417)
+      c = md5ff(c, d, a, b, x[i + 10], 17, -42063)
+      b = md5ff(b, c, d, a, x[i + 11], 22, -1990404162)
+      a = md5ff(a, b, c, d, x[i + 12], 7, 1804603682)
+      d = md5ff(d, a, b, c, x[i + 13], 12, -40341101)
+      c = md5ff(c, d, a, b, x[i + 14], 17, -1502002290)
+      b = md5ff(b, c, d, a, x[i + 15], 22, 1236535329)
+
+      a = md5gg(a, b, c, d, x[i + 1], 5, -165796510)
+      d = md5gg(d, a, b, c, x[i + 6], 9, -1069501632)
+      c = md5gg(c, d, a, b, x[i + 11], 14, 643717713)
+      b = md5gg(b, c, d, a, x[i], 20, -373897302)
+      a = md5gg(a, b, c, d, x[i + 5], 5, -701558691)
+      d = md5gg(d, a, b, c, x[i + 10], 9, 38016083)
+      c = md5gg(c, d, a, b, x[i + 15], 14, -660478335)
+      b = md5gg(b, c, d, a, x[i + 4], 20, -405537848)
+      a = md5gg(a, b, c, d, x[i + 9], 5, 568446438)
+      d = md5gg(d, a, b, c, x[i + 14], 9, -1019803690)
+      c = md5gg(c, d, a, b, x[i + 3], 14, -187363961)
+      b = md5gg(b, c, d, a, x[i + 8], 20, 1163531501)
+      a = md5gg(a, b, c, d, x[i + 13], 5, -1444681467)
+      d = md5gg(d, a, b, c, x[i + 2], 9, -51403784)
+      c = md5gg(c, d, a, b, x[i + 7], 14, 1735328473)
+      b = md5gg(b, c, d, a, x[i + 12], 20, -1926607734)
+
+      a = md5hh(a, b, c, d, x[i + 5], 4, -378558)
+      d = md5hh(d, a, b, c, x[i + 8], 11, -2022574463)
+      c = md5hh(c, d, a, b, x[i + 11], 16, 1839030562)
+      b = md5hh(b, c, d, a, x[i + 14], 23, -35309556)
+      a = md5hh(a, b, c, d, x[i + 1], 4, -1530992060)
+      d = md5hh(d, a, b, c, x[i + 4], 11, 1272893353)
+      c = md5hh(c, d, a, b, x[i + 7], 16, -155497632)
+      b = md5hh(b, c, d, a, x[i + 10], 23, -1094730640)
+      a = md5hh(a, b, c, d, x[i + 13], 4, 681279174)
+      d = md5hh(d, a, b, c, x[i], 11, -358537222)
+      c = md5hh(c, d, a, b, x[i + 3], 16, -722521979)
+      b = md5hh(b, c, d, a, x[i + 6], 23, 76029189)
+      a = md5hh(a, b, c, d, x[i + 9], 4, -640364487)
+      d = md5hh(d, a, b, c, x[i + 12], 11, -421815835)
+      c = md5hh(c, d, a, b, x[i + 15], 16, 530742520)
+      b = md5hh(b, c, d, a, x[i + 2], 23, -995338651)
+
+      a = md5ii(a, b, c, d, x[i], 6, -198630844)
+      d = md5ii(d, a, b, c, x[i + 7], 10, 1126891415)
+      c = md5ii(c, d, a, b, x[i + 14], 15, -1416354905)
+      b = md5ii(b, c, d, a, x[i + 5], 21, -57434055)
+      a = md5ii(a, b, c, d, x[i + 12], 6, 1700485571)
+      d = md5ii(d, a, b, c, x[i + 3], 10, -1894986606)
+      c = md5ii(c, d, a, b, x[i + 10], 15, -1051523)
+      b = md5ii(b, c, d, a, x[i + 1], 21, -2054922799)
+      a = md5ii(a, b, c, d, x[i + 8], 6, 1873313359)
+      d = md5ii(d, a, b, c, x[i + 15], 10, -30611744)
+      c = md5ii(c, d, a, b, x[i + 6], 15, -1560198380)
+      b = md5ii(b, c, d, a, x[i + 13], 21, 1309151649)
+      a = md5ii(a, b, c, d, x[i + 4], 6, -145523070)
+      d = md5ii(d, a, b, c, x[i + 11], 10, -1120210379)
+      c = md5ii(c, d, a, b, x[i + 2], 15, 718787259)
+      b = md5ii(b, c, d, a, x[i + 9], 21, -343485551)
+
+      a = safeAdd(a, olda)
+      b = safeAdd(b, oldb)
+      c = safeAdd(c, oldc)
+      d = safeAdd(d, oldd)
+    }
+    return [a, b, c, d]
+  }
+
+  /**
+   * Convert an array of little-endian words to a string
+   *
+   * @param {Array<number>} input MD5 Array
+   * @returns {string} MD5 string
+   */
+  function binl2rstr(input) {
+    var i
+    var output = ''
+    var length32 = input.length * 32
+    for (i = 0; i < length32; i += 8) {
+      output += String.fromCharCode((input[i >> 5] >>> i % 32) & 0xff)
+    }
+    return output
+  }
+
+  /**
+   * Convert a raw string to an array of little-endian words
+   * Characters >255 have their high-byte silently ignored.
+   *
+   * @param {string} input Raw input string
+   * @returns {Array<number>} Array of little-endian words
+   */
+  function rstr2binl(input) {
+    var i
+    var output = []
+    output[(input.length >> 2) - 1] = undefined
+    for (i = 0; i < output.length; i += 1) {
+      output[i] = 0
+    }
+    var length8 = input.length * 8
+    for (i = 0; i < length8; i += 8) {
+      output[i >> 5] |= (input.charCodeAt(i / 8) & 0xff) << i % 32
+    }
+    return output
+  }
+
+  /**
+   * Calculate the MD5 of a raw string
+   *
+   * @param {string} s Input string
+   * @returns {string} Raw MD5 string
+   */
+  function rstrMD5(s) {
+    return binl2rstr(binlMD5(rstr2binl(s), s.length * 8))
+  }
+
+  /**
+   * Calculates the HMAC-MD5 of a key and some data (raw strings)
+   *
+   * @param {string} key HMAC key
+   * @param {string} data Raw input string
+   * @returns {string} Raw MD5 string
+   */
+  function rstrHMACMD5(key, data) {
+    var i
+    var bkey = rstr2binl(key)
+    var ipad = []
+    var opad = []
+    var hash
+    ipad[15] = opad[15] = undefined
+    if (bkey.length > 16) {
+      bkey = binlMD5(bkey, key.length * 8)
+    }
+    for (i = 0; i < 16; i += 1) {
+      ipad[i] = bkey[i] ^ 0x36363636
+      opad[i] = bkey[i] ^ 0x5c5c5c5c
+    }
+    hash = binlMD5(ipad.concat(rstr2binl(data)), 512 + data.length * 8)
+    return binl2rstr(binlMD5(opad.concat(hash), 512 + 128))
+  }
+
+  /**
+   * Convert a raw string to a hex string
+   *
+   * @param {string} input Raw input string
+   * @returns {string} Hex encoded string
+   */
+  function rstr2hex(input) {
+    var hexTab = '0123456789abcdef'
+    var output = ''
+    var x
+    var i
+    for (i = 0; i < input.length; i += 1) {
+      x = input.charCodeAt(i)
+      output += hexTab.charAt((x >>> 4) & 0x0f) + hexTab.charAt(x & 0x0f)
+    }
+    return output
+  }
+
+  /**
+   * Encode a string as UTF-8
+   *
+   * @param {string} input Input string
+   * @returns {string} UTF8 string
+   */
+  function str2rstrUTF8(input) {
+    return unescape(encodeURIComponent(input))
+  }
+
+  /**
+   * Encodes input string as raw MD5 string
+   *
+   * @param {string} s Input string
+   * @returns {string} Raw MD5 string
+   */
+  function rawMD5(s) {
+    return rstrMD5(str2rstrUTF8(s))
+  }
+  /**
+   * Encodes input string as Hex encoded string
+   *
+   * @param {string} s Input string
+   * @returns {string} Hex encoded string
+   */
+  function hexMD5(s) {
+    return rstr2hex(rawMD5(s))
+  }
+  /**
+   * Calculates the raw HMAC-MD5 for the given key and data
+   *
+   * @param {string} k HMAC key
+   * @param {string} d Input string
+   * @returns {string} Raw MD5 string
+   */
+  function rawHMACMD5(k, d) {
+    return rstrHMACMD5(str2rstrUTF8(k), str2rstrUTF8(d))
+  }
+  /**
+   * Calculates the Hex encoded HMAC-MD5 for the given key and data
+   *
+   * @param {string} k HMAC key
+   * @param {string} d Input string
+   * @returns {string} Raw MD5 string
+   */
+  function hexHMACMD5(k, d) {
+    return rstr2hex(rawHMACMD5(k, d))
+  }
+
+  /**
+   * Calculates MD5 value for a given string.
+   * If a key is provided, calculates the HMAC-MD5 value.
+   * Returns a Hex encoded string unless the raw argument is given.
+   *
+   * @param {string} string Input string
+   * @param {string} [key] HMAC key
+   * @param {boolean} [raw] Raw output switch
+   * @returns {string} MD5 output
+   */
+  function md5(string, key, raw) {
+    if (!key) {
+      if (!raw) {
+        return hexMD5(string)
+      }
+      return rawMD5(string)
+    }
+    if (!raw) {
+      return hexHMACMD5(key, string)
+    }
+    return rawHMACMD5(key, string)
+  }
+
+  if (typeof define === 'function' && define.amd) {
+    define(function () {
+      return md5
+    })
+  } else if (typeof module === 'object' && module.exports) {
+    module.exports = md5
+  } else {
+    $.md5 = md5
+  }
+})(this)

+ 76 - 0
common/router/index.js

@@ -0,0 +1,76 @@
+// 路由
+import {
+	RouterMount,
+	createRouter
+} from 'uni-simple-router'
+import store from '@/common/store'
+const router = createRouter({
+	platform: process.env.VUE_APP_PLATFORM,
+	applet: {
+		animationDuration: 0 //默认 300ms 
+	},
+	routerErrorEach: ({
+		type,
+		msg
+	}) => {
+		switch (type) {
+			case 3: // APP退出应用
+				// #ifdef APP-PLUS
+				router.$lockStatus = false;
+				uni.showModal({
+					title: '提示',
+					content: '您确定要退出应用吗?',
+					success: function(res) {
+						if (res.confirm) {
+							plus.runtime.quit();
+						}
+					}
+				});
+				// #endif
+				break;
+			case 2:
+			case 0:
+				router.$lockStatus = false;
+				break;
+			default:
+				break;
+		}
+
+	},
+	// 通配符,非定义页面,跳转404
+	routes: [...ROUTES,
+		{
+			path: '*',
+			redirect: (to) => {
+				return {
+					name: '404'
+				}
+			}
+		},
+	]
+});
+
+//全局路由前置守卫
+router.beforeEach((to, from, next) => {
+	// 权限控制登录
+	if (to.meta && to.meta.auth && !store.getters.isLogin) {
+		uni.$u.toast('请登录后再操作')
+		let pageParams = {
+			url: to.path,
+			params:to.query
+		}
+		uni.setStorageSync('formPage',JSON.stringify(pageParams))
+		setTimeout(()=>{
+			next({
+				path: '/pages/account/login/login'
+			})
+		},1000)
+	} else {
+		next()
+	}
+});
+
+export {
+	router,
+	RouterMount
+}

+ 15 - 0
common/store/index.js

@@ -0,0 +1,15 @@
+import Vue from 'vue'
+import Vuex from 'vuex'
+
+Vue.use(Vuex)
+import user from './modules/user.js'
+import update from './modules/update.js'
+
+const store = new Vuex.Store({
+	modules: {
+		user,
+		update,
+	}
+})
+
+export default store

+ 94 - 0
common/store/modules/update.js

@@ -0,0 +1,94 @@
+/**
+ * 系统更新 - 后续版本更新
+ **/
+import Vue from 'vue';
+// import { getappdownload } from '@/common/request/apis/h5'
+// import { checkUpdate } from '@/common/utils/app-update-check';
+
+export default {
+	namespaced: true,
+	// 储存数据
+	state: {
+		update: false,
+		data: {},
+		link: {},
+		download: {
+			path: null,
+			start: false,
+			install: false,
+			progress: 0,
+			totalBytesWritten: 0,
+			totalBytesExpectedToWrite: 0,
+		},
+		task: null
+	},
+	// 修改数据
+	mutations: {
+		edit(state, {data, index}){
+			state[index] = data;
+		}
+	},
+	actions: {
+		async update({commit, dispatch}, type) {
+			// #ifdef MP
+			const mp = uni.getUpdateManager();
+			// 请求完新版本信息的回调
+			mp.onCheckForUpdate((res) => {
+				console.log(res.hasUpdate)
+				if (!res.hasUpdate&&type) uni.$u.toast('已是最新版本,无需更新!')
+			});
+			mp.onUpdateReady((res) => {
+				// 新的版本已经下载好,调用 applyUpdate 应用新版本并重启
+				uni.showModal({
+					title: '更新提示',
+					content: '新版本已经准备好,是否重启应用?',
+					success(show) {
+						if (show.confirm) {
+							mp.applyUpdate();
+						}
+					}
+				});
+			});
+			mp.onUpdateFailed((res) => {
+				console.log('新的版本下载失败')
+			});
+			// #endif
+			
+			// #ifdef APP-PLUS
+			const system_info = uni.getSystemInfoSync();
+			let params = {
+				os: system_info.platform //本机设备操作系统  (android || ios)
+			};
+			if (params.os != 'ios' && params.os != 'android') false; //如果不是安卓或ios 返回false
+					
+			//这里自行请求API获取版本信息 建议传入操作系统标识,返回本机对应的操作系统最新版本信息,也就是安卓的返回就是安卓的版本信息  ios返回就是ios的版本信息
+			getappdownload()
+				.then(res => {
+					let versionInfo = res.data.version;
+					if (versionInfo&&versionInfo.downloadurl) {
+						let update_info = {
+							version: params.os == 'ios' ? versionInfo.newversion : versionInfo.newversion, //线上版本
+							now_url: params.os == 'ios' ? versionInfo.downloadurl : versionInfo.downloadurl, //更新链接
+							silent: versionInfo.silent ? versionInfo.silent : 0, //是否是静默更新
+							force: versionInfo.enforce, //是否是强制更新
+							net_check: versionInfo.net_check ? versionInfo.net_check : 1, //非WIfi是否提示
+							note: versionInfo.content //更新内容
+						};
+						checkUpdate(update_info, type).then(res => {
+							if (res.msg) {
+								if(type){
+									uni.$u.toast(res.msg)
+								}
+							}
+						}); ///检查更新
+					}
+					//checkUpdate 这个方法会做什么?:线上版本号 与 本地版本号做对比 ,如果需要更新  根据静默,强制、wifi等信息执行静默更新或跳转到升级页面
+					//跳转升级页面的前提是,需要新建并在pages.json中配置升级页面,配置方法请查看插件详情
+				})
+				.catch(err => {
+					uni.$u.toast(err.msg);
+				});
+			// #endif
+		},
+	}
+};

+ 319 - 0
common/store/modules/user.js

@@ -0,0 +1,319 @@
+
+// 用户数据模块
+import http from '@/common/request/index'
+import store from '@/common/store'
+import tools from '@/common/utils/tools'
+// import {
+// 	bindUser,
+// 	getUserInfo
+// } from '@/common/request/apis/user.js';
+// import {
+// 	logout
+// } from '@/common/request/apis/account.js';
+// import {
+// 	Openid,
+// 	getShareImage,
+// 	appShareConfig
+// } from '@/common/request/apis/index';
+// state 静态
+const state = {
+	dynamic_switch: uni.getStorageSync("dynamic_switch") || uni.getStorageSync("dynamic_switch") === 0 ? uni.getStorageSync("dynamic_switch") : 1,
+	alumni_switch: uni.getStorageSync("alumni_switch") || uni.getStorageSync("alumni_switch") === 0 ? uni.getStorageSync("alumni_switch") : 1,
+	product_switch: uni.getStorageSync("product_switch") || uni.getStorageSync("product_switch") === 0 ? uni.getStorageSync("product_switch") : 1,
+	token: uni.getStorageSync("token") || "",
+	isStart: false, // 是否开屏
+	isLogin: uni.getStorageSync("isLogin") || false, // 是否登陆
+	isOneLogin: 0, // 是否登陆
+	userInfo: uni.getStorageSync("userInfo") || {}, // 用户信息
+	// 福利类型:1=咨询服务(数量),2=课程服务,3=Al使用[咨询+文书](限次),4=Al使用[咨询+文书](限时长),5=合同下载(次数),
+	// 6=合同审查(次数),7=诉前辅导(次数),8=文书代写[律师](次数),9=法律顾问(次数),10=应急法援(次数),11=风险查询(次数)
+	userEquity: uni.getStorageSync("userEquity") || {}, // 权益信息
+	imgUrl: '', //服务器图片baseUrl
+	videoHistory: uni.getStorageSync("videoHistory") || [], //查找视频历史记录
+	helpHistory: uni.getStorageSync("helpHistory") || [], //查找帮助历史记录
+	pageParams: {}
+}
+// getters 可以获取 computer 进行动态刷新
+const getters = {
+	dynamic_switch: state => state.dynamic_switch,
+	alumni_switch: state => state.alumni_switch,
+	product_switch: state => state.product_switch,
+	token: state => state.token,
+	isStart: state => state.isStart,
+	isLogin: state => state.isLogin,
+	isOneLogin: state => state.isOneLogin,
+	userInfo: state => state.userInfo,
+	userEquity: state => state.userEquity,
+	imgUrl: imgURL => state.imgUrl,
+	videoHistory: videoHistory => state.videoHistory,
+	helpHistory: helpHistory => state.helpHistory,
+	pageParams: pageParams => state.pageParams
+}
+// actions 异步数据  接口
+const actions = {
+	// 登录
+	userLogin({
+		commit,
+		dispatch
+	}, userInfo) {
+		commit('setToken', userInfo.token);
+		commit('setLogin', true);
+		commit('setisOneLogin', 1);
+
+		commit('setUserInfo', userInfo);
+		// dispatch('chat/start', '', {
+		// 	root: true
+		// })
+		const user_id = uni.getStorageSync('user_id')
+		// 登录重新配置分享信息
+		// appShareConfig().then((res) => {
+		// 	if (res.code === 1) {
+		// 		const path = userInfo.user_id ?
+		// 			`/pages/index/index?user_id=${userInfo.id}` :
+		// 			`/pages/index/index`
+		// 		const href = userInfo.user_id ?
+		// 			`${this.H5_URL}/pages/h5/download?user_id=${userInfo.id}` :
+		// 			`${this.H5_URL}/pages/h5/download`
+		// 		res.data.forEach((item) => {
+		// 			if (item.type == 2) {
+		// 				// #ifdef MP-WEIXIN
+		// 				uni.$u.mpShare = {
+		// 					title: item.title,
+		// 					path: path,
+		// 					imageUrl: item.image
+		// 				}
+		// 				// #endif
+		// 			} else if (item.type == 1) {
+		// 				// #ifdef APP
+		// 				uni.$u.mpShare = {
+		// 					title: item.title,
+		// 					path: href,
+		// 					imageUrl: item.image,
+		// 					summary: item.subtitle
+		// 				}
+		// 				// #endif
+		// 			}
+		// 		})
+		// 	}
+		// })
+		// 分享绑定关系
+		if (user_id) {
+			bindUser({
+				share_id: user_id
+			}).then(res => {}).catch(err => {
+				uni.$u.toast(err.msg);
+				console.log(err)
+				// 错误提示框
+			});
+			uni.removeStorageSync('user_id')
+		}
+		// #ifdef MP-WEIXIN
+		// 获取openid
+		if (!uni.getStorageSync('openid')) {
+			uni.login({
+				success(res) {
+					Openid({
+						code: res.code
+					})
+						.then((res) => {
+							if (res.code == 1) {
+								uni.setStorageSync('openid', res.data.openid)
+							}
+						})
+						.catch((err) => {
+							uni.$u.toast(err.msg)
+						})
+				}
+			})
+		}
+		// #endif
+	},
+	// 退出登录
+	userLogout({
+		commit,
+		dispatch
+	},isPort) {
+		if(isPort) {
+			logout({}).then(res => {
+				if (res.code == 1) {
+					commit('setToken', "");
+					commit('setLogin', false);
+					commit('setisOneLogin', 0);
+					commit('setUserInfo', {});
+					uni.$u.toast(res.msg);
+				} else {
+					uni.$u.toast(res.msg);
+				}
+			}).catch(err => {
+				uni.$u.toast(err.msg);
+			})
+		}else{
+			commit('setToken', "");
+			commit('setLogin', false);
+			commit('setisOneLogin', 0);
+			commit('setUserInfo', {});
+		}
+		
+		
+		// dispatch('chat/initChat', '', {
+		// 	root: true
+		// })
+		// dispatch('chat/close', '', {
+		// 	root: true
+		// })
+		// 重置全局分享信息
+		// share.setShareInfo();
+	},
+	// 获取权益信息
+	getUserEquity({
+		commit,
+		dispatch
+	}) {
+		getRemainNumber().then(res => {
+			commit('setUserEquity', res.data);
+		}).catch(err => {
+			uni.$u.toast(err.msg);
+			// 错误提示框
+		});
+	},
+	// 更新用户信息
+	updateUserInfo({
+		commit,
+		dispatch
+	}) {
+		return new Promise((resolve, reject) => {
+			//调用登陆接口
+			getUserInfo().then(res => {
+				if (res.code == 1) {
+					res.data.tagList = res.data.tag ? res.data.tag.split(",") : []
+					commit('setUserInfo', res.data)
+					resolve(res)
+				} else {
+					uni.$u.toast(res.msg);
+					reject(res)
+				}
+			}).catch(err => {
+				uni.$u.toast(err.msg);
+				reject(err)
+			})
+		})
+	},
+	getPagePath({
+		commit,
+		dispatch
+	}) {
+		let curPage = getCurrentPages();
+		let route = curPage[curPage.length - 1].route; //获取当前页面的路由
+		let params = curPage[curPage.length - 1].options; //获取当前页面参数,如果有则返回参数的对象,没有参数返回空对象{}
+		// 需要返回格式  /pages/XX?id=XX 打开下面的
+		let query = '';
+		let keys = Object.keys(params); //获取对象的key 返回对象key的数组
+		if (keys.length > 0) {
+			query = keys.reduce((pre, cur) => {
+				return pre + cur + '=' + params[cur] + '&';
+			}, '?').slice(0, -1);
+		}
+		return new Promise((resolve, reject) => {
+			//调用登陆接口
+			getShareImage({
+				url: '/' + route
+			}).then(res => {
+				if (res.code == 1) {
+					let obj = {
+						pathObj: {
+							url: route,
+							params
+						},
+						shareImg: res.data,
+						path:route + query
+					}
+					//存储信息在state中
+					commit('setPageParams', obj)
+					resolve(obj)
+				}
+			}).catch(err => {
+				reject(err)
+			})
+		})
+	}
+}
+//静态数据 方法 可更改数据
+const mutations = {
+	set_dynamic_switch(state, payload) {
+		state.dynamic_switch = +payload;
+		uni.setStorageSync("dynamic_switch", +payload);
+	},
+	set_alumni_switch(state, payload) {
+		state.alumni_switch = +payload;
+		uni.setStorageSync("alumni_switch", +payload);
+	},
+	set_product_switch(state, payload) {
+		state.product_switch = +payload;
+		uni.setStorageSync("product_switch", +payload);
+	},
+	setToken(state, payload) {
+		state.token = payload;
+		uni.setStorageSync("token", payload);
+	},
+	// 开屏动画
+	setStart(state, data) {
+		state.isStart = data;
+	},
+	// 登录态
+	setLogin(state, data) {
+		state.isLogin = data;
+		uni.setStorageSync('isLogin', data);
+	},
+	// 是否第一次登录
+	setisOneLogin(state, data) {
+		state.isOneLogin = data;
+		uni.setStorageSync('isOneLogin', data);
+	},
+	// 用户信息
+	setUserInfo(state, data) {
+		state.userInfo = data;
+		uni.setStorageSync("userInfo", data);
+	},
+	// 权益信息
+	setUserEquity(state, data) {
+		state.userEquity = data;
+		uni.setStorageSync("userEquity", data);
+	},
+	//搜索视频历史记录
+	setVideoHistory(state, data) {
+		if (!data) {
+			console.log(data)
+			// let arr=[]
+			state.videoHistory = []
+			uni.setStorageSync("videoHistory", state.videoHistory);
+		} else {
+			state.videoHistory.push(data);
+			uni.setStorageSync("videoHistory", state.videoHistory);
+		}
+	},
+	//搜索帮助历史记录
+	setHelpHistory(state, data) {
+		if (!data) {
+			console.log(data)
+			// let arr=[]
+			state.helpHistory = []
+			uni.setStorageSync("helpHistory", state.helpHistory);
+		} else {
+			state.helpHistory.push(data);
+			uni.setStorageSync("helpHistory", state.helpHistory);
+		}
+	},
+	// 缓存当前路径和分享参数
+	setPageParams(state, payload) {
+		state.pageParams = payload;
+	},
+}
+
+
+
+export default {
+	state,
+	mutations,
+	actions,
+	getters
+}

+ 109 - 0
common/utils/app-update-check.js

@@ -0,0 +1,109 @@
+///检查是否更新
+
+// type  1手动点击更新  0是自动检查
+export function checkUpdate(update_info, type = 0) {
+
+	return new Promise((reolve, reject) => {
+		// 获取版本号
+		plus.runtime.getProperty(plus.runtime.appid, async (inf) => {
+			console.log('当前版本', inf.version);
+			console.log('最新版本', update_info.version);
+			let need_update = await compareVersion(inf.version, update_info.version); // 检查是否需要升级(对比版本号)
+			if (!need_update) {
+				return reolve({
+					msg: "已经是最新版本了"
+				})
+			} //不需要更新
+
+			//需要更新,判断是不是静默更新
+			if (/\.wgt$/.test(update_info.now_url) && update_info.silent == 1) {
+				console.log("静默更新");
+				if (type == 1) {
+					return reolve({
+						msg: "已经是最新版本了"
+					})
+				}
+				startSilentUpdate(update_info.now_url); //开始静默更新
+				return reolve({
+					msg: ""
+				})
+			}
+
+
+			//判断当前版本是不是点击过暂不更新
+			let update_ignore_version = uni.getStorageSync("update_ignore") || "0.0.0";
+
+			console.log("强制更新", update_info.force);
+			if (type === 0 && update_ignore_version == update_info.version && update_info.force === 0) {
+				console.log("之前取消过这个版本,就不再提示了");
+				return reolve({
+					msg:''
+				})
+			}
+
+			//弹出更新
+			uni.navigateTo({
+				url: "/pages/index/app-update?updata_info=" + JSON.stringify(update_info),
+				animationType: "fade-in"
+			})
+		});
+	})
+
+
+
+}
+
+//对比版本号
+function compareVersion(ov, nv) {
+	return new Promise((reolve, reject) => {
+		if (!ov || !nv || ov == "" || nv == "") {
+			return reolve(false);
+		}
+		let b = false;
+		let ova = ov.split(".", 4);
+		let nva = nv.split(".", 4);
+		for (let i = 0; i < ova.length && i < nva.length; i++) {
+			let so = ova[i],
+				no = parseInt(so),
+				sn = nva[i],
+				nn = parseInt(sn);
+			if (nn > no || sn.length > so.length) {
+				return reolve(true);
+			} else if (nn < no) {
+				return reolve(false);
+
+			}
+		}
+		if (nva.length > ova.length && 0 == nv.indexOf(ov)) {
+			return reolve(true);
+		} else {
+			return reolve(false);
+		}
+	})
+}
+
+//开始静默更新
+function startSilentUpdate(url) {
+	let options = {
+		method: "get"
+	};
+	console.log("开始静默更新", url);
+	let dtask = plus.downloader.createDownload(url, options);
+	dtask.addEventListener("statechanged", function(task, status) {
+		if (status === null) {} else if (status == 200) {
+			//在这里打印会不停的执行,请注意,正式上线切记不要在这里打印东西///////////////////////////////////////////////////
+			switch (task.state) {
+				case 4:
+					console.log("下载结束", task.filename);
+					installWgt(task.filename); // 安装  
+					break;
+			}
+		}
+	});
+	dtask.start();
+}
+
+// 安装文件
+function installWgt(path) {
+	plus.runtime.install(path);
+}

+ 179 - 0
common/utils/sdk-h5-weixin.js

@@ -0,0 +1,179 @@
+/**
+ * 本模块封装微信浏览器下的一些方法。
+ * 更多微信网页开发sdk方法,详见:https://developers.weixin.qq.com/doc/offiaccount/OA_Web_Apps/JS-SDK.html
+ */
+
+import jweixin from 'weixin-js-sdk';
+import { getjssdk } from '@/common/request/apis/index'
+
+let configSuccess = false;
+
+export default {
+  //判断是否在微信中
+  isWechat() {
+    const ua = window.navigator.userAgent.toLowerCase();
+    if (ua.match(/micromessenger/i) == 'micromessenger') {
+      return true;
+    } else {
+      return false;
+    }
+  },
+
+  isReady(api) {
+    jweixin.ready(api);
+  },
+
+  // 初始化JSSDK
+  async init(callback) {
+    if (!this.isWechat()) {
+      uni.$u.toast('请使用微信网页浏览器打开');
+      return;
+    }
+
+    const url = location.href;
+    const { code, data } = await getjssdk({
+      url: url,
+    });
+
+    if (code === 1) {
+      jweixin.config({
+        debug: false,
+        appId: data.appId,
+        timestamp: data.timestamp,
+        nonceStr: data.nonceStr,
+        signature: data.signature,
+        jsApiList: data.jsApiList,
+        openTagList: data.openTagList,
+      });
+    }
+
+    configSuccess = true;
+
+    jweixin.error((err) => {
+      configSuccess = false;
+      // uni.$u.toast('微信JSSDK:' + err.errMsg);
+    });
+
+    if (callback) {
+      callback(data);
+    }
+  },
+
+  //在需要定位页面调用
+  getLocation(callback) {
+    this.isReady(() => {
+      jweixin.getLocation({
+        type: 'gcj02', // 默认为wgs84的gps坐标,如果要返回直接给openLocation用的火星坐标,可传入'gcj02'
+        success: function (res) {
+          callback(res);
+        },
+        fail: function (res) {
+          console.log('%c微信H5sdk,getLocation失败:', 'color:green;background:yellow');
+        },
+      });
+    });
+  },
+
+  //获取微信收货地址
+  openAddress(callback) {
+    this.isReady(() => {
+      jweixin.openAddress({
+        success: function (res) {
+          callback.success && callback.success(res);
+        },
+        fail: function (err) {
+          callback.error && callback.error(err);
+          console.log('%c微信H5sdk,openAddress失败:', 'color:green;background:yellow');
+        },
+        complete: function (res) {},
+      });
+    });
+  },
+
+  // 微信扫码
+  scanQRCode(callback) {
+    this.isReady(() => {
+      jweixin.scanQRCode({
+        needResult: 1, // 默认为0,扫描结果由微信处理,1则直接返回扫描结果,
+        scanType: ['qrCode', 'barCode'], // 可以指定扫二维码还是一维码,默认二者都有
+        success: function (res) {
+          callback(res);
+        },
+        fail: function (res) {
+          console.log('%c微信H5sdk,scanQRCode失败:', 'color:green;background:yellow');
+        },
+      });
+    });
+  },
+
+  // 更新微信分享信息
+  updateShareInfo(data, callback = null) {
+    this.isReady(() => {
+      const shareData = {
+        title: data.title,
+        desc: data.desc,
+        link: data.link,
+        imgUrl: data.image,
+        success: function (res) {
+          if (callback) {
+            callback(res);
+          }
+          // 分享后的一些操作,比如分享统计等等
+        },
+        cancel: function (res) {},
+      };
+
+      // 新版 分享聊天api
+      jweixin.updateAppMessageShareData(shareData);
+      // 新版 分享到朋友圈api
+      jweixin.updateTimelineShareData(shareData);
+    });
+  },
+
+  // 打开坐标位置
+  openLocation(data, callback) {
+    this.isReady(() => {
+      jweixin.openLocation({
+        //根据传入的坐标打开地图
+        latitude: data.latitude,
+        longitude: data.longitude,
+      });
+    });
+  },
+
+  // 选择图片
+  chooseImage(callback) {
+    this.isReady(() => {
+      jweixin.chooseImage({
+        count: 1,
+        sizeType: ['compressed'],
+        sourceType: ['album'],
+        success: function (rs) {
+          callback(rs);
+        },
+      });
+    });
+  },
+
+  //微信支付
+  wxpay(data, callback) {
+    this.isReady(() => {
+      jweixin.chooseWXPay({
+        timestamp: data.timeStamp, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符
+        nonceStr: data.nonceStr, // 支付签名随机串,不长于 32 位
+        package: data.package, // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=\*\*\*)
+        signType: data.signType, // 签名方式,默认为'SHA1',使用新版支付需传入'MD5'
+        paySign: data.paySign, // 支付签名
+        success: function (res) {
+          callback.success && callback.success(res);
+        },
+        fail: function (err) {
+          callback.fail && callback.fail(err);
+        },
+        cancel: function (err) {
+          callback.cancel && callback.cancel(err);
+        },
+      });
+    });
+  },
+};

+ 585 - 0
common/utils/sdk-h5.js

@@ -0,0 +1,585 @@
+//#ifdef H5
+/** 
+ * 设置剪切板
+uni.setClipboardData({ data:'文字', success:function(data){}, fail:function(err){}, complete:function(res){} })
+
+获取剪贴板
+uni.getClipboardData({success:function(data){}, fail:function(err){}, complete:function(res){} })
+
+保存图片到系统相册
+uni.saveImageToPhotosAlbum({ filePath: res.tempFilePaths[0], success: function () { console.log('save success'); } })
+
+保存视频到系统相册
+uni.saveVideoToPhotosAlbum({ filePath: res.tempFilePath, success: function () { console.log('save success'); } });
+ * **/
+! function(t, e) {
+	try {
+		window.ClipboardJS = e();
+	} catch (e) {};
+	"object" == typeof exports && "object" == typeof module ? module.exports = e() : "function" == typeof define && define
+		.amd ? define([], e) : "object" == typeof exports ? exports.ClipboardJS = e() : t.ClipboardJS = e()
+}(this, function() {
+	return function(n) {
+		var o = {};
+
+		function r(t) {
+			if (o[t]) return o[t].exports;
+			var e = o[t] = {
+				i: t,
+				l: !1,
+				exports: {}
+			};
+			return n[t].call(e.exports, e, e.exports, r), e.l = !0, e.exports
+		}
+		return r.m = n, r.c = o, r.d = function(t, e, n) {
+			r.o(t, e) || Object.defineProperty(t, e, {
+				enumerable: !0,
+				get: n
+			})
+		}, r.r = function(t) {
+			"undefined" != typeof Symbol && Symbol.toStringTag && Object.defineProperty(t, Symbol.toStringTag, {
+				value: "Module"
+			}), Object.defineProperty(t, "__esModule", {
+				value: !0
+			})
+		}, r.t = function(e, t) {
+			if (1 & t && (e = r(e)), 8 & t) return e;
+			if (4 & t && "object" == typeof e && e && e.__esModule) return e;
+			var n = Object.create(null);
+			if (r.r(n), Object.defineProperty(n, "default", {
+					enumerable: !0,
+					value: e
+				}), 2 & t && "string" != typeof e)
+				for (var o in e) r.d(n, o, function(t) {
+					return e[t]
+				}.bind(null, o));
+			return n
+		}, r.n = function(t) {
+			var e = t && t.__esModule ? function() {
+				return t.default
+			} : function() {
+				return t
+			};
+			return r.d(e, "a", e), e
+		}, r.o = function(t, e) {
+			return Object.prototype.hasOwnProperty.call(t, e)
+		}, r.p = "", r(r.s = 0)
+	}([function(t, e, n) {
+		"use strict";
+		var r = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(t) {
+				return typeof t
+			} : function(t) {
+				return t && "function" == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype ? "symbol" :
+					typeof t
+			},
+			i = function() {
+				function o(t, e) {
+					for (var n = 0; n < e.length; n++) {
+						var o = e[n];
+						o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(
+							t, o.key, o)
+					}
+				}
+				return function(t, e, n) {
+					return e && o(t.prototype, e), n && o(t, n), t
+				}
+			}(),
+			a = o(n(1)),
+			c = o(n(3)),
+			u = o(n(4));
+
+		function o(t) {
+			return t && t.__esModule ? t : {
+				default: t
+			}
+		}
+		var l = function(t) {
+			function o(t, e) {
+				! function(t, e) {
+					if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
+				}(this, o);
+				var n = function(t, e) {
+					if (!t) throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
+					return !e || "object" != typeof e && "function" != typeof e ? t : e
+				}(this, (o.__proto__ || Object.getPrototypeOf(o)).call(this));
+				return n.resolveOptions(e), n.listenClick(t), n
+			}
+			return function(t, e) {
+				if ("function" != typeof e && null !== e) throw new TypeError(
+					"Super expression must either be null or a function, not " + typeof e);
+				t.prototype = Object.create(e && e.prototype, {
+					constructor: {
+						value: t,
+						enumerable: !1,
+						writable: !0,
+						configurable: !0
+					}
+				}), e && (Object.setPrototypeOf ? Object.setPrototypeOf(t, e) : t.__proto__ = e)
+			}(o, c.default), i(o, [{
+				key: "resolveOptions",
+				value: function() {
+					var t = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : {};
+					this.action = "function" == typeof t.action ? t.action : this.defaultAction, this.target = "function" ==
+						typeof t.target ? t.target : this.defaultTarget, this.text = "function" == typeof t.text ? t.text : this
+						.defaultText, this.container = "object" === r(t.container) ? t.container : document.body
+				}
+			}, {
+				key: "listenClick",
+				value: function(t) {
+					var e = this;
+					this.listener = (0, u.default)(t, "click", function(t) {
+						return e.onClick(t)
+					})
+				}
+			}, {
+				key: "onClick",
+				value: function(t) {
+					var e = t.delegateTarget || t.currentTarget;
+					this.clipboardAction && (this.clipboardAction = null), this.clipboardAction = new a.default({
+						action: this.action(e),
+						target: this.target(e),
+						text: this.text(e),
+						container: this.container,
+						trigger: e,
+						emitter: this
+					})
+				}
+			}, {
+				key: "defaultAction",
+				value: function(t) {
+					return s("action", t) || 'copy'
+				}
+			}, {
+				key: "defaultTarget",
+				value: function(t) {
+					var e = s("target", t);
+					if (e) {
+						return document.querySelector(e)
+					}
+				}
+			}, {
+				key: "defaultText",
+				value: function(t) {
+					return s("text", t) || this.text
+				}
+			}, {
+				key: "destroy",
+				value: function() {
+					this.listener.destroy(), this.clipboardAction && (this.clipboardAction.destroy(), this.clipboardAction =
+						null)
+				}
+			}], [{
+				key: "isSupported",
+				value: function() {
+					var t = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : ["copy", "cut"],
+						e = "string" == typeof t ? [t] : t,
+						n = !!document.queryCommandSupported;
+					return e.forEach(function(t) {
+						n = n && !!document.queryCommandSupported(t)
+					}), n
+				}
+			}]), o
+		}();
+
+		function s(t, e) {
+			var n = "data-clipboard-" + t;
+			let isFun = e && typeof e.hasAttribute === 'function';
+			if (isFun && e.hasAttribute(n)) {
+				return e.getAttribute(n)
+			}
+		}
+		t.exports = l
+	}, function(t, e, n) {
+		"use strict";
+		var o, r = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(t) {
+				return typeof t
+			} : function(t) {
+				return t && "function" == typeof Symbol && t.constructor === Symbol && t !== Symbol.prototype ? "symbol" :
+					typeof t
+			},
+			i = function() {
+				function o(t, e) {
+					for (var n = 0; n < e.length; n++) {
+						var o = e[n];
+						o.enumerable = o.enumerable || !1, o.configurable = !0, "value" in o && (o.writable = !0), Object.defineProperty(
+							t, o.key, o)
+					}
+				}
+				return function(t, e, n) {
+					return e && o(t.prototype, e), n && o(t, n), t
+				}
+			}(),
+			a = n(2),
+			c = (o = a) && o.__esModule ? o : {
+				default: o
+			};
+		var u = function() {
+			function e(t) {
+				! function(t, e) {
+					if (!(t instanceof e)) throw new TypeError("Cannot call a class as a function")
+				}(this, e), this.resolveOptions(t), this.initSelection()
+			}
+			return i(e, [{
+				key: "resolveOptions",
+				value: function() {
+					var t = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : {};
+					this.action = t.action, this.container = t.container, this.emitter = t.emitter, this.target = t.target,
+						this.text = t.text, this.trigger = t.trigger, this.selectedText = ""
+				}
+			}, {
+				key: "initSelection",
+				value: function() {
+					this.text ? this.selectFake() : this.target && this.selectTarget()
+				}
+			}, {
+				key: "selectFake",
+				value: function() {
+					var t = this,
+						e = "rtl" == document.documentElement.getAttribute("dir");
+					this.removeFake(), this.fakeHandlerCallback = function() {
+							return t.removeFake()
+						}, this.fakeHandler = this.container.addEventListener("click", this.fakeHandlerCallback) || !0, this.fakeElem =
+						document.createElement("textarea"), this.fakeElem.style.fontSize = "12pt", this.fakeElem.style.border =
+						"0", this.fakeElem.style.padding = "0", this.fakeElem.style.margin = "0", this.fakeElem.style.position =
+						"absolute", this.fakeElem.style[e ? "right" : "left"] = "-9999px";
+					var n = window.pageYOffset || document.documentElement.scrollTop;
+					this.fakeElem.style.top = n + "px", this.fakeElem.setAttribute("readonly", ""), this.fakeElem.value =
+						this.text, this.container.appendChild(this.fakeElem), this.selectedText = (0, c.default)(this.fakeElem),
+						this.copyText()
+				}
+			}, {
+				key: "removeFake",
+				value: function() {
+					this.fakeHandler && (this.container.removeEventListener("click", this.fakeHandlerCallback), this.fakeHandler =
+						null, this.fakeHandlerCallback = null), this.fakeElem && (this.container.removeChild(this.fakeElem),
+						this.fakeElem = null)
+				}
+			}, {
+				key: "selectTarget",
+				value: function() {
+					this.selectedText = (0, c.default)(this.target), this.copyText()
+				}
+			}, {
+				key: "copyText",
+				value: function() {
+					var e = void 0;
+					try {
+						e = document.execCommand(this.action)
+					} catch (t) {
+						e = !1
+					}
+					this.handleResult(e)
+				}
+			}, {
+				key: "handleResult",
+				value: function(t) {
+					this.emitter.emit(t ? "success" : "error", {
+						action: this.action,
+						text: this.selectedText,
+						trigger: this.trigger,
+						clearSelection: this.clearSelection.bind(this)
+					})
+				}
+			}, {
+				key: "clearSelection",
+				value: function() {
+					this.trigger && this.trigger.focus(), window.getSelection().removeAllRanges()
+				}
+			}, {
+				key: "destroy",
+				value: function() {
+					this.removeFake()
+				}
+			}, {
+				key: "action",
+				set: function() {
+					var t = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : "copy";
+					if (this._action = t, "copy" !== this._action && "cut" !== this._action) throw new Error(
+						'Invalid "action" value, use either "copy" or "cut"')
+				},
+				get: function() {
+					return this._action
+				}
+			}, {
+				key: "target",
+				set: function(t) {
+					if (void 0 !== t) {
+						if (!t || "object" !== (void 0 === t ? "undefined" : r(t)) || 1 !== t.nodeType) throw new Error(
+							'Invalid "target" value, use a valid Element');
+						if ("copy" === this.action && t.hasAttribute("disabled")) throw new Error(
+							'Invalid "target" attribute. Please use "readonly" instead of "disabled" attribute');
+						if ("cut" === this.action && (t.hasAttribute("readonly") || t.hasAttribute("disabled"))) throw new Error(
+							'Invalid "target" attribute. You can\'t cut text from elements with "readonly" or "disabled" attributes'
+						);
+						this._target = t
+					}
+				},
+				get: function() {
+					return this._target
+				}
+			}]), e
+		}();
+		t.exports = u
+	}, function(t, e) {
+		t.exports = function(t) {
+			var e;
+			if ("SELECT" === t.nodeName) t.focus(), e = t.value;
+			else if ("INPUT" === t.nodeName || "TEXTAREA" === t.nodeName) {
+				var n = t.hasAttribute("readonly");
+				n || t.setAttribute("readonly", ""), t.select(), t.setSelectionRange(0, t.value.length), n || t.removeAttribute(
+					"readonly"), e = t.value
+			} else {
+				t.hasAttribute("contenteditable") && t.focus();
+				var o = window.getSelection(),
+					r = document.createRange();
+				r.selectNodeContents(t), o.removeAllRanges(), o.addRange(r), e = o.toString()
+			}
+			return e
+		}
+	}, function(t, e) {
+		function n() {}
+		n.prototype = {
+			on: function(t, e, n) {
+				var o = this.e || (this.e = {});
+				return (o[t] || (o[t] = [])).push({
+					fn: e,
+					ctx: n
+				}), this
+			},
+			once: function(t, e, n) {
+				var o = this;
+
+				function r() {
+					o.off(t, r), e.apply(n, arguments)
+				}
+				return r._ = e, this.on(t, r, n)
+			},
+			emit: function(t) {
+				for (var e = [].slice.call(arguments, 1), n = ((this.e || (this.e = {}))[t] || []).slice(), o = 0, r = n.length; o <
+					r; o++) n[o].fn.apply(n[o].ctx, e);
+				return this
+			},
+			off: function(t, e) {
+				var n = this.e || (this.e = {}),
+					o = n[t],
+					r = [];
+				if (o && e)
+					for (var i = 0, a = o.length; i < a; i++) o[i].fn !== e && o[i].fn._ !== e && r.push(o[i]);
+				return r.length ? n[t] = r : delete n[t], this
+			}
+		}, t.exports = n
+	}, function(t, e, n) {
+		var d = n(5),
+			h = n(6);
+		t.exports = function(t, e, n) {
+			if (!t && !e && !n) throw new Error("Missing required arguments");
+			if (!d.string(e)) throw new TypeError("Second argument must be a String");
+			if (!d.fn(n)) throw new TypeError("Third argument must be a Function");
+			if (d.node(t)) return s = e, f = n, (l = t).addEventListener(s, f), {
+				destroy: function() {
+					l.removeEventListener(s, f)
+				}
+			};
+			if (d.nodeList(t)) return a = t, c = e, u = n, Array.prototype.forEach.call(a, function(t) {
+				t.addEventListener(c, u)
+			}), {
+				destroy: function() {
+					Array.prototype.forEach.call(a, function(t) {
+						t.removeEventListener(c, u)
+					})
+				}
+			};
+			if (d.string(t)) return o = t, r = e, i = n, h(document.body, o, r, i);
+			throw new TypeError("First argument must be a String, HTMLElement, HTMLCollection, or NodeList");
+			var o, r, i, a, c, u, l, s, f
+		}
+	}, function(t, n) {
+		n.node = function(t) {
+			return void 0 !== t && t instanceof HTMLElement && 1 === t.nodeType
+		}, n.nodeList = function(t) {
+			var e = Object.prototype.toString.call(t);
+			return void 0 !== t && ("[object NodeList]" === e || "[object HTMLCollection]" === e) && "length" in t && (0 ===
+				t.length || n.node(t[0]))
+		}, n.string = function(t) {
+			return "string" == typeof t || t instanceof String
+		}, n.fn = function(t) {
+			return "[object Function]" === Object.prototype.toString.call(t)
+		}
+	}, function(t, e, n) {
+		var a = n(7);
+
+		function i(t, e, n, o, r) {
+			var i = function(e, n, t, o) {
+				return function(t) {
+					t.delegateTarget = a(t.target, n), t.delegateTarget && o.call(e, t)
+				}
+			}.apply(this, arguments);
+			return t.addEventListener(n, i, r), {
+				destroy: function() {
+					t.removeEventListener(n, i, r)
+				}
+			}
+		}
+		t.exports = function(t, e, n, o, r) {
+			return "function" == typeof t.addEventListener ? i.apply(null, arguments) : "function" == typeof n ? i.bind(
+				null, document).apply(null, arguments) : ("string" == typeof t && (t = document.querySelectorAll(t)), Array.prototype
+				.map.call(t, function(t) {
+					return i(t, e, n, o, r)
+				}))
+		}
+	}, function(t, e) {
+		if ("undefined" != typeof Element && !Element.prototype.matches) {
+			var n = Element.prototype;
+			n.matches = n.matchesSelector || n.mozMatchesSelector || n.msMatchesSelector || n.oMatchesSelector || n.webkitMatchesSelector
+		}
+		t.exports = function(t, e) {
+			for (; t && 9 !== t.nodeType;) {
+				if ("function" == typeof t.matches && t.matches(e)) return t;
+				t = t.parentNode
+			}
+		}
+	}])
+});
+let Types = {
+	isFunction: function(obj) {
+		var type = Object.prototype.toString.call(obj)
+		return type == '[object Function]'
+	},
+	isObject: function(obj) {
+		var type = Object.prototype.toString.call(obj)
+		return type == '[object Object]'
+	},
+	isString: function(obj) {
+		var type = Object.prototype.toString.call(obj)
+		return type == '[object String]'
+	}
+}
+uni.setClipboardData = function(options) {
+	let emptyFun = function() {}
+	let config = {
+		data: null,
+		event: null,
+		success: emptyFun,
+		fail: emptyFun,
+		complete: emptyFun
+	}
+	if (options && Types.isObject(options)) {
+		config = Object.assign({}, config, options)
+	}
+	if (options && Types.isString(options)) {
+		config = Object.assign({}, config, {
+			data: options
+		})
+	}
+	let data = config.data
+	let success = config.success || emptyFun
+	let fail = config.fail || emptyFun
+	let complete = config.complete || emptyFun
+	let e = config.event || window.event || {}
+	let cb = new ClipboardJS('.null', {
+		text: () => data
+	})
+	cb.on('success', function(res) {
+		window.__clipboard__ = data;
+		success && Types.isFunction(success) && success({
+			data: res.text
+		})
+		complete && Types.isFunction(complete) && complete()
+		cb.off('error')
+		cb.off('success')
+		cb.destroy()
+	})
+	cb.on('error', function(err) {
+		fail && Types.isFunction(fail) && fail(err)
+		complete && Types.isFunction(complete) && complete()
+		cb.off('error')
+		cb.off('success')
+		cb.destroy()
+	})
+	cb.onClick(e)
+};
+uni.getClipboardData = function(options) {
+	let emptyFun = function() {}
+	let config = {
+		data: null,
+		event: null,
+		success: emptyFun,
+		fail: emptyFun,
+		complete: emptyFun
+	}
+	if (options && Types.isObject(options)) {
+		config = Object.assign({}, config, options)
+	}
+	let success = config.success || emptyFun
+	let fail = config.fail || emptyFun
+	let complete = config.complete || emptyFun
+	if (window.__clipboard__ !== undefined) {
+		success && Types.isFunction(success) && success({
+			data: window.__clipboard__
+		})
+	} else {
+		fail && Types.isFunction(fail) && fail({
+			data: null
+		})
+	}
+	complete && Types.isFunction(complete) && complete()
+};
+
+function fileDownLoad(data) {
+	var linkElement = document.createElement('a')
+	linkElement.setAttribute('href', data.blob)
+	linkElement.setAttribute('downLoad', data.name)
+	linkElement.click()
+}
+uni.saveImageToPhotosAlbum = uni.saveVideoToPhotosAlbum = function(options) {
+	let emptyFun = function() {}
+	let config = {
+		filePath: null,
+		success: emptyFun,
+		fail: emptyFun,
+		complete: emptyFun
+	}
+	if (options && Types.isObject(options)) {
+		config = Object.assign({}, config, options)
+	}
+	if (options && Types.isString(options)) {
+		config = Object.assign({}, config, {
+			filePath: options
+		})
+	}
+	let filePath = config.filePath
+	let success = config.success || emptyFun
+	let fail = config.fail || emptyFun
+	let complete = config.complete || emptyFun
+	if (!filePath) {
+		fail && Types.isFunction(fail) && fail({
+			msg: 'no File'
+		})
+		complete && Types.isFunction(complete) && complete()
+		return
+	}
+	let names = filePath.split('/')
+	let name = names[names.length - 1]
+	uni.downloadFile({
+		url: filePath,
+		success: function(res) {
+			let tempFilePath = res.tempFilePath
+			fileDownLoad({
+				name: name,
+				blob: tempFilePath
+			})
+			success && Types.isFunction(success) && success({
+				filePath: filePath
+			})
+		},
+		fail: function(err) {
+			fail && Types.isFunction(fail) && fail({
+				msg: err
+			})
+		},
+		complete: function() {
+			complete && Types.isFunction(complete) && complete()
+		}
+	})
+}
+//#endif

+ 256 - 0
common/utils/tools.js

@@ -0,0 +1,256 @@
+import {
+	API_URL
+} from '@/env'
+import {
+	router
+} from '@/common/router'
+// #ifdef H5
+import $wxsdk from '@/common/utils/sdk-h5-weixin';
+// #endif
+export default {
+
+	/**
+	 * 跳转再封装,主要是为了兼容外链。
+	 * @param {String} path - 跳转路径
+	 * @param {isTabbar} isTabbar - 是否是底部导航
+	 */
+	routerTo(path, isTabbar) {
+		if (path) {
+			// 是否跳转外部链接
+			if (~path.indexOf('http') || ~path.indexOf('www')) {
+				// #ifdef H5
+				window.location = path;
+				// #endif
+				// #ifndef  H5
+				router.push({
+					path: '/pages/public/webview',
+					query: {
+						'webviewPath': path
+					}
+				})
+				// #endif
+				return false
+			}
+			if (isTabbar) {
+				router.replaceAll(path)
+			} else {
+				path.includes('/pages/index') && !path.includes('/pages/index/view') ? router.replaceAll(path) : router
+					.push(path)
+			}
+
+		} else {
+			console.log(`%cerr:没有填写跳转路径`, 'color:green;background:yellow');
+		}
+	},
+	/**
+	 * 导航-打开位置
+	 * @param {Number} longitude - 经度
+	 * @param {Number} latitude - 纬度
+	 * @param {String} address - 地理位置
+	 */
+	openLocation(longitude, latitude, address) {
+		uni.openLocation({
+			longitude: parseFloat(longitude),
+			latitude: parseFloat(latitude),
+			address: address
+		})
+	},
+	/**
+	 * 图片处理-预览图片
+	 * @param {Array} urls - 图片列表
+	 * @param {Number} current - 首个预览下标
+	 */
+	previewImage(urls = [], current = 0) {
+		uni.previewImage({
+			urls: urls,
+			current: current,
+			indicator: 'default',
+			loop: true,
+			fail(err) {
+				console.log('previewImage出错', urls, err)
+			},
+		})
+	},
+
+	/**
+	 * 数据分组
+	 * @param {Array} oArr - 原数组列表
+	 * @param {Number} length - 单个数组长度
+	 * @return {Array}  arr - 分组后的新数组
+	 */
+	splitData(oArr = [], length = 1) {
+		let arr = [];
+		let minArr = [];
+		oArr.forEach(c => {
+			if (minArr.length === length) {
+				minArr = [];
+			}
+			if (minArr.length === 0) {
+				arr.push(minArr);
+			}
+			minArr.push(c);
+		});
+
+		return arr;
+	},
+	/**
+	 * 获取指定日期
+	 * @param {Date} date - 传入的时间Date对象
+	 * @return {Object}  days - 添加的天数,可以是正数、负数或零
+	 */
+	getDaysToDate(date, days) {
+		console.log(date)
+		if (!(date instanceof Date)) {
+			throw new Error('第一个参数必须是Date对象');
+		}
+		if (typeof days !== 'number') {
+			throw new Error('第二个参数必须是数字');
+		}
+
+		const newDate = new Date(date)
+		newDate.setDate(date.getDate() + days)
+		console.log(newDate)
+		return newDate;
+	},
+	/**
+	 * 剩余时间格式化
+	 * @param {Number} t - 剩余多少秒
+	 * @return {Object}  format - 格式后的天时分秒对象
+	 */
+	format(t) {
+		let format = {
+			d: '00',
+			h: '00',
+			m: '00',
+			s: '00'
+		};
+		if (t > 0) {
+			let d = Math.floor(t / 86400);
+			let h = Math.floor((t / 3600) % 24);
+			let m = Math.floor((t / 60) % 60);
+			let s = Math.floor(t % 60);
+			format.d = d
+			format.h = h
+			format.m = m
+			format.s = s
+		}
+		return format;
+	},
+
+	/**
+	 * 打电话
+	 * @param {String<Number>} phoneNumber - 数字字符串
+	 */
+	callPhone(phoneNumber = '') {
+		let num = phoneNumber.toString()
+		uni.makePhoneCall({
+			phoneNumber: num,
+			fail(err) {
+				console.log('makePhoneCall出错', err)
+			},
+		});
+	},
+
+	/**
+	 * 微信头像
+	 * @param {String} url -图片地址
+	 */
+	checkMPUrl(url) {
+		// #ifdef MP
+		if (
+			url.substring(0, 4) === 'http' &&
+			url.substring(0, 5) !== 'https' &&
+			url.substring(0, 12) !== 'http://store' &&
+			url.substring(0, 10) !== 'http://tmp' &&
+			url.substring(0, 10) !== 'http://usr'
+		) {
+			url = 'https' + url.substring(4, url.length);
+		}
+		// #endif
+		return url;
+	},
+
+	/**
+	 * 中间部分*
+	 */
+	hiddenText(str, frontLen, endLen) {
+		//str:要进行隐藏的变量  frontLen: 前面需要保留几位    endLen: 后面需要保留几位
+		var len = str.length - frontLen - endLen;
+		var xing = "";
+		for (var i = 0; i < len; i++) {
+			xing += "*";
+		}
+		return (
+			str.substring(0, frontLen) + xing + str.substring(str.length - endLen)
+		)
+	},
+	amountCN(n) {
+		const fraction = ['角', '分']
+		const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
+		const unit = [
+			['元', '万', '亿'],
+			['', '拾', '佰', '仟']
+		]
+		const head = n < 0 ? '欠' : ''
+		n = Math.abs(n)
+		let s = ''
+		for (let i = 0; i < fraction.length; i++) {
+			s += (digit[Math.floor(n * 10 * Math.pow(10, i)) % 10] + fraction[i]).replace(/零./, '')
+		}
+		s = s || '整'
+		n = Math.floor(n)
+		// eslint-disable-next-line no-redeclare
+		for (let i = 0; i < unit[0].length && n > 0; i++) {
+			let p = ''
+			for (let j = 0; j < unit[1].length && n > 0; j++) {
+				p = digit[n % 10] + unit[1][j] + p
+				n = Math.floor(n / 10)
+			}
+			s = p.replace(/(零.)*零$/, '').replace(/^$/, '零') + unit[0][i] + s
+		}
+		return (
+			head +
+			s
+			.replace(/(零.)*零元/, '元')
+			.replace(/(零.)+/g, '零')
+			.replace(/^整$/, '零元整')
+		)
+	},
+	// 分享复制
+	shareCopy(page) {
+		// #ifdef H5
+		const url = location.href.split('/pages')[0];
+		uni.setClipboardData({
+			data: url + page,
+			showToast: false,
+			success() {
+				uni.showToast({
+					mask: true,
+					title: '您已复制邀请链接,快去分享吧',
+					icon: 'success'
+				})
+				// uni.showModal({
+				// 	title: '温馨提示',
+				// 	content: '您已复制邀请链接,快去分享吧',
+				// 	showCancel: false
+				// })
+			},
+			fail() {
+				uni.$u.toast('复制失败,请重新点击')
+			}
+		})
+		// #endif
+	},
+	// H5设置分享信息
+	shareHandle(detail) {
+		// #ifdef H5
+		const url = location.href.split('/pages')[0];
+		$wxsdk.updateShareInfo({
+			title: detail.title,
+			desc: detail.desc,
+			link: url + detail.link,
+			image: detail.image
+		});
+		// #endif
+	},
+}

BIN
components/.DS_Store


+ 483 - 0
components/upload-file/upload-file.vue

@@ -0,0 +1,483 @@
+<template>
+	<view class="upload_box">
+		<view class="upload_list">
+			<view v-for="(item, index) in fileList" :key="index" class="flex_box upload_item">
+				<view @click="$openFile(item.url)" class="flex_box upload_left">
+					<u-image
+						width="60rpx"
+						height="60rpx"
+						:showError="false"
+						:showLoading="false"
+						:src="`${$IMG_URL}/static/format/${fileTypes.indexOf(item.type) === '-1' ? 'file' : item.type}.png`"
+					></u-image>
+					<view class="upload_content">
+						<view class="upload_name">{{ item.name }}</view>
+						<view v-if="item.size" class="upload_text">{{ item.size }}</view>
+					</view>
+				</view>
+				<u-image
+					v-if="!isDetail"
+					@click="deletePic(index)"
+					width="48rpx"
+					height="48rpx"
+					:showError="false"
+					:showLoading="false"
+					:src="`${$IMG_URL}/static/home/icon_cancel@2x.png`"
+				></u-image>
+				<view class="service_check">
+					<u-icon color="#FD910C" size="24rpx" :name="item.status === 'success' ? 'checkmark-circle-fill' : 'close-circle-fill'"></u-icon>
+				</view>
+			</view>
+		</view>
+		<!-- #ifdef APP -->
+		<view class="upload_btn_box" v-if="(!isDetail || limit === fileList.length) && fileShow">
+			<template v-if="authList['WRITE_EXTERNAL_STORAGE'] && authList['CAMERA']">
+				<lsj-upload
+					ref="lsjUpload"
+					:wxFileType="wxFileType"
+					:extension="extension"
+					:childId="childId"
+					:width="width"
+					:height="height"
+					:option="option"
+					:size="fileSize"
+					:count="limit"
+					:multiple="multiple"
+					:formats="fileType"
+					:accept="fileAccept"
+					:debug="debug"
+					:instantly="instantly"
+					@uploadEnd="onuploadEnd"
+					@progress="onprogre"
+					@change="change"
+				>
+					<view class="upload_btn">
+						<u-image :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase2-2.png`"></u-image>
+					</view>
+				</lsj-upload>
+			</template>
+			<view v-else-if="!authList['WRITE_EXTERNAL_STORAGE']" class="upload_btn_auth" @tap.stop="openAuth('WRITE_EXTERNAL_STORAGE')">
+				<view class="upload_btn">
+					<u-image :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase2-2.png`"></u-image>
+				</view>
+			</view>
+			<view v-else class="upload_btn_auth" @tap.stop="openAuth('CAMERA')">
+				<view class="upload_btn">
+					<u-image :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase2-2.png`"></u-image>
+				</view>
+			</view>
+		</view>
+		<yk-authpup ref="authpup" type="top" @changeAuth="changeAuth" :permissionID="permissionID"></yk-authpup>
+		<!-- #endif -->
+		<!-- #ifndef APP -->
+		<view class="upload_btn_box" v-if="(!isDetail || limit === fileList.length) && fileShow">
+			<lsj-upload
+				ref="lsjUpload"
+				:wxFileType="wxFileType"
+				:extension="extension"
+				:childId="childId"
+				:width="width"
+				:height="height"
+				:option="option"
+				:size="fileSize"
+				:count="limit"
+				:multiple="multiple"
+				:formats="fileType"
+				:accept="fileAccept"
+				:debug="debug"
+				:instantly="instantly"
+				@uploadEnd="onuploadEnd"
+				@progress="onprogre"
+				@change="change"
+			>
+				<view class="upload_btn">
+					<u-image :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase2-2.png`"></u-image>
+				</view>
+			</lsj-upload>
+		</view>
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import ykAuthpup from '@/components/yk-authpup/yk-authpup'
+import { getFileName } from '@/common/request/apis/index'
+export default {
+	name: 'UploadFile',
+	components: {
+		ykAuthpup
+	},
+	props: {
+		// 值
+		value: [String, Object, Array],
+		// 宽度
+		width: {
+			type: String,
+			default: '104rpx'
+		},
+		// 高度
+		height: {
+			type: String,
+			default: '104rpx'
+		},
+		// 数量限制
+		limit: {
+			type: Number,
+			default: 9
+		},
+		// 大小限制(MB)
+		fileSize: {
+			type: Number,
+			default: 200
+		},
+		// 文件类型, 例如'png, jpg, jpeg']
+		fileType: {
+			type: String,
+			default: ''
+		},
+		// 文件类型, 例如'audio/*, video/*, image/*']
+		fileAccept: {
+			type: String,
+			default: '*'
+		},
+		// 是否文件覆盖图片
+		isOne: {
+			type: Boolean,
+			default: false
+		},
+		// 强制刷新
+		fileShow: {
+			type: Boolean,
+			default: true
+		},
+		// 是否多选
+		multiple: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示提示
+		isShowTip: {
+			type: Boolean,
+			default: true
+		},
+		// 是否详情
+		isDetail: {
+			type: Boolean,
+			default: false
+		},
+		//id
+		childId: {
+			type: String,
+			default: 'lsjUpload'
+		},
+		// 微信选择文件类型
+		//all=从所有文件选择,
+		//video=只能选择视频文件,
+		//image=只能选择图片文件,
+		//file=可以选择除了图片和视频之外的其它的文件
+		wxFileType: { type: String, default: 'all' },
+		extension: { type: Array, default: () => ['*'] }
+	},
+	data() {
+		return {
+			fileTypes: ['bmp', 'doc', 'docx', 'gif', 'jpeg', 'jpg', 'mp3', 'mp4', 'pdf', 'png', 'ppt', 'pptx', 'rar', 'wav', 'webm', 'webp', 'xls', 'xlsx', 'zip'],
+			// 上传接口参数
+			option: {
+				// 上传服务器地址,需要替换为你的接口地址
+				url: this.$UPLOAD_URL, // 该地址非真实路径,需替换为你项目自己的接口地址
+				// 上传附件的key
+				name: 'file',
+				// 根据你接口需求自定义请求头,默认不要写content-type,让浏览器自适配
+				header: {
+					token: `${this.$store.getters.token}`
+				},
+				// 根据你接口需求自定义body参数
+				formData: {
+					filename: ''
+				}
+			},
+			// 选择文件后是否立即自动上传,true=选择后立即上传
+			instantly: true,
+			// 文件回显列表
+			files: new Map(),
+			// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
+			wxFiles: [],
+			// 是否打印日志
+			debug: false,
+			// 文件显示列表
+			fileList: [],
+			// 权限判断
+			permissionID: ''
+		}
+	},
+	watch: {
+		value: {
+			async handler(val) {
+				if (val) {
+					// if (this.fileList.length > 0) return
+					// 首先将值转为数组
+					const list = Array.isArray(val) ? val : this.value.split(',')
+					const files = []
+					// 然后将数组转为对象数组
+					for (let i = 0; i < list.length; i++) {
+						let item = list[i]
+						if (typeof item === 'string') {
+							let name = item + ''
+							let size = ''
+							if (name && name.lastIndexOf('/') > -1) {
+								const result = await getFileName({
+									file_name: name,
+									type: 1
+								})
+								name = result && result.code === 1 ? result.data.filename : name
+								size = result && result.code === 1 ? this.$filterSize(result.data.filesize) : size
+							}
+							item = {
+								name: name,
+								url: this.getUrl(item),
+								fullurl: item,
+								size: size,
+								status: 'success',
+								message: '',
+								type: item.slice(item.lastIndexOf('.') + 1).toLowerCase()
+							}
+						}
+						files.push(item)
+					}
+					this.fileList = files
+				} else {
+					this.fileList = []
+					return []
+				}
+			},
+			deep: true,
+			immediate: true
+		}
+	},
+	computed: {
+		// 是否显示提示
+		showTip() {
+			return this.isShowTip && (this.fileType || this.fileSize)
+		},
+		...mapGetters('auth', ['authList', 'onceIn'])
+	},
+	mounted() {
+		// #ifdef APP
+		// if (this.onceIn) {
+		// 	this.$store.commit('auth/edit', {data: false, index: 'onceIn'})
+		// 	this.openAuth('WRITE_EXTERNAL_STORAGE')
+		// }
+		// #endif
+	},
+	methods: {
+		//打开自定义权限目的弹框
+		openAuth(permissionID) {
+			this.permissionID = permissionID //这个是对应的权限 ACCESS_FINE_LOCATION 位置权限 / WRITE_EXTERNAL_STORAGE 存储空间/照片权限 / CAMERA相机权限 / CALL_PHONE 拨打电话
+			setTimeout(() => {
+				this.$refs['authpup'].open()
+			}, 500)
+		},
+		//用户授权权限后的回调
+		changeAuth() {
+			//这里是权限通过后执行自己的代码逻辑
+			console.log('权限已授权,可执行自己的代码逻辑了')
+			this.authList[this.permissionID] = true
+			const list = { ...this.authList }
+			this.$store.commit('auth/edit', { data: list, index: 'authList' })
+			if (!this.authList['CAMERA']) {
+				this.openAuth('CAMERA')
+			}
+		},
+		// 手动触发
+		actionClick() {
+			// 强制更新视图
+			this.$forceUpdate()
+			console.log(this.extension)
+			this.$nextTick(() => {
+				this.$refs.lsjUpload.onClick()
+			})
+		},
+		// 刷新组件
+		refresh() {
+			this.$refs.lsjUpload.hide()
+			this.$nextTick(() => {
+				setTimeout(() => {
+					this.$refs.lsjUpload.show()
+				}, 200)
+			})
+		},
+		/**
+		 * 某文件上传结束回调(成功失败都回调)
+		 * @param {Object} item 当前上传完成的文件
+		 */
+		onuploadEnd(item) {
+			if (item) {
+				if (this.isOne) {
+					this.files.clear()
+					this.wxFiles = []
+					this.fileList = []
+				}
+				// 更新当前窗口状态变化的文件
+				this.files.set(item.name, item)
+				this.wxFiles = [...this.files.values()]
+				// 强制更新视图
+				this.$forceUpdate()
+				const isHas = this.fileList.find((file) => file.name === item.name)
+				if (item.responseText && !isHas) {
+					const result = JSON.parse(item.responseText)
+					if (result.code === 1) {
+						this.fileList.push({
+							status: 'success',
+							name: item.name,
+							size: this.$filterSize(item.size),
+							message: '',
+							url: result.data.fullurl,
+							fullurl: result.data.url,
+							type: result.data.url.slice(result.data.url.lastIndexOf('.') + 1).toLowerCase()
+						})
+					} else {
+						uni.showModal({
+							content: `${item.name}上传失败,请重新上传`,
+							showCancel: false
+						})
+					}
+				}
+			}
+			let isAll = [...this.files.values()].find((item) => item.type !== 'success')
+			if (!isAll) {
+				this.setUrl()
+			}
+		},
+		/**
+		 * 上传进度回调
+		 * 如果网页上md文档没有渲染出事件名称onprogre,请复制代码的小伙伴自行添加上哈,没有哪个事件是只(item)的
+		 * @param {Object} item 当前正在上传的文件
+		 */
+		onprogre(item) {
+			// 更新当前状态变化的文件
+			this.files.set(item.name, item)
+			// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
+			this.wxFiles = [...this.files.values()]
+
+			// 强制更新视图
+			this.$forceUpdate()
+		},
+		/**
+		 * 文件选择回调
+		 * @param {Object} files 已选择的所有文件Map集合
+		 */
+		change(files) {
+			const list = [...files.values()]
+			if (list.length === 0) return
+			uni.showLoading({
+				title: '上传中',
+				mask: true
+			})
+			// 更新选择的文件
+			this.files = files
+			// 强制更新视图
+			this.$forceUpdate()
+			this.wxFiles = [...list]
+		},
+		/**
+		 * 移除某个文件
+		 * @param {Object} name 带后缀名的文件名称
+		 */
+		deletePic(index) {
+			// name=指定文件名,不传name默认移除所有文件
+			this.$refs.lsjUpload.clear(this.fileList[index].name)
+			this.fileList.splice(index, 1)
+			this.setUrl()
+		},
+		// 设置图片
+		setUrl() {
+			let that = this
+			let urls = []
+			let list = []
+			for (let i = 0; i < that.fileList.length; i++) {
+				if (that.fileList[i].status === 'success') {
+					urls.push(that.fileList[i].fullurl)
+					list.push({
+						url: that.fileList[i].fullurl,
+						fullurl: that.fileList[i].url,
+						index: i,
+						name: that.fileList[i].name
+					})
+				}
+			}
+			that.$emit('input', urls.join(','))
+			that.$emit('change', list)
+			uni.hideLoading()
+		},
+		// 设置域名
+		getUrl(url) {
+			if (url.indexOf('http') === -1) url = this.$UPLOAD_BASE + url
+			return url
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.upload_box {
+	position: relative;
+	.upload_btn_box {
+		position: relative;
+		z-index: 99;
+		.upload_btn_auth {
+			width: 96rpx;
+			height: 96rpx;
+		}
+		.upload_btn {
+			width: 96rpx;
+			height: 96rpx;
+			position: relative;
+		}
+	}
+
+	.upload_list {
+		.upload_item {
+			background: rgba(134, 158, 180, 0.1);
+			border-radius: 8rpx;
+			padding: 8rpx 24rpx 8rpx 8rpx;
+			margin-bottom: 24rpx;
+			position: relative;
+
+			.upload_left {
+				width: calc(100% - 48rpx);
+				padding-left: 16rpx;
+			}
+
+			.upload_content {
+				width: calc(100% - 96rpx);
+				padding: 0 16rpx;
+
+				.upload_name {
+					width: 100%;
+					word-break: break-all;
+					font-size: 28rpx;
+					color: #333333;
+					line-height: 40rpx;
+					margin-bottom: 8rpx;
+				}
+
+				.upload_text {
+					font-size: 24rpx;
+					color: #999999;
+					line-height: 34rpx;
+				}
+			}
+
+			.service_check {
+				position: absolute;
+				top: 0;
+				right: 0;
+				z-index: 10;
+				text-align: center;
+				padding: 6rpx;
+			}
+		}
+	}
+}
+</style>

+ 289 - 0
components/upload-image/upload-image.vue

@@ -0,0 +1,289 @@
+<template>
+	<view class="upload_box">
+		<!-- #ifdef APP -->
+		<template v-if="authList['WRITE_EXTERNAL_STORAGE']&&authList['CAMERA']">
+			<u-upload
+				:deletable="!isDetail"
+				:fileList="fileList1"
+				@afterRead="afterRead"
+				@delete="deletePic"
+				:name="name"
+				:multiple="multiple"
+				:maxCount="limit"
+				:width="width"
+				:height="height"
+				:previewFullImage="true"
+			>
+				<view class="upload_btn">
+					<u-image :radius="radius" :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/contract/icon_increase1-2.png`"></u-image>
+				</view>
+			</u-upload>
+		</template>
+		<view v-else-if="!authList['WRITE_EXTERNAL_STORAGE']" class="upload_btn_auth" @tap.stop="openAuth('WRITE_EXTERNAL_STORAGE')">
+			<view class="upload_btn">
+				<u-image :radius="radius" :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/common/upload-img.png`"></u-image>
+			</view>
+		</view>
+		<view v-else class="upload_btn_auth" @tap.stop="openAuth('CAMERA')">
+			<view class="upload_btn">
+				<u-image :radius="radius" :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/common/upload-img.png`"></u-image>
+			</view>
+		</view>
+		<yk-authpup ref="authpup" type="top" @changeAuth="changeAuth" :permissionID="permissionID"></yk-authpup>
+		<!-- #endif -->
+		<!-- #ifndef APP -->
+		<u-upload
+			:deletable="!isDetail"
+			:fileList="fileList1"
+			@afterRead="afterRead"
+			@delete="deletePic"
+			:name="name"
+			:multiple="multiple"
+			:maxCount="limit"
+			:width="width"
+			:height="height"
+			:radius="radius"
+			:previewFullImage="true"
+		>
+			<view class="upload_btn">
+				<u-image :radius="radius" :width="width" :height="height" :showError="false" :showLoading="false" :src="`${$IMG_URL}/static/common/upload-img.png`"></u-image>
+			</view>
+		</u-upload>
+		<!-- #endif -->
+	</view>
+</template>
+
+<script>
+import { mapGetters } from 'vuex'
+import ykAuthpup from '@/components/yk-authpup/yk-authpup'
+export default {
+	name: 'UploadImage',
+	components: {
+		ykAuthpup
+	},
+	props: {
+		// 值
+		value: [String, Object, Array],
+		// 宽度
+		width: {
+			type: String,
+			default: '180rpx'
+		},
+		// 高度
+		height: {
+			type: String,
+			default: '180rpx'
+		},
+		// 圆角
+		radius: {
+			type: String,
+			default: '12rpx'
+		},
+		// 数量限制
+		limit: {
+			type: Number,
+			default: 9
+		},
+		// 大小限制(MB)
+		fileSize: {
+			type: Number,
+			default: 20
+		},
+		// 文件类型, 例如['png', 'jpg', 'jpeg']
+		fileType: {
+			type: Array,
+			default: () => []
+		},
+		// 标识name
+		name: {
+			type: String,
+			default: '1'
+		},
+		// 是否多选
+		multiple: {
+			type: Boolean,
+			default: true
+		},
+		// 是否显示提示
+		isShowTip: {
+			type: Boolean,
+			default: false
+		},
+		// 是否详情
+		isDetail: {
+			type: Boolean,
+			default: false
+		},
+		// 是否在上传的时候回显图片
+		showImg: {
+			type: Boolean,
+			default: true
+		}
+	},
+	data() {
+		return {
+			// 权限判断
+			permissionID: '',
+			fileList1: [] //图片上传的文件
+		}
+	},
+	watch: {
+		value: {
+			async handler(val) {
+				if (val) {
+					if (this[`fileList${this.name}`].length > 0) return
+					// 首先将值转为数组
+					const list = Array.isArray(val) ? val : this.value.split(',')
+					const files = []
+					// 然后将数组转为对象数组
+					for (let i = 0; i < list.length; i++) {
+						let item = list[i]
+						if (typeof item === 'string') {
+							let name = item + ''
+							item = {
+								url: this.getUrl(item),
+								fullurl: item,
+								status: 'success'
+							}
+						}
+						files.push(item)
+					}
+					this[`fileList${this.name}`] = files
+				} else {
+					this[`fileList${this.name}`] = []
+					return []
+				}
+			},
+			deep: true,
+			immediate: true
+		}
+	},
+	computed: {
+		// 是否显示提示
+		showTip() {
+			return this.isShowTip && (this.fileType || this.fileSize)
+		},
+		...mapGetters('auth', ['authList', 'onceIn']),
+	},
+	mounted() {
+		// #ifdef APP
+		// if (this.onceIn) {
+		// 	this.$store.commit('auth/edit', {data: false, index: 'onceIn'})
+		// 	this.openAuth('WRITE_EXTERNAL_STORAGE')
+		// }
+		// #endif
+	},
+	methods: {
+		//打开自定义权限目的弹框
+		openAuth(permissionID) {
+			this.permissionID = permissionID //这个是对应的权限 ACCESS_FINE_LOCATION 位置权限 / WRITE_EXTERNAL_STORAGE 存储空间/照片权限 / CAMERA相机权限 / CALL_PHONE 拨打电话
+			setTimeout(() => {
+				this.$refs['authpup'].open()
+			}, 500)
+		},
+		//用户授权权限后的回调
+		changeAuth() {
+			//这里是权限通过后执行自己的代码逻辑
+			console.log('权限已授权,可执行自己的代码逻辑了')
+			this.authList[this.permissionID] = true
+			const list = {...this.authList}
+			this.$store.commit('auth/edit', {data: list, index: 'authList'})
+			if (!this.authList['CAMERA']) {
+				this.openAuth('CAMERA')
+			}
+		},
+		// 删除图片
+		deletePic(event) {
+			this[`fileList${event.name}`].splice(event.index, 1)
+			const files = this[`fileList${event.name}`].length ? this[`fileList${event.name}`].map((item) => item.fullurl).join(',') : ''
+			this.$emit('input', files)
+		},
+		// 新增图片
+		async afterRead(event) {
+			// 当设置 multiple 为 true 时, file 为数组格式,否则为对象格式
+			let lists = [].concat(event.file)
+			let fileListLen = this[`fileList${event.name}`].length
+			lists.map((item) => {
+				this[`fileList${event.name}`].push({
+					...item,
+					status: 'uploading',
+					message: '上传中'
+				})
+			})
+			let successNum = 0
+			let failNum = 0
+			for (let i = 0; i < lists.length; i++) {
+				const result = await this.uploadFilePromise(lists[i].url)
+				let item = this[`fileList${event.name}`][fileListLen]
+				if (result.code === 1) {
+					successNum++
+				} else {
+					failNum++
+				}
+				this[`fileList${event.name}`].splice(
+					fileListLen,
+					1,
+					Object.assign(item, {
+						status: result.code === 1 ? 'success' : 'fail',
+						message: result.code === 1 ? '' : '上传失败',
+						url: result.code === 1 ? result.data.fullurl : '',
+						fullurl: result.code === 1 ? result.data.url : ''
+					})
+				)
+				fileListLen++
+			}
+			if(!this.showImg) {
+				const files = this[`fileList${event.name}`].length ? this[`fileList${event.name}`][0] : ''
+				this[`fileList${event.name}`] = []
+				this.$emit('change',files)
+				return
+			}
+			if (lists.length > 1) {
+				uni.showModal({
+					content: `本次上传成功${successNum}个,失败${failNum}个`,
+					showCancel: false
+				})
+			}
+			this[`fileList${event.name}`] = this[`fileList${event.name}`].filter((item) => item.status === 'success')
+			const files = this[`fileList${event.name}`].length ? this[`fileList${event.name}`].map((item) => item.fullurl).join(',') : ''
+			this.$emit('input', files)
+		},
+		uploadFilePromise(url) {
+			return new Promise((resolve, reject) => {
+				let a = uni.uploadFile({
+					url: this.$UPLOAD_URL, //
+					filePath: url,
+					name: 'file',
+					header: {
+						token: `${this.$store.getters.token}`
+					},
+					success: (res) => {
+						let data = JSON.parse(res.data)
+						resolve(data)
+					}
+				})
+			})
+		},
+		// 设置域名
+		getUrl(url) {
+			if (url.indexOf('http') === -1) url = this.$UPLOAD_BASE + url
+			return url
+		}
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.upload_box {
+	position: relative;
+	.upload_btn_auth {
+		// width: 180rpx;
+		// height: 180rpx;
+	}
+	.upload_btn {
+		// width: 180rpx;
+		// height: 180rpx;
+		position: relative;
+	}
+}
+</style>

+ 329 - 0
components/yk-authpup/yk-authpup.vue

@@ -0,0 +1,329 @@
+<template>
+	<view v-if="showPopup&&popupShow" class="uni-popup" :style="{top:isNativeHead?'':StatusBar}" @click="close(true)">
+		<view :class="[type, ani, animation ? 'ani' : '']" class="uni-custom uni-popup__wrapper">
+			<view class="uni-popup__wrapper-box">
+				<view class="title">{{authList[permissionID].title}}</view>
+				<view class="content">{{authList[permissionID].content}}</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		name: 'YkAuthpup',
+		props: {
+			// 开启动画
+			animation: {
+				type: Boolean,
+				default: true
+			},
+			type: {
+				type: String,
+				default: 'top'
+			},
+			show: {
+				type: Boolean,
+				default: true
+			},
+			//是否是原生头部
+			isNativeHead:{
+				type: Boolean,
+				default: false
+			},
+			permissionID: {
+				type: [String, Number],
+				default: ''
+			}
+		},
+		data() {
+			return {
+				ani: '',
+				showPopup: false,
+				popupShow: true,
+				StatusBar:'',
+				refuseNum:'',//拒绝次数,
+				authList: {
+					'WRITE_EXTERNAL_STORAGE': {
+						title: "先见平台对存储空间/照片权限申请说明",
+						content: "便于您使用该功能上传您的照片/图片/视频及用于更换头像、意见反馈、保存相册、分享、下载与律师或客服沟通等场景中读取和写入相册和文件内容。"
+					},
+					'ACCESS_FINE_LOCATION': {
+						title: "先见平台对地理位置权限申请说明",
+						content: "便于应用程序可以提供基于位置的服务、定位导航、附近搜索等功能。"
+					},
+					'CAMERA':{
+						title: "先见平台对相机/摄像头权限申请说明",
+						content: "便于您使用该功能拍照上传您的照片/视频及用于更换头像、意见反馈、保存相册、发布动态、下载与律师或客服沟通等场景中使用"
+					},
+					'RECORD_AUDIO':{
+						title: "先见平台对麦克风权限申请说明",
+						content: "便于您使用该功能进行录音、语音通话、发布语音、与律师或客服语音沟通等场景中使用"
+					},
+					'CALL_PHONE': {
+						title: "先见平台对拨打/管理电话权限申请说明",
+						content: "便于您使用该功能联系律师或客服、业务经理与联系等场景下使用"
+					}
+				}
+			}
+		},
+		created() {
+			// #ifdef APP-PLUS
+			this.getSystemInfo();
+			// #endif
+		},
+		methods: {
+			//获取状态栏高度
+			getSystemInfo() {
+				let _this = this;
+				uni.getSystemInfo({
+					success: function(e) {
+						_this.StatusBar = e.statusBarHeight + 'px'; //用于自定义头部时,给手机状态栏留出位置,可通过isNativeHead这个参数控制
+					}
+				})
+			},
+			open() {
+				this.requestPermissions(this.permissionID);
+			},
+			close(type) {
+				this.ani = '';
+				this.showPopup = false;
+			},
+			//权限检测
+			requestPermissions(permissionID) {
+				let _this = this;
+				// #ifdef APP-PLUS
+				//判断安卓与ios设备
+				if (plus.os.name == 'Android') {
+					let _permissionID = 'android.permission.' + permissionID;
+					plus.android.checkPermission(_permissionID,
+						granted => {
+							if (granted.checkResult == -1) {
+								//还未授权当前查询的权限,打开权限申请目的自定义弹框
+								_this.showPopup = true;
+								_this.popupShow = true
+								_this.$nextTick(() => {
+									setTimeout(() => {
+										_this.ani = 'uni-' + _this.type
+									},30)
+								})
+							}
+						},
+						error => {
+							console.log(error.message);
+						}
+					);
+					plus.android.requestPermissions([_permissionID],
+						(e) => {
+							//关闭权限申请目的自定义弹框
+							_this.popupShow = true
+							console.log(e,'kkkkkkkk')
+							if (e.granted.length > 0) {
+								//当前查询权限已授权,此时可以通知页面执行接下来的操作
+								_this.popupShow = false
+								_this.$emit('changeAuth');
+							}
+							if (e.deniedAlways.length > 0) {
+								_this.showPopup = true
+								_this.$nextTick(() => {
+									setTimeout(() => {
+										_this.ani = 'uni-' + _this.type
+									},30)
+								})
+								//当前查询权限已被永久禁用,此时需要引导用户跳转手机系统设置去开启
+								uni.showModal({
+									title: '温馨提示',
+									content: '还没有该权限,立即去设置开启?',
+									cancelText: "取消",
+									confirmText: "去设置",
+									showCancel: true,
+									confirmColor: '#000',
+									cancelColor: '#666',
+									success: (res) => {
+										if (res.confirm) {
+											_this.goSetting();
+										}
+									},
+									complete() {
+										_this.popupShow = false
+									}
+								})
+							}
+						})
+				} else {
+					//IOS不需要添加自定义弹框来描述权限目的,因为在配置文件的隐私信息访问的许可描述里可添加
+					//正常可以直接调用uni的API调起权限询问弹框使用各种权限,下面的判断使用场景主要是在IOS禁用某权限后,这个可以判断有无权限,进而引导用户跳转设置开启,仅列出了位置、相册、通讯录、相机、录音等权限,其他IOS权限可具体参考 https://ext.dcloud.net.cn/plugin?id=15787
+					let result = 0;
+					if (permissionID == 'ACCESS_FINE_LOCATION') {
+						//IOS检测位置权限
+						let cLLocationManager = plus.ios.importClass("CLLocationManager"),
+							authStatus = cLLocationManager.authorizationStatus(),
+							enable = cLLocationManager.locationServicesEnabled();
+						if (enable && authStatus != 2) {
+							result = 1;
+						} else {
+							result = 0;
+						}
+						plus.ios.deleteObject(cLLocationManager);
+					} else if (permissionID == 'WRITE_EXTERNAL_STORAGE') {
+						//IOS检测相册权限
+						let PHPhotoLibrary = plus.ios.importClass("PHPhotoLibrary"),
+							authStatus = PHPhotoLibrary.authorizationStatus();
+						if (authStatus === 3) {
+							result = 1;
+						} else {
+							result = 0;
+						}
+						plus.ios.deleteObject(PHPhotoLibrary);
+					} else if (permissionID == 'CAMERA') {
+						//IOS检测相机/摄像头权限
+						let avCaptureDevice = plus.ios.importClass("AVCaptureDevice"),
+						    authStatus = avCaptureDevice.authorizationStatusForMediaType("vide");
+						if (authStatus === 3) {
+						  result = 1;
+						} else {
+						  result = 0;
+						}
+						plus.ios.deleteObject(avCaptureDevice);
+					} else if (permissionID == 'CALL_PHONE') {
+						//IOS检测通讯录权限
+						let contactStore = plus.ios.importClass("CNContactStore"),
+							authStatus = contactStore.authorizationStatusForEntityType(0);
+						if (authStatus === 3) {
+							result = 1;
+						} else {
+							result = 0;
+						}
+						plus.ios.deleteObject(contactStore);
+					}else if(permissionID == 'RECORD_AUDIO'){
+						//IOS检测麦克风权限
+						let aVAudioSession = plus.ios.importClass("AVAudioSession"),
+						  aVAudio = aVAudioSession.sharedInstance(),
+						  authStatus = aVAudio.recordPermission();
+						if ([1684369017, 1970168948].includes(authStatus)) {
+						  result = 0;
+						} else {
+						  result = 1;
+						}
+						plus.ios.deleteObject(aVAudioSession);
+					}
+					if (result) {
+						//当前查询权限已授权,此时可以通知页面执行接下来的操作
+						that.$emit('changeAuth')
+					} else {
+						//当前查询的权限已禁用,引导用户跳转手机系统设置去开启
+						uni.showModal({
+							title: '温馨提示',
+							content: '还没有该权限,立即去设置开启?',
+							cancelText: "取消",
+							confirmText: "去设置",
+							showCancel: true,
+							confirmColor: '#000',
+							cancelColor: '#666',
+							success: (res) => {
+								if (res.confirm) {
+									_this.goSetting();
+								}
+							}
+						})
+					}
+				}
+				// #endif
+			},
+			//跳转手机系统设置
+			goSetting() {
+				if (plus.os.name == "iOS") {
+					var UIApplication = plus.ios.import("UIApplication");
+					var application2 = UIApplication.sharedApplication();
+					var NSURL2 = plus.ios.import("NSURL");
+					var setting2 = NSURL2.URLWithString("app-settings:");
+					application2.openURL(setting2);
+					plus.ios.deleteObject(setting2);
+					plus.ios.deleteObject(NSURL2);
+					plus.ios.deleteObject(application2);
+				} else {
+					var Intent = plus.android.importClass("android.content.Intent");
+					var Settings = plus.android.importClass("android.provider.Settings");
+					var Uri = plus.android.importClass("android.net.Uri");
+					var mainActivity = plus.android.runtimeMainActivity();
+					var intent = new Intent();
+					intent.setAction(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
+					var uri = Uri.fromParts("package", mainActivity.getPackageName(), null);
+					intent.setData(uri);
+					mainActivity.startActivity(intent);
+				}
+			}
+		}
+	}
+</script>
+<style lang="scss">
+	.uni-popup {
+		position: fixed;
+		top: 0;
+		bottom: 0;
+		left: 0;
+		right: 0;
+		z-index: 999999;
+		overflow: hidden;
+		&__wrapper {
+			position: absolute;
+			z-index: 999;
+			/* #ifndef APP-NVUE */
+			box-sizing: border-box;
+			/* #endif */
+			&.ani {
+				/* #ifndef APP-NVUE */
+				transition: all 0.3s;
+				/* #endif */
+			}
+			&.top {
+				top: 0;
+				width:705rpx;
+				/* #ifdef APP-NVUE */
+				left:22.5rpx;
+				/* #endif */
+				/* #ifndef APP-NVUE */
+				left:0;
+				transform: translateY(-705rpx);
+				/* #endif */
+			}
+			&-box {
+				position: relative;
+				/* #ifndef APP-NVUE */
+				box-sizing: border-box;
+				/* #endif */
+			}
+			&.uni-custom {
+				& .uni-popup__wrapper-box {
+					width: 705rpx;
+					/* #ifndef APP-NVUE */
+					margin: 0 22.5rpx;
+					/* #endif */
+					padding: 30upx;
+					background: #fff;
+					border: solid 2rpx #ddd;
+					/* #ifndef APP-NVUE */
+					box-sizing: border-box;
+					/* #endif */
+					border-radius: 16rpx;
+					.title{
+						font-size: 32rpx;
+						font-weight: bold;
+					}
+					.content{
+						margin-top: 16rpx;
+						line-height: 1.6;
+					}
+				}
+				&.top{
+					& .uni-popup__wrapper-box {
+						width: 705rpx;
+					}
+				}
+			}
+			&.uni-top{
+				transform: translateY(0);
+			}
+		}
+	}
+</style>

+ 17 - 0
env.js

@@ -0,0 +1,17 @@
+/**
+ *  全局配置文件
+ */
+export const BASE_URL = process.env.NODE_ENV === "development" ? 'https://xxxx.com' : 'https://xxxx.com'
+
+
+// 上传路径
+export const UPLOAD_URL = `${BASE_URL}/api/common/upload`
+
+// 全局网络图片地址变量,css背景图片地址变量在uni.scss中定义
+export const UPLOAD_BASE = `https://xxx.com`
+export const IMG_URL = `${UPLOAD_BASE}`
+export const imgurl = ''
+
+
+//后台是否开通直播权限,根据情况在manifest.json中引入直播插件,并在pages.json中打开直播页面
+export const HAS_LIVE = false 

+ 21 - 0
index.html

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

+ 113 - 0
main.js

@@ -0,0 +1,113 @@
+import App from './App'
+import vueComposition from "@vue/composition-api" 
+Vue.use(vueComposition)
+// #ifndef VUE3
+import Vue from 'vue'
+import {
+	router,
+	RouterMount
+} from "@/common/router";
+import store from "@/common/store";
+import common from "@/common";
+import uView from '@/uni_modules/uview-ui'
+
+// 引入uView对小程序分享的mixin封装
+let mpShare = require('@/uni_modules/uview-ui/libs/mixin/mpShare.js');
+Vue.mixin(mpShare)
+
+Vue.config.productionTip = false
+App.mpType = 'app'
+
+// store处理
+Vue.prototype.$store = store
+
+//引入路由
+Vue.use(router);
+// 引入全局uView
+Vue.use(uView);
+// 加载common
+Vue.use(common);
+
+try {
+	function isPromise(obj) {
+		return (
+			!!obj &&
+			(typeof obj === "object" || typeof obj === "function") &&
+			typeof obj.then === "function"
+		);
+	}
+
+	// 统一 vue2 API Promise 化返回格式与 vue3 保持一致
+	uni.addInterceptor({
+		returnValue(res) {
+			if (!isPromise(res)) {
+				return res;
+			}
+			return new Promise((resolve, reject) => {
+				res.then((res) => {
+					if (res[0]) {
+						reject(res[0]);
+					} else {
+						resolve(res[1]);
+					}
+				});
+			});
+		},
+	});
+} catch (error) {}
+
+const app = new Vue({
+	...App
+})
+// 去除console
+if (process.env.NODE_ENV !== "development") {
+	console.log = () => {}
+}
+// http拦截器,将此部分放在new Vue()和app.$mount()之间,才能App.vue中正常使用
+require('@/common/request/index.js')(app)
+
+// 引入公共方法
+import { downloadFile, goChat, openFile, setCache, exportWord, filterSize, countChar, textFormat, accMul, accDiv, accAdd, accSub, parseDate } from '@/common/js/public.js'
+
+
+// #ifdef APP
+// app文件分享
+import {anyShareFile} from '@/common/js/appFileShare.js'
+Vue.prototype.$anyShareFile = anyShareFile
+// #endif
+
+Vue.prototype.$downloadFile = downloadFile
+Vue.prototype.$openFile = openFile
+Vue.prototype.$filterSize = filterSize
+Vue.prototype.$goChat = goChat
+Vue.prototype.$setCache = setCache
+Vue.prototype.$exportWord = exportWord
+Vue.prototype.$countChar = countChar
+Vue.prototype.$textFormat = textFormat
+Vue.prototype.$accMul = accMul
+Vue.prototype.$accDiv = accDiv
+Vue.prototype.$accAdd = accAdd
+Vue.prototype.$accSub = accSub
+Vue.prototype.$parseDate = parseDate
+
+
+// #ifdef H5
+RouterMount(app, router, "#app");
+// #endif
+// #ifndef H5
+app.$mount();
+// #endif
+
+// #endif
+
+// #ifdef VUE3
+import {
+	createSSRApp
+} from 'vue'
+export function createApp() {
+	const app = createSSRApp(App)
+	return {
+		app
+	}
+}
+// #endif

+ 222 - 0
manifest.json

@@ -0,0 +1,222 @@
+{
+    "name" : "pdaApp",
+    "appid" : "__UNI__A30DD8B",
+    "description" : "pdaApp",
+    "versionName" : "1.0.0",
+    "versionCode" : 100,
+    "transformPx" : false,
+    /* 5+App特有相关 */
+    "app-plus" : {
+        "usingComponents" : true,
+        "nvueStyleCompiler" : "uni-app",
+        "compilerVersion" : 3,
+        "splashscreen" : {
+            "alwaysShowBeforeRender" : true,
+            "waiting" : true,
+            "autoclose" : true,
+            "delay" : 0
+        },
+        "ssl" : {
+            "untrustedca" : "refuse"
+        },
+        /* 模块配置 */
+        "modules" : {
+            "Camera" : {},
+            "Geolocation" : {},
+            "LivePusher" : {},
+            "Maps" : {},
+            "VideoPlayer" : {},
+            "Record" : {},
+            "Payment" : {},
+            "Share" : {},
+            "OAuth" : {},
+            "Barcode" : {}
+        },
+        /* 应用发布信息 */
+        "distribute" : {
+            /* android打包配置 */
+            "android" : {
+                "permissions" : [
+                    "<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
+                    "<uses-permission android:name=\"android.permission.VIBRATE\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
+                    "<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CAMERA\"/>",
+                    "<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
+                    "<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
+                    "<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
+                    "<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
+                    "<uses-feature android:name=\"android.hardware.camera\"/>",
+                    "<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
+                    "<uses-permission android:name=\"android.permission.CALL_PHONE\"/>"
+                ],
+                "abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ],
+                "schemes" : "fapai",
+                "targetSdkVersion" : 30
+            },
+            /* ios打包配置 */
+            "ios" : {
+                "dSYMs" : false,
+                "privacyDescription" : {
+                    "NSPhotoLibraryUsageDescription" : ""
+                },
+                "capabilities" : {
+                    "entitlements" : {
+                        "com.apple.developer.associated-domains" : [
+                            "applinks:static-mp-4f07bc02-a8cc-4a76-8ce3-9af038d87d14.next.bspapp.com"
+                        ]
+                    }
+                }
+            },
+            /* SDK配置 */
+            "sdkConfigs" : {
+                "geolocation" : {
+                    "amap" : {
+                        "name" : "amap_13875387821Bii8XMrGp",
+                        "__platform__" : [ "ios", "android" ],
+                        "appkey_ios" : "16bea07ea42140aa2c4674df0e5272b9",
+                        "appkey_android" : "9ea9a2a74db73730b3ef47dbcb417783"
+                    }
+                },
+                "maps" : {
+                    "amap" : {
+                        "name" : "amap_13875387821Bii8XMrGp",
+                        "appkey_ios" : "16bea07ea42140aa2c4674df0e5272b9",
+                        "appkey_android" : "9ea9a2a74db73730b3ef47dbcb417783"
+                    }
+                },
+                "speech" : {},
+                "ad" : {},
+                "payment" : {
+                    "weixin" : {
+                        "__platform__" : [ "ios", "android" ],
+                        "appid" : "wx19e2eecb5e0dcb3d",
+                        "UniversalLinks" : "https://static-mp-4f07bc02-a8cc-4a76-8ce3-9af038d87d14.next.bspapp.com/uni-universallinks/__UNI__599FB9B/"
+                    }
+                },
+                "share" : {
+                    "weixin" : {
+                        "appid" : "wx19e2eecb5e0dcb3d",
+                        "UniversalLinks" : "https://static-mp-4f07bc02-a8cc-4a76-8ce3-9af038d87d14.next.bspapp.com/uni-universallinks/__UNI__599FB9B/"
+                    }
+                },
+                "oauth" : {
+                    "weixin" : {
+                        "appid" : "wx19e2eecb5e0dcb3d",
+                        "UniversalLinks" : "https://static-mp-4f07bc02-a8cc-4a76-8ce3-9af038d87d14.next.bspapp.com/uni-universallinks/__UNI__599FB9B/"
+                    }
+                }
+            },
+            "icons" : {
+                "android" : {
+                    "hdpi" : "",
+                    "xhdpi" : "",
+                    "xxhdpi" : "",
+                    "xxxhdpi" : ""
+                },
+                "ios" : {
+                    "appstore" : "",
+                    "ipad" : {
+                        "app" : "",
+                        "app@2x" : "",
+                        "notification" : "",
+                        "notification@2x" : "",
+                        "proapp@2x" : "",
+                        "settings" : "",
+                        "settings@2x" : "",
+                        "spotlight" : "",
+                        "spotlight@2x" : ""
+                    },
+                    "iphone" : {
+                        "app@2x" : "",
+                        "app@3x" : "",
+                        "notification@2x" : "",
+                        "notification@3x" : "",
+                        "settings@2x" : "",
+                        "settings@3x" : "",
+                        "spotlight@2x" : "",
+                        "spotlight@3x" : ""
+                    }
+                }
+            },
+            "splashscreen" : {
+                "androidStyle" : "default",
+                "android" : {
+                    "hdpi" : "",
+                    "xhdpi" : "",
+                    "xxhdpi" : ""
+                },
+                "useOriginalMsgbox" : true
+            }
+        },
+        "nativePlugins" : {
+            "life-FileShare" : {
+                "__plugin_info__" : {
+                    "name" : "安卓ios分享任意类型文件",
+                    "description" : "ios和安卓任意分享文件",
+                    "platforms" : "Android,iOS",
+                    "url" : "https://ext.dcloud.net.cn/plugin?id=2307",
+                    "android_package_name" : "yazyun.xianjianWeb",
+                    "ios_bundle_id" : "",
+                    "isCloud" : true,
+                    "bought" : 1,
+                    "pid" : "2307",
+                    "parameters" : {}
+                }
+            }
+        }
+    },
+    /* 快应用特有相关 */
+    "quickapp" : {},
+    /* 小程序特有相关 */
+    "mp-weixin" : {
+        "appid" : "",
+        "libVersion" : "latest",
+        "setting" : {
+            "urlCheck" : false,
+            "minified" : true,
+            "postcss" : true,
+            "es6" : true
+        },
+        "usingComponents" : true,
+        "permission" : {
+            "scope.userLocation" : {
+                "desc" : "您的位置信息将用于小程序位置接口的效果展示"
+            }
+        },
+        "requiredPrivateInfos" : [ "getLocation", "chooseLocation" ],
+        "optimization" : {
+            "subPackages" : true
+        }
+    },
+    "mp-alipay" : {
+        "usingComponents" : true
+    },
+    "mp-baidu" : {
+        "usingComponents" : true
+    },
+    "mp-toutiao" : {
+        "usingComponents" : true
+    },
+    "uniStatistics" : {
+        "enable" : false
+    },
+    "vueVersion" : "2",
+    "h5" : {
+        "title" : "先见平台",
+        "router" : {
+            "mode" : "history"
+        },
+        "template" : "template.h5.html",
+        "devServer" : {
+            "disableHostCheck" : true
+        }
+    },
+    "locale" : "zh-Hans",
+    "fallbackLocale" : "zh-Hans",
+    "_spaceID" : "mp-4f07bc02-a8cc-4a76-8ce3-9af038d87d14"
+}

+ 596 - 0
package-lock.json

@@ -0,0 +1,596 @@
+{
+  "name": "xianjianWeb",
+  "version": "1.0.0",
+  "lockfileVersion": 1,
+  "requires": true,
+  "dependencies": {
+    "@ampproject/remapping": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/@ampproject/remapping/-/remapping-2.3.0.tgz",
+      "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+      "requires": {
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "@antfu/utils": {
+      "version": "0.7.10",
+      "resolved": "https://registry.npmmirror.com/@antfu/utils/-/utils-0.7.10.tgz",
+      "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww=="
+    },
+    "@babel/code-frame": {
+      "version": "7.26.2",
+      "resolved": "https://registry.npmmirror.com/@babel/code-frame/-/code-frame-7.26.2.tgz",
+      "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==",
+      "requires": {
+        "@babel/helper-validator-identifier": "^7.25.9",
+        "js-tokens": "^4.0.0",
+        "picocolors": "^1.0.0"
+      }
+    },
+    "@babel/compat-data": {
+      "version": "7.26.8",
+      "resolved": "https://registry.npmmirror.com/@babel/compat-data/-/compat-data-7.26.8.tgz",
+      "integrity": "sha512-oH5UPLMWR3L2wEFLnFJ1TZXqHufiTKAiLfqw5zkhS4dKXLJ10yVztfil/twG8EDTA4F/tvVNw9nOl4ZMslB8rQ=="
+    },
+    "@babel/core": {
+      "version": "7.26.10",
+      "resolved": "https://registry.npmmirror.com/@babel/core/-/core-7.26.10.tgz",
+      "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==",
+      "requires": {
+        "@ampproject/remapping": "^2.2.0",
+        "@babel/code-frame": "^7.26.2",
+        "@babel/generator": "^7.26.10",
+        "@babel/helper-compilation-targets": "^7.26.5",
+        "@babel/helper-module-transforms": "^7.26.0",
+        "@babel/helpers": "^7.26.10",
+        "@babel/parser": "^7.26.10",
+        "@babel/template": "^7.26.9",
+        "@babel/traverse": "^7.26.10",
+        "@babel/types": "^7.26.10",
+        "convert-source-map": "^2.0.0",
+        "debug": "^4.1.0",
+        "gensync": "^1.0.0-beta.2",
+        "json5": "^2.2.3",
+        "semver": "^6.3.1"
+      }
+    },
+    "@babel/generator": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmmirror.com/@babel/generator/-/generator-7.27.0.tgz",
+      "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==",
+      "requires": {
+        "@babel/parser": "^7.27.0",
+        "@babel/types": "^7.27.0",
+        "@jridgewell/gen-mapping": "^0.3.5",
+        "@jridgewell/trace-mapping": "^0.3.25",
+        "jsesc": "^3.0.2"
+      }
+    },
+    "@babel/helper-compilation-targets": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.27.0.tgz",
+      "integrity": "sha512-LVk7fbXml0H2xH34dFzKQ7TDZ2G4/rVTOrq9V+icbbadjbVxxeFeDsNHv2SrZeWoA+6ZiTyWYWtScEIW07EAcA==",
+      "requires": {
+        "@babel/compat-data": "^7.26.8",
+        "@babel/helper-validator-option": "^7.25.9",
+        "browserslist": "^4.24.0",
+        "lru-cache": "^5.1.1",
+        "semver": "^6.3.1"
+      }
+    },
+    "@babel/helper-module-imports": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz",
+      "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==",
+      "requires": {
+        "@babel/traverse": "^7.25.9",
+        "@babel/types": "^7.25.9"
+      }
+    },
+    "@babel/helper-module-transforms": {
+      "version": "7.26.0",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz",
+      "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==",
+      "requires": {
+        "@babel/helper-module-imports": "^7.25.9",
+        "@babel/helper-validator-identifier": "^7.25.9",
+        "@babel/traverse": "^7.25.9"
+      }
+    },
+    "@babel/helper-string-parser": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz",
+      "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA=="
+    },
+    "@babel/helper-validator-identifier": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz",
+      "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ=="
+    },
+    "@babel/helper-validator-option": {
+      "version": "7.25.9",
+      "resolved": "https://registry.npmmirror.com/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz",
+      "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw=="
+    },
+    "@babel/helpers": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmmirror.com/@babel/helpers/-/helpers-7.27.0.tgz",
+      "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==",
+      "requires": {
+        "@babel/template": "^7.27.0",
+        "@babel/types": "^7.27.0"
+      }
+    },
+    "@babel/parser": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmmirror.com/@babel/parser/-/parser-7.27.0.tgz",
+      "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==",
+      "requires": {
+        "@babel/types": "^7.27.0"
+      }
+    },
+    "@babel/template": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmmirror.com/@babel/template/-/template-7.27.0.tgz",
+      "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==",
+      "requires": {
+        "@babel/code-frame": "^7.26.2",
+        "@babel/parser": "^7.27.0",
+        "@babel/types": "^7.27.0"
+      }
+    },
+    "@babel/traverse": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmmirror.com/@babel/traverse/-/traverse-7.27.0.tgz",
+      "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==",
+      "requires": {
+        "@babel/code-frame": "^7.26.2",
+        "@babel/generator": "^7.27.0",
+        "@babel/parser": "^7.27.0",
+        "@babel/template": "^7.27.0",
+        "@babel/types": "^7.27.0",
+        "debug": "^4.3.1",
+        "globals": "^11.1.0"
+      }
+    },
+    "@babel/types": {
+      "version": "7.27.0",
+      "resolved": "https://registry.npmmirror.com/@babel/types/-/types-7.27.0.tgz",
+      "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==",
+      "requires": {
+        "@babel/helper-string-parser": "^7.25.9",
+        "@babel/helper-validator-identifier": "^7.25.9"
+      }
+    },
+    "@jridgewell/gen-mapping": {
+      "version": "0.3.8",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.8.tgz",
+      "integrity": "sha512-imAbBGkb+ebQyxKgzv5Hu2nmROxoDOXHh80evxdoXNOrvAnVx7zimzc1Oo5h9RlfV4vPXaE2iM5pOFbvOCClWA==",
+      "requires": {
+        "@jridgewell/set-array": "^1.2.1",
+        "@jridgewell/sourcemap-codec": "^1.4.10",
+        "@jridgewell/trace-mapping": "^0.3.24"
+      }
+    },
+    "@jridgewell/resolve-uri": {
+      "version": "3.1.2",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+      "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw=="
+    },
+    "@jridgewell/set-array": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/set-array/-/set-array-1.2.1.tgz",
+      "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A=="
+    },
+    "@jridgewell/sourcemap-codec": {
+      "version": "1.5.0",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz",
+      "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ=="
+    },
+    "@jridgewell/trace-mapping": {
+      "version": "0.3.25",
+      "resolved": "https://registry.npmmirror.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz",
+      "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==",
+      "requires": {
+        "@jridgewell/resolve-uri": "^3.1.0",
+        "@jridgewell/sourcemap-codec": "^1.4.14"
+      }
+    },
+    "@rollup/pluginutils": {
+      "version": "5.1.4",
+      "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.1.4.tgz",
+      "integrity": "sha512-USm05zrsFxYLPdWWq+K3STlWiT/3ELn3RcV5hJMghpeAIhxfsUIg6mt12CBJBInWMV4VneoV7SfGv8xIwo2qNQ==",
+      "requires": {
+        "@types/estree": "^1.0.0",
+        "estree-walker": "^2.0.2",
+        "picomatch": "^4.0.2"
+      }
+    },
+    "@trysound/sax": {
+      "version": "0.2.0",
+      "resolved": "https://registry.npmmirror.com/@trysound/sax/-/sax-0.2.0.tgz",
+      "integrity": "sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA=="
+    },
+    "@types/estree": {
+      "version": "1.0.7",
+      "resolved": "https://registry.npmmirror.com/@types/estree/-/estree-1.0.7.tgz",
+      "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ=="
+    },
+    "@vue/compiler-core": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.13.tgz",
+      "integrity": "sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==",
+      "requires": {
+        "@babel/parser": "^7.25.3",
+        "@vue/shared": "3.5.13",
+        "entities": "^4.5.0",
+        "estree-walker": "^2.0.2",
+        "source-map-js": "^1.2.0"
+      }
+    },
+    "@vue/compiler-dom": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.13.tgz",
+      "integrity": "sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==",
+      "requires": {
+        "@vue/compiler-core": "3.5.13",
+        "@vue/shared": "3.5.13"
+      }
+    },
+    "@vue/reactivity-transform": {
+      "version": "3.3.13",
+      "resolved": "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.13.tgz",
+      "integrity": "sha512-oWnydGH0bBauhXvh5KXUy61xr9gKaMbtsMHk40IK9M4gMuKPJ342tKFarY0eQ6jef8906m35q37wwA8DMZOm5Q==",
+      "requires": {
+        "@babel/parser": "^7.23.5",
+        "@vue/compiler-core": "3.3.13",
+        "@vue/shared": "3.3.13",
+        "estree-walker": "^2.0.2",
+        "magic-string": "^0.30.5"
+      },
+      "dependencies": {
+        "@vue/compiler-core": {
+          "version": "3.3.13",
+          "resolved": "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.13.tgz",
+          "integrity": "sha512-bwi9HShGu7uaZLOErZgsH2+ojsEdsjerbf2cMXPwmvcgZfVPZ2BVZzCVnwZBxTAYd6Mzbmf6izcUNDkWnBBQ6A==",
+          "requires": {
+            "@babel/parser": "^7.23.5",
+            "@vue/shared": "3.3.13",
+            "estree-walker": "^2.0.2",
+            "source-map-js": "^1.0.2"
+          }
+        },
+        "@vue/shared": {
+          "version": "3.3.13",
+          "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.13.tgz",
+          "integrity": "sha512-/zYUwiHD8j7gKx2argXEMCUXVST6q/21DFU0sTfNX0URJroCe3b1UF6vLJ3lQDfLNIiiRl2ONp7Nh5UVWS6QnA=="
+        }
+      }
+    },
+    "@vue/shared": {
+      "version": "3.5.13",
+      "resolved": "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.13.tgz",
+      "integrity": "sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ=="
+    },
+    "acorn": {
+      "version": "8.14.1",
+      "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
+      "integrity": "sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg=="
+    },
+    "boolbase": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmmirror.com/boolbase/-/boolbase-1.0.0.tgz",
+      "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="
+    },
+    "browserslist": {
+      "version": "4.24.4",
+      "resolved": "https://registry.npmmirror.com/browserslist/-/browserslist-4.24.4.tgz",
+      "integrity": "sha512-KDi1Ny1gSePi1vm0q4oxSF8b4DR44GF4BbmS2YdhPLOEqd8pDviZOGH/GsmRwoWJ2+5Lr085X7naowMwKHDG1A==",
+      "requires": {
+        "caniuse-lite": "^1.0.30001688",
+        "electron-to-chromium": "^1.5.73",
+        "node-releases": "^2.0.19",
+        "update-browserslist-db": "^1.1.1"
+      }
+    },
+    "caniuse-lite": {
+      "version": "1.0.30001712",
+      "resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001712.tgz",
+      "integrity": "sha512-MBqPpGYYdQ7/hfKiet9SCI+nmN5/hp4ZzveOJubl5DTAMa5oggjAuoi0Z4onBpKPFI2ePGnQuQIzF3VxDjDJig=="
+    },
+    "commander": {
+      "version": "7.2.0",
+      "resolved": "https://registry.npmmirror.com/commander/-/commander-7.2.0.tgz",
+      "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="
+    },
+    "convert-source-map": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/convert-source-map/-/convert-source-map-2.0.0.tgz",
+      "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg=="
+    },
+    "css-select": {
+      "version": "5.1.0",
+      "resolved": "https://registry.npmmirror.com/css-select/-/css-select-5.1.0.tgz",
+      "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==",
+      "requires": {
+        "boolbase": "^1.0.0",
+        "css-what": "^6.1.0",
+        "domhandler": "^5.0.2",
+        "domutils": "^3.0.1",
+        "nth-check": "^2.0.1"
+      }
+    },
+    "css-tree": {
+      "version": "2.3.1",
+      "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-2.3.1.tgz",
+      "integrity": "sha512-6Fv1DV/TYw//QF5IzQdqsNDjx/wc8TrMBZsqjL9eW01tWb7R7k/mq+/VXfJCl7SoD5emsJop9cOByJZfs8hYIw==",
+      "requires": {
+        "mdn-data": "2.0.30",
+        "source-map-js": "^1.0.1"
+      }
+    },
+    "css-what": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmmirror.com/css-what/-/css-what-6.1.0.tgz",
+      "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw=="
+    },
+    "csso": {
+      "version": "5.0.5",
+      "resolved": "https://registry.npmmirror.com/csso/-/csso-5.0.5.tgz",
+      "integrity": "sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==",
+      "requires": {
+        "css-tree": "~2.2.0"
+      },
+      "dependencies": {
+        "css-tree": {
+          "version": "2.2.1",
+          "resolved": "https://registry.npmmirror.com/css-tree/-/css-tree-2.2.1.tgz",
+          "integrity": "sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==",
+          "requires": {
+            "mdn-data": "2.0.28",
+            "source-map-js": "^1.0.1"
+          }
+        },
+        "mdn-data": {
+          "version": "2.0.28",
+          "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.28.tgz",
+          "integrity": "sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g=="
+        }
+      }
+    },
+    "debug": {
+      "version": "4.4.0",
+      "resolved": "https://registry.npmmirror.com/debug/-/debug-4.4.0.tgz",
+      "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==",
+      "requires": {
+        "ms": "^2.1.3"
+      }
+    },
+    "defu": {
+      "version": "6.1.4",
+      "resolved": "https://registry.npmmirror.com/defu/-/defu-6.1.4.tgz",
+      "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg=="
+    },
+    "dom-serializer": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmmirror.com/dom-serializer/-/dom-serializer-2.0.0.tgz",
+      "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+      "requires": {
+        "domelementtype": "^2.3.0",
+        "domhandler": "^5.0.2",
+        "entities": "^4.2.0"
+      }
+    },
+    "domelementtype": {
+      "version": "2.3.0",
+      "resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-2.3.0.tgz",
+      "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
+    },
+    "domhandler": {
+      "version": "5.0.3",
+      "resolved": "https://registry.npmmirror.com/domhandler/-/domhandler-5.0.3.tgz",
+      "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+      "requires": {
+        "domelementtype": "^2.3.0"
+      }
+    },
+    "domutils": {
+      "version": "3.2.2",
+      "resolved": "https://registry.npmmirror.com/domutils/-/domutils-3.2.2.tgz",
+      "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
+      "requires": {
+        "dom-serializer": "^2.0.0",
+        "domelementtype": "^2.3.0",
+        "domhandler": "^5.0.3"
+      }
+    },
+    "electron-to-chromium": {
+      "version": "1.5.134",
+      "resolved": "https://registry.npmmirror.com/electron-to-chromium/-/electron-to-chromium-1.5.134.tgz",
+      "integrity": "sha512-zSwzrLg3jNP3bwsLqWHmS5z2nIOQ5ngMnfMZOWWtXnqqQkPVyOipxK98w+1beLw1TB+EImPNcG8wVP/cLVs2Og=="
+    },
+    "entities": {
+      "version": "4.5.0",
+      "resolved": "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz",
+      "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="
+    },
+    "escalade": {
+      "version": "3.2.0",
+      "resolved": "https://registry.npmmirror.com/escalade/-/escalade-3.2.0.tgz",
+      "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA=="
+    },
+    "estree-walker": {
+      "version": "2.0.2",
+      "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz",
+      "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+    },
+    "gensync": {
+      "version": "1.0.0-beta.2",
+      "resolved": "https://registry.npmmirror.com/gensync/-/gensync-1.0.0-beta.2.tgz",
+      "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
+    },
+    "globals": {
+      "version": "11.12.0",
+      "resolved": "https://registry.npmmirror.com/globals/-/globals-11.12.0.tgz",
+      "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+    },
+    "js-base64": {
+      "version": "3.7.7",
+      "resolved": "https://registry.npmmirror.com/js-base64/-/js-base64-3.7.7.tgz",
+      "integrity": "sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw=="
+    },
+    "js-tokens": {
+      "version": "4.0.0",
+      "resolved": "https://registry.npmmirror.com/js-tokens/-/js-tokens-4.0.0.tgz",
+      "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+    },
+    "jsesc": {
+      "version": "3.1.0",
+      "resolved": "https://registry.npmmirror.com/jsesc/-/jsesc-3.1.0.tgz",
+      "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="
+    },
+    "json5": {
+      "version": "2.2.3",
+      "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz",
+      "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg=="
+    },
+    "lru-cache": {
+      "version": "5.1.1",
+      "resolved": "https://registry.npmmirror.com/lru-cache/-/lru-cache-5.1.1.tgz",
+      "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+      "requires": {
+        "yallist": "^3.0.2"
+      }
+    },
+    "magic-string": {
+      "version": "0.30.17",
+      "resolved": "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.17.tgz",
+      "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==",
+      "requires": {
+        "@jridgewell/sourcemap-codec": "^1.5.0"
+      }
+    },
+    "mdn-data": {
+      "version": "2.0.30",
+      "resolved": "https://registry.npmmirror.com/mdn-data/-/mdn-data-2.0.30.tgz",
+      "integrity": "sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA=="
+    },
+    "ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
+    },
+    "node-releases": {
+      "version": "2.0.19",
+      "resolved": "https://registry.npmmirror.com/node-releases/-/node-releases-2.0.19.tgz",
+      "integrity": "sha512-xxOWJsBKtzAq7DY0J+DTzuz58K8e7sJbdgwkbMWQe8UYB6ekmsQ45q0M/tJDsGaZmbC+l7n57UV8Hl5tHxO9uw=="
+    },
+    "nth-check": {
+      "version": "2.1.1",
+      "resolved": "https://registry.npmmirror.com/nth-check/-/nth-check-2.1.1.tgz",
+      "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+      "requires": {
+        "boolbase": "^1.0.0"
+      }
+    },
+    "picocolors": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz",
+      "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="
+    },
+    "picomatch": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.2.tgz",
+      "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg=="
+    },
+    "semver": {
+      "version": "6.3.1",
+      "resolved": "https://registry.npmmirror.com/semver/-/semver-6.3.1.tgz",
+      "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="
+    },
+    "source-map-js": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz",
+      "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="
+    },
+    "svgo": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmmirror.com/svgo/-/svgo-3.3.2.tgz",
+      "integrity": "sha512-OoohrmuUlBs8B8o6MB2Aevn+pRIH9zDALSR+6hhqVfa6fRwG/Qw9VUMSMW9VNg2CFc/MTIfabtdOVl9ODIJjpw==",
+      "requires": {
+        "@trysound/sax": "0.2.0",
+        "commander": "^7.2.0",
+        "css-select": "^5.1.0",
+        "css-tree": "^2.3.1",
+        "css-what": "^6.1.0",
+        "csso": "^5.0.5",
+        "picocolors": "^1.0.0"
+      }
+    },
+    "uni-read-pages": {
+      "version": "1.0.5",
+      "resolved": "https://registry.npmmirror.com/uni-read-pages/-/uni-read-pages-1.0.5.tgz",
+      "integrity": "sha512-GkrrZ0LX0vn9R5k6RKEi0Ez3Q3e2vUpjXQ8Z6/K/d28KudI9ajqgt8WEjQFlG5EPm1K6uTArN8LlqmZTEixDUA=="
+    },
+    "uni-simple-router": {
+      "version": "2.0.7",
+      "resolved": "https://registry.npmmirror.com/uni-simple-router/-/uni-simple-router-2.0.7.tgz",
+      "integrity": "sha512-8FKv5dw7Eoonm0gkO8udprrxzin0fNUI0+AvIphFkFRH5ZmP5ZWJ2pvnWzb2NiiqQSECTSU5VSB7HhvOSwD5eA=="
+    },
+    "unplugin": {
+      "version": "1.16.1",
+      "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz",
+      "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==",
+      "requires": {
+        "acorn": "^8.14.0",
+        "webpack-virtual-modules": "^0.6.2"
+      }
+    },
+    "unplugin-vue2-script-setup": {
+      "version": "0.11.4",
+      "resolved": "https://registry.npmmirror.com/unplugin-vue2-script-setup/-/unplugin-vue2-script-setup-0.11.4.tgz",
+      "integrity": "sha512-HOJn7PwKBq36vvtAkx2QXpmMmXk9owIX4H77d4cF5RpiPuSY/u4P3nWF6a276LHrBGtb0/UHmCbeUeGeeJ1DWg==",
+      "requires": {
+        "@antfu/utils": "^0.7.4",
+        "@babel/core": "^7.22.1",
+        "@babel/generator": "^7.22.3",
+        "@babel/parser": "^7.22.4",
+        "@babel/traverse": "^7.22.4",
+        "@babel/types": "^7.22.4",
+        "@rollup/pluginutils": "^5.0.2",
+        "@vue/compiler-core": "^3.3.4",
+        "@vue/compiler-dom": "^3.3.4",
+        "@vue/reactivity-transform": "^3.3.4",
+        "@vue/shared": "^3.3.4",
+        "defu": "^6.1.2",
+        "magic-string": "^0.30.0",
+        "unplugin": "^1.3.1"
+      }
+    },
+    "update-browserslist-db": {
+      "version": "1.1.3",
+      "resolved": "https://registry.npmmirror.com/update-browserslist-db/-/update-browserslist-db-1.1.3.tgz",
+      "integrity": "sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==",
+      "requires": {
+        "escalade": "^3.2.0",
+        "picocolors": "^1.1.1"
+      }
+    },
+    "webpack-virtual-modules": {
+      "version": "0.6.2",
+      "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz",
+      "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ=="
+    },
+    "weixin-js-sdk": {
+      "version": "1.6.5",
+      "resolved": "https://registry.npmmirror.com/weixin-js-sdk/-/weixin-js-sdk-1.6.5.tgz",
+      "integrity": "sha512-Gph1WAWB2YN/lMOFB/ymb+hbU/wYazzJgu6PMMktCy9cSCeW5wA6Zwt0dpahJbJ+RJEwtTv2x9iIu0U4enuVSQ=="
+    },
+    "yallist": {
+      "version": "3.1.1",
+      "resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
+      "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g=="
+    }
+  }
+}

+ 25 - 0
package.json

@@ -0,0 +1,25 @@
+{
+  "name": "xianjianWeb",
+  "version": "1.0.0",
+  "description": "",
+  "main": "main.js",
+  "scripts": {
+    "test": "echo \"Error: no test specified\" && exit 1",
+    "svgicon": "node ./uni_modules/zui-svg-icon/tools/generate-svg-icon.js"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://git.code.tencent.com/yazyun/web/2023/xianjianWeb.git"
+  },
+  "keywords": [],
+  "author": "",
+  "license": "ISC",
+  "dependencies": {
+    "js-base64": "^3.7.5",
+    "svgo": "^3.3.2",
+    "uni-read-pages": "^1.0.5",
+    "uni-simple-router": "^2.0.6",
+    "unplugin-vue2-script-setup": "^0.11.4",
+    "weixin-js-sdk": "^1.6.5"
+  }
+}

+ 45 - 0
pages.json

@@ -0,0 +1,45 @@
+{
+	"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
+		{
+			"path": "pages/index/index",
+			"style": {
+				"navigationBarTitleText": "首页",
+				"navigationStyle": "default"
+			},
+			"meta": {
+				"auth": false,
+				"sync": true,
+				"title": "推荐",
+				"group": "平台"
+			}
+		},
+		{
+			"path": "pages/index/app-update",
+			"style": {
+				"navigationBarTitleText": ""
+			}
+		}
+	],
+	/* 分包预载配置 */
+	// "preloadRule": {
+	// 	"pages/index/index": {
+	// 		"network": "all",
+	// 		"packages": ["pages/account", "pages/app", "pages/user"]
+	// 	}
+	// },
+	"tabBar": {
+		"backgroundColor": "#FFFFFF",
+		"borderStyle": "white",
+		"color": "#BEBEBE",
+		"selectedColor": "#323232",
+		"list": []
+	},
+	"globalStyle": {
+		"navigationBarTextStyle": "black",
+		"navigationBarTitleText": "先见平台",
+		"navigationBarBackgroundColor": "#FAFAFC",
+		"backgroundColor": "#FAFAFC",
+		"navigationStyle": "custom"
+	},
+	"uniIdRouter": {}
+}

+ 335 - 0
pages/index/app-update.vue

@@ -0,0 +1,335 @@
+<template>
+	<view class="page-height">
+		<view class="page-content">
+			<view class="wrap" v-if="popup_show">
+				<view class="popup-bg">
+					<view class="popup-content" :class="{'popup-content-show' : popup_show}">
+						<view class="update-wrap">
+							<image :src="`${$IMG_URL}/static/common/img.png`" class="top-img"></image>
+							<view class="content">
+								<text class="title">发现新版本V{{update_info.version}}</text>
+								<!-- 升级描述 -->
+								<view class="title-sub" v-html="update_info.note"></view>
+								<!-- 升级按钮 -->
+								<button class="btn" v-if="downstatus < 1" @click="onUpdate()">立即升级</button>
+								<!-- 下载进度 -->
+								<view class="sche-wrap" v-else>
+									<!-- 更新包下载中 -->
+									<view class="sche-bg">
+										<view class="sche-bg-jindu" :style="lengthWidth"></view>
+									</view>
+									<text class="down-text">下载进度:{{(downSize/1024/1024 ).toFixed(2)}}M/{{(fileSize/1024/1024).toFixed(2)}}M</text>
+								</view>
+							</view>
+						</view>
+						<image :src="`${$IMG_URL}/static/common/close.png`" class="close-ioc" @click="closeUpdate()" v-if="downstatus < 1 && update_info.force == 0"></image>
+					</view>
+				</view>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	export default {
+		data() {
+			return {
+				popup_show: true,
+				update_info: null, //上一页面传过来的升级参数
+				note: [], //升级说明数组格式
+				fileSize: 0, //文件大小
+				downSize: 0, //已下载大小
+				downing: false, //是否下载中
+				downstatus: 0, //0未下载  1已开始 2已连接到资源  3已接收到数据  4下载完成
+			}
+		},
+		onLoad() {
+			const option = this.$Route.query
+			if (option.updata_info) {
+				this.update_info = option.updata_info.version ? option.updata_info : JSON.parse(option.updata_info)
+				this.note = this.update_info.note.split("\n"); //版本说明
+			} else {
+				plus.nativeUI.toast("参数出错");
+				setTimeout(() => {
+					uni.navigateBack()
+				}, 500)
+			}
+
+		},
+		onBackPress(e) {
+			if (e.from == "backbutton") return true; //APP安卓物理返回键逻辑
+		},
+		computed: {
+			// 下载进度计算
+			lengthWidth: function() {
+				let w = this.downSize / this.fileSize * 100;
+				if (!w) {
+					w = 0
+				} else {
+					w = w.toFixed(2)
+				}
+				return {
+					width: w + "%" //return 宽度半分比
+				}
+			},
+			getHeight: function() {
+				let bottom = 0;
+				if (this.tabbar) {
+					bottom = 50;
+				}
+				return {
+					"bottom": bottom + 'px',
+					"height": "auto"
+				}
+			}
+		},
+		methods: {
+			// 当点击更新时
+			onUpdate() {
+				//判断是否为WIFI网络 并且是非强制更新
+				if (this.update_info.net_check == 1 && this.update_info.net_check == 0) {
+					//判断是否为wifi模式
+					uni.getNetworkType({
+						success: (res) => {
+							if (res.networkType == "wifi") {
+								this.startUpdate(); //开始更新
+							} else {
+								uni.showModal({
+									title: '提示',
+									content: '当前网络非WIFI,继续更新可能会产生流量,确认要更新吗?',
+									success: (modal_res) => {
+										if (modal_res.confirm) {
+											this.startUpdate(); //开始更新
+										}
+									}
+								});
+							}
+						}
+					});
+				} else {
+					this.startUpdate(); //开始更新
+				}
+			},
+			//开始更新
+			startUpdate() {
+				if (this.downing) return false; //如果正在下载就停止操作
+				this.downing = true; //状态改变 正在下载中
+				if (/\.apk$/.test(this.update_info.now_url)) {
+					// 如果是apk地址
+					this.download_wgt() // 安装包/升级包更新
+				} else if (/\.wgt$/.test(this.update_info.now_url)) {
+					// 如果是更新包
+					this.download_wgt() // 安装包/升级包更新
+				} else {
+					plus.runtime.openURL(this.update_info.now_url, function() { //调用外部浏览器打开更新地址
+						plus.nativeUI.toast("打开错误");
+					});
+				}
+			},
+			// 下载升级资源包
+			download_wgt() {
+				plus.nativeUI.showWaiting("下载更新文件..."); //下载更新文件...
+				let options = {
+					method: "get"
+				};
+				let dtask = plus.downloader.createDownload(this.update_info.now_url, options);
+				dtask.addEventListener("statechanged", (task, status) => {
+					if (status === null) {} else if (status == 200) {
+						//在这里打印会不停的执行,请注意,正式上线切记不要在这里打印东西!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
+						this.downstatus = task.state;
+						switch (task.state) {
+							case 3: // 已接收到数据  
+								plus.nativeUI.closeWaiting();
+								this.downSize = task.downloadedSize;
+								if (task.totalSize) {
+									this.fileSize = task.totalSize; //服务器须返回正确的content-length才会有长度
+								}
+								break;
+							case 4:
+								this.installWgt(task.filename); // 安装  
+								break;
+						}
+					} else {
+						plus.nativeUI.closeWaiting();
+						plus.nativeUI.toast("下载出错");
+						this.downing = false;
+						this.downstatus = 0;
+					}
+				});
+				dtask.start();
+			},
+			// 安装文件
+			installWgt(path) {
+				plus.nativeUI.showWaiting("安装更新文件..."); //安装更新文件...
+				plus.runtime.install(path, {}, function() {
+					plus.nativeUI.closeWaiting();
+					// 应用资源下载完成!
+					plus.nativeUI.alert("更新完成,请重启APP!", function() {
+						plus.runtime.restart(); //重启APP
+					});
+				}, function(e) {
+					plus.nativeUI.closeWaiting();
+					// 安装更新文件失败
+					plus.nativeUI.alert("安装更新文件失败[" + e.code + "]:" + e.message);
+				});
+			},
+			// 取消更新
+			closeUpdate() {
+				uni.setStorageSync("update_ignore", this.update_info.version);
+				uni.navigateBack()
+			},
+		}
+	}
+</script>
+
+<style lang="scss" scoped>
+	page {
+		background: transparent;
+	}
+	.page-height {
+		height: 100vh;
+		display: flex;
+		flex-direction: column;
+		justify-content: center;
+		align-items: center;
+		background-color: rgba($color: #000000, $alpha: .7);
+	}
+
+	.popup-bg {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: center;
+		position: fixed;
+		top: 0;
+		left: 0;
+		right: 0;
+		bottom: 0;
+		width: 750rpx;
+	}
+
+	.popup-content {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+	}
+
+	.popup-content-show {
+		animation: mymove 300ms;
+		transform: scale(1);
+	}
+
+	@keyframes mymove {
+		0% {
+			transform: scale(0);
+			/*开始为原始大小*/
+		}
+
+		100% {
+			transform: scale(1);
+		}
+
+	}
+
+
+
+	.update-wrap {
+		width: 580rpx;
+		border-radius: 18rpx;
+		position: relative;
+		display: flex;
+		flex-direction: column;
+		background-color: #ffffff;
+		padding: 170rpx 30rpx 0;
+
+		.top-img {
+			position: absolute;
+			left: 0;
+			width: 100%;
+			height: 256rpx;
+			top: -128rpx;
+		}
+
+		.content {
+			display: flex;
+			flex-direction: column;
+			align-items: center;
+			padding-bottom: 40rpx;
+
+			.title {
+				font-size: 32rpx;
+				font-weight: bold;
+				color: #6526f3;
+			}
+
+			.title-sub {
+				text-align: center;
+				font-size: 24rpx;
+				color: #666666;
+				padding: 30rpx 0;
+			}
+
+			.btn {
+				width: 460rpx;
+				display: flex;
+				align-items: center;
+				justify-content: center;
+				color: #ffffff;
+				font-size: 30rpx;
+				height: 80rpx;
+				line-height: 80rpx;
+				border-radius: 100px;
+				background-color: #6526f3;
+				margin-top: 20rpx;
+			}
+		}
+	}
+
+
+	.close-ioc {
+		width: 70rpx;
+		height: 70rpx;
+		margin-top: 30rpx;
+	}
+
+	.sche-wrap {
+		display: flex;
+		flex-direction: column;
+		align-items: center;
+		justify-content: flex-end;
+		padding: 10rpx 50rpx 0;
+
+		.sche-wrap-text {
+			font-size: 24rpx;
+			color: #666;
+			margin-bottom: 20rpx;
+		}
+
+		.sche-bg {
+			position: relative;
+			background-color: #cccccc;
+			height: 30rpx;
+			border-radius: 100px;
+			width: 480rpx;
+			display: flex;
+			align-items: center;
+
+			.sche-bg-jindu {
+				position: absolute;
+				left: 0;
+				top: 0;
+				height: 30rpx;
+				min-width: 40rpx;
+				border-radius: 100px;
+				background: url($IMG_URL+'/static/common/round.png') #5775e7 center right 4rpx no-repeat;
+				background-size: 26rpx 26rpx;
+			}
+		}
+
+		.down-text {
+			font-size: 24rpx;
+			color: #5674e5;
+			margin-top: 16rpx;
+		}
+	}
+</style>

+ 48 - 0
pages/index/index.vue

@@ -0,0 +1,48 @@
+<template>
+	<view class="container_box">
+		<!-- <u-navbar height="90rpx" :auto-back="true" title=" " bgColor="#fff" :placeholder="true">
+			<view class="u-nav-slot flex_box" slot="left">
+			
+			</view>
+		</u-navbar> -->
+		<view class="container_main">
+			erp pda app
+		</view>
+	</view>
+</template>
+
+<script>
+const Base64 = require('js-base64').Base64
+import { bannerList } from '@/common/request/apis/index'
+import { mapGetters } from 'vuex'
+export default {
+	data() {
+		return {
+			
+		}
+	},
+	onLoad() {
+		// JSON.parse(Base64.decode(that.$Route.query.classData))
+	},
+	onShow() {
+		
+	},
+	computed: {
+		// ...mapGetters(['isLogin'])
+	},
+	methods: {
+	
+	}
+}
+</script>
+
+<style lang="scss" scoped>
+.container_box {
+	background-color: #ffffff;
+	padding: 0 0 20rpx;
+	.container_main {
+		padding: 0 20rpx;
+	}
+	
+}
+</style>

+ 51 - 0
static/svg-icons-lib.js

@@ -0,0 +1,51 @@
+/**
+ *
+ * Icon Library for <zui-svg-icon> usage
+ *
+ * Auto generated by /tools/generate-svg-icon.js
+ *
+ * !!! DO NOT MODIFY MANUALLY !!!
+ *
+ * @datetime 2024/7/23 15:42:43
+ *
+ */
+
+// == collection start
+const collections = {
+  default: {
+    "icons": {
+      "back": [
+        "<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"50\"><path fill=\"#323232\" fill-rule=\"evenodd\" d=\"M48 30c0 11.046-8.954 20-20 20h-8C9.977 50 1.699 42.619.248 33H0V3.5a3.5 3.5 0 1 1 7 0V30h.055c.553 7.634 6.398 13 14.445 13h3C33.06 43 41 39.06 41 30.5c0-7.7-4.814-12.457-12.009-13.338l2.003 2.003a3.414 3.414 0 1 1-4.829 4.829l-8.159-8.159a3.4 3.4 0 0 1-.989-2.531 3.49 3.49 0 0 1 .826-3.597l8.364-8.364a3.5 3.5 0 1 1 4.95 4.95L27.45 10H28c11.046 0 20 8.954 20 20\"/></svg>",
+        0
+      ]
+    },
+    "currentColor": "",
+    "$_colorPalette": [
+      "#323232"
+    ]
+  },
+}
+// == collection end
+
+const svglib = {}
+
+svglib.registerCollection = (key, lib) => {
+  if (collections[key]) {
+    return
+  }
+
+  if (typeof lib.registerCollection === 'function') {
+    collections[key] = lib.getCollection('default')
+  } else {
+    collections[key] = lib
+  }
+}
+
+svglib.getCollection = (key = 'default') => {
+  if (!collections[key]) throw new Error(`没有找到名为 ${key} 的图标库。`)
+
+  return collections[key]
+}
+
+export const SvgIconLib = svglib
+export default SvgIconLib

+ 24 - 0
svgo.config.js

@@ -0,0 +1,24 @@
+// svgo.config.js
+module.exports = {
+  multipass: true, // boolean. false by default
+  datauri: 'enc', // 'base64' (default), 'enc' or 'unenc'.
+  js2svg: {
+    indent: 2, // string with spaces or number of spaces. 4 by default
+    pretty: true, // boolean, false by default
+  },
+  plugins: [
+    // set of built-in plugins enabled by default
+    'preset-default',
+
+    // enable built-in plugins by name
+    'prefixIds',
+
+    // or by expanded notation which allows to configure plugin
+    {
+      name: 'sortAttrs',
+      params: {
+        xmlnsOrder: 'alphabetical',
+      },
+    },
+  ],
+}

+ 78 - 0
uni.scss

@@ -0,0 +1,78 @@
+/**
+ * 这里是uni-app内置的常用样式变量
+ *
+ * uni-app 官方扩展插件及插件市场(https://ext.dcloud.net.cn)上很多三方插件均使用了这些样式变量
+ * 如果你是插件开发者,建议你使用scss预处理,并在插件代码中直接使用这些变量(无需 import 这个文件),方便用户通过搭积木的方式开发整体风格一致的App
+ *
+ */
+
+/**
+ * 如果你是App开发者(插件使用者),你可以通过修改这些变量来定制自己的插件主题,实现自定义主题功能
+ *
+ * 如果你的项目同样使用了scss预处理,你也可以直接在你的 scss 代码中使用如下变量,同时无需 import 这个文件
+ */
+@import '@/uni_modules/uview-ui/theme.scss';
+$IMG_URL: 'https://qnyxj.hnxjrw.com'; //css中背景图片变量。js图片变量在env.js
+$imgurl: ''; //css中背景图片变量。js图片变量在env.js
+/* 颜色变量 */
+
+/* 行为相关颜色 */
+$uni-color-primary: #007aff;
+$uni-color-success: #4cd964;
+$uni-color-warning: #f0ad4e;
+$uni-color-error: #dd524d;
+
+/* 文字基本颜色 */
+$uni-text-color:#333;//基本色
+$uni-text-color-inverse:#fff;//反色
+$uni-text-color-grey:#999;//辅助灰色,如加载更多的提示信息
+$uni-text-color-placeholder: #808080;
+$uni-text-color-disable:#c0c0c0;
+
+/* 背景颜色 */
+$uni-bg-color:#ffffff;
+$uni-bg-color-grey:#f8f8f8;
+$uni-bg-color-hover:#f1f1f1;//点击状态颜色
+$uni-bg-color-mask:rgba(0, 0, 0, 0.4);//遮罩颜色
+
+/* 边框颜色 */
+$uni-border-color:#c8c7cc;
+
+/* 尺寸变量 */
+
+/* 文字尺寸 */
+$uni-font-size-sm:12px;
+$uni-font-size-base:14px;
+$uni-font-size-lg:16;
+
+/* 图片尺寸 */
+$uni-img-size-sm:20px;
+$uni-img-size-base:26px;
+$uni-img-size-lg:40px;
+
+/* Border Radius */
+$uni-border-radius-sm: 2px;
+$uni-border-radius-base: 3px;
+$uni-border-radius-lg: 6px;
+$uni-border-radius-circle: 50%;
+
+/* 水平间距 */
+$uni-spacing-row-sm: 5px;
+$uni-spacing-row-base: 10px;
+$uni-spacing-row-lg: 15px;
+
+/* 垂直间距 */
+$uni-spacing-col-sm: 4px;
+$uni-spacing-col-base: 8px;
+$uni-spacing-col-lg: 12px;
+
+/* 透明度 */
+$uni-opacity-disabled: 0.3; // 组件禁用态的透明度
+
+/* 文章场景相关 */
+$uni-color-title: #2C405A; // 文章标题颜色
+$uni-font-size-title:20px;
+$uni-color-subtitle: #555555; // 二级标题颜色
+$uni-font-size-subtitle:26px;
+$uni-color-paragraph: #3F536E; // 文章段落颜色
+$uni-font-size-paragraph:15px;

+ 201 - 0
uni_modules/Sansnn-uQRCode/LICENSE.md

@@ -0,0 +1,201 @@
+                                 Apache License
+                           Version 2.0, January 2004
+                        http://www.apache.org/licenses/
+
+   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+   1. Definitions.
+
+      "License" shall mean the terms and conditions for use, reproduction,
+      and distribution as defined by Sections 1 through 9 of this document.
+
+      "Licensor" shall mean the copyright owner or entity authorized by
+      the copyright owner that is granting the License.
+
+      "Legal Entity" shall mean the union of the acting entity and all
+      other entities that control, are controlled by, or are under common
+      control with that entity. For the purposes of this definition,
+      "control" means (i) the power, direct or indirect, to cause the
+      direction or management of such entity, whether by contract or
+      otherwise, or (ii) ownership of fifty percent (50%) or more of the
+      outstanding shares, or (iii) beneficial ownership of such entity.
+
+      "You" (or "Your") shall mean an individual or Legal Entity
+      exercising permissions granted by this License.
+
+      "Source" form shall mean the preferred form for making modifications,
+      including but not limited to software source code, documentation
+      source, and configuration files.
+
+      "Object" form shall mean any form resulting from mechanical
+      transformation or translation of a Source form, including but
+      not limited to compiled object code, generated documentation,
+      and conversions to other media types.
+
+      "Work" shall mean the work of authorship, whether in Source or
+      Object form, made available under the License, as indicated by a
+      copyright notice that is included in or attached to the work
+      (an example is provided in the Appendix below).
+
+      "Derivative Works" shall mean any work, whether in Source or Object
+      form, that is based on (or derived from) the Work and for which the
+      editorial revisions, annotations, elaborations, or other modifications
+      represent, as a whole, an original work of authorship. For the purposes
+      of this License, Derivative Works shall not include works that remain
+      separable from, or merely link (or bind by name) to the interfaces of,
+      the Work and Derivative Works thereof.
+
+      "Contribution" shall mean any work of authorship, including
+      the original version of the Work and any modifications or additions
+      to that Work or Derivative Works thereof, that is intentionally
+      submitted to Licensor for inclusion in the Work by the copyright owner
+      or by an individual or Legal Entity authorized to submit on behalf of
+      the copyright owner. For the purposes of this definition, "submitted"
+      means any form of electronic, verbal, or written communication sent
+      to the Licensor or its representatives, including but not limited to
+      communication on electronic mailing lists, source code control systems,
+      and issue tracking systems that are managed by, or on behalf of, the
+      Licensor for the purpose of discussing and improving the Work, but
+      excluding communication that is conspicuously marked or otherwise
+      designated in writing by the copyright owner as "Not a Contribution."
+
+      "Contributor" shall mean Licensor and any individual or Legal Entity
+      on behalf of whom a Contribution has been received by Licensor and
+      subsequently incorporated within the Work.
+
+   2. Grant of Copyright License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      copyright license to reproduce, prepare Derivative Works of,
+      publicly display, publicly perform, sublicense, and distribute the
+      Work and such Derivative Works in Source or Object form.
+
+   3. Grant of Patent License. Subject to the terms and conditions of
+      this License, each Contributor hereby grants to You a perpetual,
+      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+      (except as stated in this section) patent license to make, have made,
+      use, offer to sell, sell, import, and otherwise transfer the Work,
+      where such license applies only to those patent claims licensable
+      by such Contributor that are necessarily infringed by their
+      Contribution(s) alone or by combination of their Contribution(s)
+      with the Work to which such Contribution(s) was submitted. If You
+      institute patent litigation against any entity (including a
+      cross-claim or counterclaim in a lawsuit) alleging that the Work
+      or a Contribution incorporated within the Work constitutes direct
+      or contributory patent infringement, then any patent licenses
+      granted to You under this License for that Work shall terminate
+      as of the date such litigation is filed.
+
+   4. Redistribution. You may reproduce and distribute copies of the
+      Work or Derivative Works thereof in any medium, with or without
+      modifications, and in Source or Object form, provided that You
+      meet the following conditions:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) You must retain, in the Source form of any Derivative Works
+          that You distribute, all copyright, patent, trademark, and
+          attribution notices from the Source form of the Work,
+          excluding those notices that do not pertain to any part of
+          the Derivative Works; and
+
+      (d) If the Work includes a "NOTICE" text file as part of its
+          distribution, then any Derivative Works that You distribute must
+          include a readable copy of the attribution notices contained
+          within such NOTICE file, excluding those notices that do not
+          pertain to any part of the Derivative Works, in at least one
+          of the following places: within a NOTICE text file distributed
+          as part of the Derivative Works; within the Source form or
+          documentation, if provided along with the Derivative Works; or,
+          within a display generated by the Derivative Works, if and
+          wherever such third-party notices normally appear. The contents
+          of the NOTICE file are for informational purposes only and
+          do not modify the License. You may add Your own attribution
+          notices within Derivative Works that You distribute, alongside
+          or as an addendum to the NOTICE text from the Work, provided
+          that such additional attribution notices cannot be construed
+          as modifying the License.
+
+      You may add Your own copyright statement to Your modifications and
+      may provide additional or different license terms and conditions
+      for use, reproduction, or distribution of Your modifications, or
+      for any such Derivative Works as a whole, provided Your use,
+      reproduction, and distribution of the Work otherwise complies with
+      the conditions stated in this License.
+
+   5. Submission of Contributions. Unless You explicitly state otherwise,
+      any Contribution intentionally submitted for inclusion in the Work
+      by You to the Licensor shall be under the terms and conditions of
+      this License, without any additional terms or conditions.
+      Notwithstanding the above, nothing herein shall supersede or modify
+      the terms of any separate license agreement you may have executed
+      with Licensor regarding such Contributions.
+
+   6. Trademarks. This License does not grant permission to use the trade
+      names, trademarks, service marks, or product names of the Licensor,
+      except as required for reasonable and customary use in describing the
+      origin of the Work and reproducing the content of the NOTICE file.
+
+   7. Disclaimer of Warranty. Unless required by applicable law or
+      agreed to in writing, Licensor provides the Work (and each
+      Contributor provides its Contributions) on an "AS IS" BASIS,
+      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+      implied, including, without limitation, any warranties or conditions
+      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+      PARTICULAR PURPOSE. You are solely responsible for determining the
+      appropriateness of using or redistributing the Work and assume any
+      risks associated with Your exercise of permissions under this License.
+
+   8. Limitation of Liability. In no event and under no legal theory,
+      whether in tort (including negligence), contract, or otherwise,
+      unless required by applicable law (such as deliberate and grossly
+      negligent acts) or agreed to in writing, shall any Contributor be
+      liable to You for damages, including any direct, indirect, special,
+      incidental, or consequential damages of any character arising as a
+      result of this License or out of the use or inability to use the
+      Work (including but not limited to damages for loss of goodwill,
+      work stoppage, computer failure or malfunction, or any and all
+      other commercial damages or losses), even if such Contributor
+      has been advised of the possibility of such damages.
+
+   9. Accepting Warranty or Additional Liability. While redistributing
+      the Work or Derivative Works thereof, You may choose to offer,
+      and charge a fee for, acceptance of support, warranty, indemnity,
+      or other liability obligations and/or rights consistent with this
+      License. However, in accepting such obligations, You may act only
+      on Your own behalf and on Your sole responsibility, not on behalf
+      of any other Contributor, and only if You agree to indemnify,
+      defend, and hold each Contributor harmless for any liability
+      incurred by, or claims asserted against, such Contributor by reason
+      of your accepting any such warranty or additional liability.
+
+   END OF TERMS AND CONDITIONS
+
+   APPENDIX: How to apply the Apache License to your work.
+
+      To apply the Apache License to your work, attach the following
+      boilerplate notice, with the fields enclosed by brackets "[]"
+      replaced with your own identifying information. (Don't include
+      the brackets!)  The text should be enclosed in the appropriate
+      comment syntax for the file format. We also recommend that a
+      file or class name and description of purpose be included on the
+      same "printed page" as the copyright notice for easier
+      identification within third-party archives.
+
+   Copyright [yyyy] [name of copyright owner]
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 207 - 0
uni_modules/Sansnn-uQRCode/README.md


+ 12 - 0
uni_modules/Sansnn-uQRCode/changelog.md

@@ -0,0 +1,12 @@
+## 4.0.6(2022-12-12)
+修复`getDrawModules`,第一次获取结果正常,后续获取`tile`模块不存在的问题;  
+修复安卓type:normal因Canvas API使用了小数或为0的参数导致生成异常的问题(注:安卓非2d Canvas部分API参数不支持携带小数,部分API参数必须大于0)。
+## 4.0.1(2022-11-28)
+优化组件loading属性的表现;  
+新增组件type选项normal,以便于在某些条件编译初始为type=2d时还可以选择使用非2d组件类型;  
+修复组件条件编译在其他编辑器语法提示报错;  
+修复原生对es5的支持。
+## 4.0.0(2022-11-21)
+v4版本源代码全面开放,开源地址:[https://github.com/Sansnn/uQRCode](https://github.com/Sansnn/uQRCode);  
+
+升级说明:v4为大版本更新,虽然已尽可能兼容上一代版本,但不可避免的还是存在一些细节差异,若更新后出现问题,请参考对照[v3 文档](https://uqrcode.cn/doc/v3),[v4 文档](https://uqrcode.cn/doc)进行修改。

+ 1 - 0
uni_modules/Sansnn-uQRCode/common/cache.js

@@ -0,0 +1 @@
+export const cacheImageList = [];

+ 41 - 0
uni_modules/Sansnn-uQRCode/common/queue.js

@@ -0,0 +1,41 @@
+function Queue() {
+  let waitingQueue = this.waitingQueue = [];
+  let isRunning = this.isRunning = false; // 记录是否有未完成的任务
+
+  function execute(task, resolve, reject) {
+    task()
+      .then((data) => {
+        resolve(data);
+      })
+      .catch((e) => {
+        reject(e);
+      })
+      .finally(() => {
+        // 等待任务队列中如果有任务,则触发它;否则设置isRunning = false,表示无任务状态
+        if (waitingQueue.length) {
+          const next = waitingQueue.shift();
+          execute(next.task, next.resolve, next.reject);
+        } else {
+          isRunning = false;
+        }
+      });
+  }
+  this.exec = function(task) {
+    return new Promise((resolve, reject) => {
+      if (isRunning) {
+        waitingQueue.push({
+          task,
+          resolve,
+          reject
+        });
+      } else {
+        isRunning = true;
+        execute(task, resolve, reject);
+      }
+    });
+  }
+}
+
+/* 队列实例,某些平台一起使用多个组件时需要通过队列逐一绘制,否则部分绘制方法异常,nvue端的iOS gcanvas尤其明显,在不通过队列绘制时会出现图片丢失的情况 */
+export const queueDraw = new Queue();
+export const queueLoadImage = new Queue();

+ 3 - 0
uni_modules/Sansnn-uQRCode/common/types/cache.d.ts

@@ -0,0 +1,3 @@
+declare module '*/common/cache' {
+  export const cacheImageList: Array;
+}

+ 4 - 0
uni_modules/Sansnn-uQRCode/common/types/queue.d.ts

@@ -0,0 +1,4 @@
+declare module '*/common/queue' {
+  export const queueDraw: any;
+  export const queueLoadImage: any;
+}

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 25 - 0
uni_modules/Sansnn-uQRCode/components/u-qrcode/u-qrcode.vue


A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 25 - 0
uni_modules/Sansnn-uQRCode/components/uqrcode/uqrcode.vue


+ 241 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/bridge/bridge-weex.js

@@ -0,0 +1,241 @@
+const isWeex = typeof WXEnvironment !== 'undefined';
+const isWeexIOS = isWeex && /ios/i.test(WXEnvironment.platform);
+const isWeexAndroid = isWeex && !isWeexIOS;
+
+import GLmethod from '../context-webgl/GLmethod';
+
+const GCanvasModule =
+    (typeof weex !== 'undefined' && weex.requireModule) ? (weex.requireModule('gcanvas')) :
+        (typeof __weex_require__ !== 'undefined') ? (__weex_require__('@weex-module/gcanvas')) : {};
+
+let isDebugging = false;
+
+let isComboDisabled = false;
+
+const logCommand = (function () {
+    const methodQuery = [];
+    Object.keys(GLmethod).forEach(key => {
+        methodQuery[GLmethod[key]] = key;
+    })
+    const queryMethod = (id) => {
+        return methodQuery[parseInt(id)] || 'NotFoundMethod';
+    }
+    const logCommand = (id, cmds) => {
+        const mId = cmds.split(',')[0];
+        const mName = queryMethod(mId);
+        console.log(`=== callNative - componentId:${id}; method: ${mName}; cmds: ${cmds}`);
+    }
+    return logCommand;
+})();
+
+function joinArray(arr, sep) {
+    let res = '';
+    for (let i = 0; i < arr.length; i++) {
+        if (i !== 0) {
+            res += sep;
+        }
+        res += arr[i];
+    }
+    return res;
+}
+
+const commandsCache = {}
+
+const GBridge = {
+
+    callEnable: (ref, configArray) => {
+
+        commandsCache[ref] = [];
+
+        return GCanvasModule.enable({
+            componentId: ref,
+            config: configArray
+        });
+    },
+
+    callEnableDebug: () => {
+        isDebugging = true;
+    },
+
+    callEnableDisableCombo: () => {
+        isComboDisabled = true;
+    },
+
+    callSetContextType: function (componentId, context_type) {
+        GCanvasModule.setContextType(context_type, componentId);
+    },
+
+    callReset: function(id){
+        GCanvasModule.resetComponent && canvasModule.resetComponent(componentId);
+    },
+
+    render: isWeexIOS ? function (componentId) {
+        return GCanvasModule.extendCallNative({
+            contextId: componentId,
+            type: 0x60000001
+        });
+    } : function (componentId) {
+        return callGCanvasLinkNative(componentId, 0x60000001, 'render');
+    },
+
+    render2d: isWeexIOS ? function (componentId, commands, callback) {
+
+        if (isDebugging) {
+            console.log('>>> >>> render2d ===');
+            console.log('>>> commands: ' + commands);
+        }
+		
+        GCanvasModule.render([commands, callback?true:false], componentId, callback);
+
+    } : function (componentId, commands,callback) {
+
+        if (isDebugging) {
+            console.log('>>> >>> render2d ===');
+            console.log('>>> commands: ' + commands);
+        }
+
+        callGCanvasLinkNative(componentId, 0x20000001, commands);
+		if(callback){
+		callback();
+		}
+    },
+
+    callExtendCallNative: isWeexIOS ? function (componentId, cmdArgs) {
+
+        throw 'should not be here anymore ' + cmdArgs;
+
+    } : function (componentId, cmdArgs) {
+
+        throw 'should not be here anymore ' + cmdArgs;
+
+    },
+
+
+    flushNative: isWeexIOS ? function (componentId) {
+
+        const cmdArgs = joinArray(commandsCache[componentId], ';');
+        commandsCache[componentId] = [];
+
+        if (isDebugging) {
+            console.log('>>> >>> flush native ===');
+            console.log('>>> commands: ' + cmdArgs);
+        }
+
+        const result = GCanvasModule.extendCallNative({
+            "contextId": componentId,
+            "type": 0x60000000,
+            "args": cmdArgs
+        });
+
+        const res = result && result.result;
+
+        if (isDebugging) {
+            console.log('>>> result: ' + res);
+        }
+
+        return res;
+
+    } : function (componentId) {
+
+        const cmdArgs = joinArray(commandsCache[componentId], ';');
+        commandsCache[componentId] = [];
+
+        if (isDebugging) {
+            console.log('>>> >>> flush native ===');
+            console.log('>>> commands: ' + cmdArgs);
+        }
+
+        const result = callGCanvasLinkNative(componentId, 0x60000000, cmdArgs);
+
+        if (isDebugging) {
+            console.log('>>> result: ' + result);
+        }
+
+        return result;
+    },
+
+    callNative: function (componentId, cmdArgs, cache) {
+
+        if (isDebugging) {
+            logCommand(componentId, cmdArgs);
+        }
+
+        commandsCache[componentId].push(cmdArgs);
+
+        if (!cache || isComboDisabled) {
+            return GBridge.flushNative(componentId);
+        } else {
+            return undefined;
+        }
+    },
+
+    texImage2D(componentId, ...args) {
+        if (isWeexIOS) {
+            if (args.length === 6) {
+                const [target, level, internalformat, format, type, image] = args;
+                GBridge.callNative(
+                    componentId,
+                    GLmethod.texImage2D + ',' + 6 + ',' + target + ',' + level + ',' + internalformat + ',' + format + ',' + type + ',' + image.src
+                )
+            } else if (args.length === 9) {
+                const [target, level, internalformat, width, height, border, format, type, image] = args;
+                GBridge.callNative(
+                    componentId,
+                    GLmethod.texImage2D + ',' + 9 + ',' + target + ',' + level + ',' + internalformat + ',' + width + ',' + height + ',' + border + ',' +
+                    + format + ',' + type + ',' + (image ? image.src : 0)
+                )
+            }
+        } else if (isWeexAndroid) {
+            if (args.length === 6) {
+                const [target, level, internalformat, format, type, image] = args;
+                GCanvasModule.texImage2D(componentId, target, level, internalformat, format, type, image.src);
+            } else if (args.length === 9) {
+                const [target, level, internalformat, width, height, border, format, type, image] = args;
+                GCanvasModule.texImage2D(componentId, target, level, internalformat, width, height, border, format, type, (image ? image.src : 0));
+            }
+        }
+    },
+
+    texSubImage2D(componentId, target, level, xoffset, yoffset, format, type, image) {
+        if (isWeexIOS) {
+            if (arguments.length === 8) {
+                GBridge.callNative(
+                    componentId,
+                    GLmethod.texSubImage2D + ',' + 6 + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset, + ',' + format + ',' + type + ',' + image.src
+                )
+            }
+        } else if (isWeexAndroid) {
+            GCanvasModule.texSubImage2D(componentId, target, level, xoffset, yoffset, format, type, image.src);
+        }
+    },
+
+    bindImageTexture(componentId, src, imageId) {
+        GCanvasModule.bindImageTexture([src, imageId], componentId);
+    },
+
+    perloadImage([url, id], callback) {
+        GCanvasModule.preLoadImage([url, id], function (image) {
+            image.url = url;
+            image.id = id;
+            callback(image);
+        });
+    },
+	
+	measureText(text, fontStyle, componentId) {
+	    return GCanvasModule.measureText([text, fontStyle], componentId);
+	},
+	
+	getImageData (componentId, x, y, w, h, callback) {
+		GCanvasModule.getImageData([x, y,w,h],componentId,callback);
+	},
+	
+	putImageData (componentId, data, x, y, w, h, callback) {
+		GCanvasModule.putImageData([x, y,w,h,data],componentId,callback);
+	},
+	
+	toTempFilePath(componentId, x, y, width, height, destWidth, destHeight, fileType, quality, callback){ 
+		GCanvasModule.toTempFilePath([x, y, width,height, destWidth, destHeight, fileType, quality], componentId, callback);
+	}
+}
+
+export default GBridge;

+ 18 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-2d/FillStyleLinearGradient.js

@@ -0,0 +1,18 @@
+class FillStyleLinearGradient {
+
+    constructor(x0, y0, x1, y1) {
+        this._start_pos = { _x: x0, _y: y0 };
+        this._end_pos = { _x: x1, _y: y1 };
+        this._stop_count = 0;
+        this._stops = [0, 0, 0, 0, 0];
+    }
+
+    addColorStop = function (pos, color) {
+        if (this._stop_count < 5 && 0.0 <= pos && pos <= 1.0) {
+            this._stops[this._stop_count] = { _pos: pos, _color: color };
+            this._stop_count++;
+        }
+    }
+}
+
+export default FillStyleLinearGradient;

+ 8 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-2d/FillStylePattern.js

@@ -0,0 +1,8 @@
+class FillStylePattern {
+    constructor(img, pattern) {
+        this._style = pattern;
+        this._img = img;
+    }
+}
+
+export default FillStylePattern;

+ 17 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-2d/FillStyleRadialGradient.js

@@ -0,0 +1,17 @@
+class FillStyleRadialGradient {
+    constructor(x0, y0, r0, x1, y1, r1) {
+        this._start_pos = { _x: x0, _y: y0, _r: r0 };
+        this._end_pos = { _x: x1, _y: y1, _r: r1 };
+        this._stop_count = 0;
+        this._stops = [0, 0, 0, 0, 0];
+    }
+
+    addColorStop(pos, color) {
+        if (this._stop_count < 5 && 0.0 <= pos && pos <= 1.0) {
+            this._stops[this._stop_count] = { _pos: pos, _color: color };
+            this._stop_count++;
+        }
+    }
+}
+
+export default FillStyleRadialGradient;

+ 666 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-2d/RenderingContext.js

@@ -0,0 +1,666 @@
+import FillStylePattern from './FillStylePattern';
+import FillStyleLinearGradient from './FillStyleLinearGradient';
+import FillStyleRadialGradient from './FillStyleRadialGradient';
+import GImage from '../env/image.js';
+import {
+	ArrayBufferToBase64,
+	Base64ToUint8ClampedArray
+} from '../env/tool.js';
+
+export default class CanvasRenderingContext2D {
+
+	_drawCommands = '';
+
+	_globalAlpha = 1.0;
+
+	_fillStyle = 'rgb(0,0,0)';
+	_strokeStyle = 'rgb(0,0,0)';
+
+	_lineWidth = 1;
+	_lineCap = 'butt';
+	_lineJoin = 'miter';
+
+	_miterLimit = 10;
+
+	_globalCompositeOperation = 'source-over';
+
+	_textAlign = 'start';
+	_textBaseline = 'alphabetic';
+
+	_font = '10px sans-serif';
+
+	_savedGlobalAlpha = [];
+
+	timer = null;
+	componentId = null;
+
+	_notCommitDrawImageCache = [];
+	_needRedrawImageCache = [];
+	_redrawCommands = '';
+	_autoSaveContext = true;
+	// _imageMap = new GHashMap();
+	// _textureMap = new GHashMap();
+
+	constructor() {
+		this.className = 'CanvasRenderingContext2D';
+		//this.save()
+	}
+
+	setFillStyle(value) {
+		this.fillStyle = value;
+	}
+
+	set fillStyle(value) {
+		this._fillStyle = value;
+
+		if (typeof(value) == 'string') {
+			this._drawCommands = this._drawCommands.concat("F" + value + ";");
+		} else if (value instanceof FillStylePattern) {
+			const image = value._img;
+			if (!image.complete) {
+				image.onload = () => {
+					var index = this._needRedrawImageCache.indexOf(image);
+					if (index > -1) {
+						this._needRedrawImageCache.splice(index, 1);
+						CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+						this._redrawflush(true);
+					}
+				}
+				this._notCommitDrawImageCache.push(image);
+			} else {
+				CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+			}
+
+			//CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+			this._drawCommands = this._drawCommands.concat("G" + image._id + "," + value._style + ";");
+		} else if (value instanceof FillStyleLinearGradient) {
+			var command = "D" + value._start_pos._x.toFixed(2) + "," + value._start_pos._y.toFixed(2) + "," +
+				value._end_pos._x.toFixed(2) + "," + value._end_pos._y.toFixed(2) + "," +
+				value._stop_count;
+			for (var i = 0; i < value._stop_count; ++i) {
+				command += ("," + value._stops[i]._pos + "," + value._stops[i]._color);
+			}
+			this._drawCommands = this._drawCommands.concat(command + ";");
+		} else if (value instanceof FillStyleRadialGradient) {
+			var command = "H" + value._start_pos._x.toFixed(2) + "," + value._start_pos._y.toFixed(2) + "," + value._start_pos._r
+				.toFixed(2) + "," +
+				value._end_pos._x.toFixed(2) + "," + value._end_pos._y.toFixed(2) + "," + value._end_pos._r.toFixed(2) + "," +
+				value._stop_count;
+			for (var i = 0; i < value._stop_count; ++i) {
+				command += ("," + value._stops[i]._pos + "," + value._stops[i]._color);
+			}
+			this._drawCommands = this._drawCommands.concat(command + ";");
+		}
+	}
+
+	get fillStyle() {
+		return this._fillStyle;
+	}
+
+	get globalAlpha() {
+		return this._globalAlpha;
+	}
+
+	setGlobalAlpha(value) {
+		this.globalAlpha = value;
+	}
+
+	set globalAlpha(value) {
+		this._globalAlpha = value;
+		this._drawCommands = this._drawCommands.concat("a" + value.toFixed(2) + ";");
+	}
+
+
+	get strokeStyle() {
+		return this._strokeStyle;
+	}
+
+	setStrokeStyle(value) {
+		this.strokeStyle = value;
+	}
+
+	set strokeStyle(value) {
+
+		this._strokeStyle = value;
+
+		if (typeof(value) == 'string') {
+			this._drawCommands = this._drawCommands.concat("S" + value + ";");
+		} else if (value instanceof FillStylePattern) {
+			CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+			this._drawCommands = this._drawCommands.concat("G" + image._id + "," + value._style + ";");
+		} else if (value instanceof FillStyleLinearGradient) {
+			var command = "D" + value._start_pos._x.toFixed(2) + "," + value._start_pos._y.toFixed(2) + "," +
+				value._end_pos._x.toFixed(2) + "," + value._end_pos._y.toFixed(2) + "," +
+				value._stop_count;
+
+			for (var i = 0; i < value._stop_count; ++i) {
+				command += ("," + value._stops[i]._pos + "," + value._stops[i]._color);
+			}
+			this._drawCommands = this._drawCommands.concat(command + ";");
+		} else if (value instanceof FillStyleRadialGradient) {
+			var command = "H" + value._start_pos._x.toFixed(2) + "," + value._start_pos._y.toFixed(2) + "," + value._start_pos._r
+				.toFixed(2) + "," +
+				value._end_pos._x.toFixed(2) + "," + value._end_pos._y + ",".toFixed(2) + value._end_pos._r.toFixed(2) + "," +
+				value._stop_count;
+
+			for (var i = 0; i < value._stop_count; ++i) {
+				command += ("," + value._stops[i]._pos + "," + value._stops[i]._color);
+			}
+			this._drawCommands = this._drawCommands.concat(command + ";");
+		}
+	}
+
+	get lineWidth() {
+		return this._lineWidth;
+	}
+
+	setLineWidth(value) {
+		this.lineWidth = value;
+	}
+
+	set lineWidth(value) {
+		this._lineWidth = value;
+		this._drawCommands = this._drawCommands.concat("W" + value + ";");
+	}
+
+	get lineCap() {
+		return this._lineCap;
+	}
+
+	setLineCap(value) {
+		this.lineCap = value;
+	}
+
+	set lineCap(value) {
+		this._lineCap = value;
+		this._drawCommands = this._drawCommands.concat("C" + value + ";");
+	}
+
+	get lineJoin() {
+		return this._lineJoin;
+	}
+
+	setLineJoin(value) {
+		this.lineJoin = value
+	}
+
+	set lineJoin(value) {
+		this._lineJoin = value;
+		this._drawCommands = this._drawCommands.concat("J" + value + ";");
+	}
+
+	get miterLimit() {
+		return this._miterLimit;
+	}
+
+	setMiterLimit(value) {
+		this.miterLimit = value
+	}
+
+	set miterLimit(value) {
+		this._miterLimit = value;
+		this._drawCommands = this._drawCommands.concat("M" + value + ";");
+	}
+
+	get globalCompositeOperation() {
+		return this._globalCompositeOperation;
+	}
+
+	set globalCompositeOperation(value) {
+
+		this._globalCompositeOperation = value;
+		let mode = 0;
+		switch (value) {
+			case "source-over":
+				mode = 0;
+				break;
+			case "source-atop":
+				mode = 5;
+				break;
+			case "source-in":
+				mode = 0;
+				break;
+			case "source-out":
+				mode = 2;
+				break;
+			case "destination-over":
+				mode = 4;
+				break;
+			case "destination-atop":
+				mode = 4;
+				break;
+			case "destination-in":
+				mode = 4;
+				break;
+			case "destination-out":
+				mode = 3;
+				break;
+			case "lighter":
+				mode = 1;
+				break;
+			case "copy":
+				mode = 2;
+				break;
+			case "xor":
+				mode = 6;
+				break;
+			default:
+				mode = 0;
+		}
+
+		this._drawCommands = this._drawCommands.concat("B" + mode + ";");
+	}
+
+	get textAlign() {
+		return this._textAlign;
+	}
+
+	setTextAlign(value) {
+		this.textAlign = value
+	}
+
+	set textAlign(value) {
+
+		this._textAlign = value;
+		let Align = 0;
+		switch (value) {
+			case "start":
+				Align = 0;
+				break;
+			case "end":
+				Align = 1;
+				break;
+			case "left":
+				Align = 2;
+				break;
+			case "center":
+				Align = 3;
+				break;
+			case "right":
+				Align = 4;
+				break;
+			default:
+				Align = 0;
+		}
+
+		this._drawCommands = this._drawCommands.concat("A" + Align + ";");
+	}
+
+	get textBaseline() {
+		return this._textBaseline;
+	}
+
+	setTextBaseline(value) {
+		this.textBaseline = value
+	}
+
+	set textBaseline(value) {
+		this._textBaseline = value;
+		let baseline = 0;
+		switch (value) {
+			case "alphabetic":
+				baseline = 0;
+				break;
+			case "middle":
+				baseline = 1;
+				break;
+			case "top":
+				baseline = 2;
+				break;
+			case "hanging":
+				baseline = 3;
+				break;
+			case "bottom":
+				baseline = 4;
+				break;
+			case "ideographic":
+				baseline = 5;
+				break;
+			default:
+				baseline = 0;
+				break;
+		}
+
+		this._drawCommands = this._drawCommands.concat("E" + baseline + ";");
+	}
+
+	get font() {
+		return this._font;
+	}
+
+	setFontSize(size) {
+		var str = this._font;
+		var strs = str.trim().split(/\s+/);
+		for (var i = 0; i < strs.length; i++) {
+			var values = ["normal", "italic", "oblique", "normal", "small-caps", "normal", "bold",
+				"bolder", "lighter", "100", "200", "300", "400", "500", "600", "700", "800", "900",
+				"normal", "ultra-condensed", "extra-condensed", "condensed", "semi-condensed",
+				"semi-expanded", "expanded", "extra-expanded", "ultra-expanded"
+			];
+
+			if (-1 == values.indexOf(strs[i].trim())) {
+				if (typeof size === 'string') {
+					strs[i] = size;
+				} else if (typeof size === 'number') {
+					strs[i] = String(size) + 'px';
+				}
+				break;
+			}
+		}
+		this.font = strs.join(" ");
+	}
+
+	set font(value) {
+		this._font = value;
+		this._drawCommands = this._drawCommands.concat("j" + value + ";");
+	}
+
+	setTransform(a, b, c, d, tx, ty) {
+		this._drawCommands = this._drawCommands.concat("t" +
+			(a === 1 ? "1" : a.toFixed(2)) + "," +
+			(b === 0 ? "0" : b.toFixed(2)) + "," +
+			(c === 0 ? "0" : c.toFixed(2)) + "," +
+			(d === 1 ? "1" : d.toFixed(2)) + "," + tx.toFixed(2) + "," + ty.toFixed(2) + ";");
+	}
+
+	transform(a, b, c, d, tx, ty) {
+		this._drawCommands = this._drawCommands.concat("f" +
+			(a === 1 ? "1" : a.toFixed(2)) + "," +
+			(b === 0 ? "0" : b.toFixed(2)) + "," +
+			(c === 0 ? "0" : c.toFixed(2)) + "," +
+			(d === 1 ? "1" : d.toFixed(2)) + "," + tx + "," + ty + ";");
+	}
+
+	resetTransform() {
+		this._drawCommands = this._drawCommands.concat("m;");
+	}
+
+	scale(a, d) {
+		this._drawCommands = this._drawCommands.concat("k" + a.toFixed(2) + "," +
+			d.toFixed(2) + ";");
+	}
+
+	rotate(angle) {
+		this._drawCommands = this._drawCommands
+			.concat("r" + angle.toFixed(6) + ";");
+	}
+
+	translate(tx, ty) {
+		this._drawCommands = this._drawCommands.concat("l" + tx.toFixed(2) + "," + ty.toFixed(2) + ";");
+	}
+
+	save() {
+		this._savedGlobalAlpha.push(this._globalAlpha);
+		this._drawCommands = this._drawCommands.concat("v;");
+	}
+
+	restore() {
+		this._drawCommands = this._drawCommands.concat("e;");
+		this._globalAlpha = this._savedGlobalAlpha.pop();
+	}
+
+	createPattern(img, pattern) {
+		if (typeof img === 'string') {
+			var imgObj = new GImage();
+			imgObj.src = img;
+			img = imgObj;
+		}
+		return new FillStylePattern(img, pattern);
+	}
+
+	createLinearGradient(x0, y0, x1, y1) {
+		return new FillStyleLinearGradient(x0, y0, x1, y1);
+	}
+
+	createRadialGradient = function(x0, y0, r0, x1, y1, r1) {
+		return new FillStyleRadialGradient(x0, y0, r0, x1, y1, r1);
+	};
+
+	createCircularGradient = function(x0, y0, r0) {
+		return new FillStyleRadialGradient(x0, y0, 0, x0, y0, r0);
+	};
+
+	strokeRect(x, y, w, h) {
+		this._drawCommands = this._drawCommands.concat("s" + x + "," + y + "," + w + "," + h + ";");
+	}
+
+
+	clearRect(x, y, w, h) {
+		this._drawCommands = this._drawCommands.concat("c" + x + "," + y + "," + w +
+			"," + h + ";");
+	}
+
+	clip() {
+		this._drawCommands = this._drawCommands.concat("p;");
+	}
+
+	resetClip() {
+		this._drawCommands = this._drawCommands.concat("q;");
+	}
+
+	closePath() {
+		this._drawCommands = this._drawCommands.concat("o;");
+	}
+
+	moveTo(x, y) {
+		this._drawCommands = this._drawCommands.concat("g" + x.toFixed(2) + "," + y.toFixed(2) + ";");
+	}
+
+	lineTo(x, y) {
+		this._drawCommands = this._drawCommands.concat("i" + x.toFixed(2) + "," + y.toFixed(2) + ";");
+	}
+
+	quadraticCurveTo = function(cpx, cpy, x, y) {
+		this._drawCommands = this._drawCommands.concat("u" + cpx + "," + cpy + "," + x + "," + y + ";");
+	}
+
+	bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y, ) {
+		this._drawCommands = this._drawCommands.concat(
+			"z" + cp1x.toFixed(2) + "," + cp1y.toFixed(2) + "," + cp2x.toFixed(2) + "," + cp2y.toFixed(2) + "," +
+			x.toFixed(2) + "," + y.toFixed(2) + ";");
+	}
+
+	arcTo(x1, y1, x2, y2, radius) {
+		this._drawCommands = this._drawCommands.concat("h" + x1 + "," + y1 + "," + x2 + "," + y2 + "," + radius + ";");
+	}
+
+	beginPath() {
+		this._drawCommands = this._drawCommands.concat("b;");
+	}
+
+
+	fillRect(x, y, w, h) {
+		this._drawCommands = this._drawCommands.concat("n" + x + "," + y + "," + w +
+			"," + h + ";");
+	}
+
+	rect(x, y, w, h) {
+		this._drawCommands = this._drawCommands.concat("w" + x + "," + y + "," + w + "," + h + ";");
+	}
+
+	fill() {
+		this._drawCommands = this._drawCommands.concat("L;");
+	}
+
+	stroke(path) {
+		this._drawCommands = this._drawCommands.concat("x;");
+	}
+
+	arc(x, y, radius, startAngle, endAngle, anticlockwise) {
+
+		let ianticlockwise = 0;
+		if (anticlockwise) {
+			ianticlockwise = 1;
+		}
+
+		this._drawCommands = this._drawCommands.concat(
+			"y" + x.toFixed(2) + "," + y.toFixed(2) + "," +
+			radius.toFixed(2) + "," + startAngle + "," + endAngle + "," + ianticlockwise +
+			";"
+		);
+	}
+
+	fillText(text, x, y) {
+		let tmptext = text.replace(/!/g, "!!");
+		tmptext = tmptext.replace(/,/g, "!,");
+		tmptext = tmptext.replace(/;/g, "!;");
+		this._drawCommands = this._drawCommands.concat("T" + tmptext + "," + x + "," + y + ",0.0;");
+	}
+
+	strokeText = function(text, x, y) {
+		let tmptext = text.replace(/!/g, "!!");
+		tmptext = tmptext.replace(/,/g, "!,");
+		tmptext = tmptext.replace(/;/g, "!;");
+		this._drawCommands = this._drawCommands.concat("U" + tmptext + "," + x + "," + y + ",0.0;");
+	}
+
+	measureText(text) {
+		return CanvasRenderingContext2D.GBridge.measureText(text, this.font, this.componentId);
+	}
+
+	isPointInPath = function(x, y) {
+		throw new Error('GCanvas not supported yet');
+	}
+
+	drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
+		if (typeof image === 'string') {
+			var imgObj = new GImage();
+			imgObj.src = image;
+			image = imgObj;
+		}
+		if (image instanceof GImage) {
+			if (!image.complete) {
+				imgObj.onload = () => {
+					var index = this._needRedrawImageCache.indexOf(image);
+					if (index > -1) {
+						this._needRedrawImageCache.splice(index, 1);
+						CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+						this._redrawflush(true);
+					}
+				}
+				this._notCommitDrawImageCache.push(image);
+			} else {
+				CanvasRenderingContext2D.GBridge.bindImageTexture(this.componentId, image.src, image._id);
+			}
+			var srcArgs = [image, sx, sy, sw, sh, dx, dy, dw, dh];
+			var args = [];
+			for (var arg in srcArgs) {
+				if (typeof(srcArgs[arg]) != 'undefined') {
+					args.push(srcArgs[arg]);
+				}
+			}
+			this.__drawImage.apply(this, args);
+			//this.__drawImage(image,sx, sy, sw, sh, dx, dy, dw, dh);
+		}
+	}
+
+	__drawImage(image, sx, sy, sw, sh, dx, dy, dw, dh) {
+		const numArgs = arguments.length;
+
+		function drawImageCommands() {
+
+			if (numArgs === 3) {
+				const x = parseFloat(sx) || 0.0;
+				const y = parseFloat(sy) || 0.0;
+
+				return ("d" + image._id + ",0,0," +
+					image.width + "," + image.height + "," +
+					x + "," + y + "," + image.width + "," + image.height + ";");
+			} else if (numArgs === 5) {
+				const x = parseFloat(sx) || 0.0;
+				const y = parseFloat(sy) || 0.0;
+				const width = parseInt(sw) || image.width;
+				const height = parseInt(sh) || image.height;
+
+				return ("d" + image._id + ",0,0," +
+					image.width + "," + image.height + "," +
+					x + "," + y + "," + width + "," + height + ";");
+			} else if (numArgs === 9) {
+				sx = parseFloat(sx) || 0.0;
+				sy = parseFloat(sy) || 0.0;
+				sw = parseInt(sw) || image.width;
+				sh = parseInt(sh) || image.height;
+				dx = parseFloat(dx) || 0.0;
+				dy = parseFloat(dy) || 0.0;
+				dw = parseInt(dw) || image.width;
+				dh = parseInt(dh) || image.height;
+
+				return ("d" + image._id + "," +
+					sx + "," + sy + "," + sw + "," + sh + "," +
+					dx + "," + dy + "," + dw + "," + dh + ";");
+			}
+		}
+		this._drawCommands += drawImageCommands();
+	}
+
+	_flush(reserve, callback) {
+		const commands = this._drawCommands;
+		this._drawCommands = '';
+		CanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback);
+		this._needRender = false;
+	}
+
+	_redrawflush(reserve, callback) {
+		const commands = this._redrawCommands;
+		CanvasRenderingContext2D.GBridge.render2d(this.componentId, commands, callback);
+		if (this._needRedrawImageCache.length == 0) {
+			this._redrawCommands = '';
+		}
+	}
+
+	draw(reserve, callback) {
+		if (!reserve) {
+			this._globalAlpha = this._savedGlobalAlpha.pop();
+			this._savedGlobalAlpha.push(this._globalAlpha);
+			this._redrawCommands = this._drawCommands;
+			this._needRedrawImageCache = this._notCommitDrawImageCache;
+			if (this._autoSaveContext) {
+				this._drawCommands = ("v;" + this._drawCommands);
+				this._autoSaveContext = false;
+			} else {
+				this._drawCommands = ("e;X;v;" + this._drawCommands);
+			}
+		} else {
+			this._needRedrawImageCache = this._needRedrawImageCache.concat(this._notCommitDrawImageCache);
+			this._redrawCommands += this._drawCommands;
+			if (this._autoSaveContext) {
+				this._drawCommands = ("v;" + this._drawCommands);
+				this._autoSaveContext = false;
+			}
+		}
+		this._notCommitDrawImageCache = [];
+		if (this._flush) {
+			this._flush(reserve, callback);
+		}
+	}
+
+	getImageData(x, y, w, h, callback) {
+		CanvasRenderingContext2D.GBridge.getImageData(this.componentId, x, y, w, h, function(res) {
+			res.data = Base64ToUint8ClampedArray(res.data);
+			if (typeof(callback) == 'function') {
+				callback(res);
+			}
+		});
+	}
+
+	putImageData(data, x, y, w, h, callback) {
+		if (data instanceof Uint8ClampedArray) {
+			data = ArrayBufferToBase64(data);
+			CanvasRenderingContext2D.GBridge.putImageData(this.componentId, data, x, y, w, h, function(res) {
+				if (typeof(callback) == 'function') {
+					callback(res);
+				}
+			});
+		}
+	}
+
+	toTempFilePath(x, y, width, height, destWidth, destHeight, fileType, quality, callback) {
+		CanvasRenderingContext2D.GBridge.toTempFilePath(this.componentId, x, y, width, height, destWidth, destHeight,
+			fileType, quality,
+			function(res) {
+				if (typeof(callback) == 'function') {
+					callback(res);
+				}
+			});
+	}
+}

+ 11 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/ActiveInfo.js

@@ -0,0 +1,11 @@
+export default class WebGLActiveInfo {
+    className = 'WebGLActiveInfo';
+
+    constructor({
+        type, name, size
+    }) {
+        this.type = type;
+        this.name = name;
+        this.size = size;
+    }
+}

+ 21 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Buffer.js

@@ -0,0 +1,21 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLBuffer';
+
+function uuid(id) {
+    return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLBuffer {
+    className = name;
+
+    constructor(id) {
+        this.id = id;
+    }
+
+    static uuid = uuid;
+
+    uuid() {
+        return uuid(this.id);
+    }
+}

+ 21 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Framebuffer.js

@@ -0,0 +1,21 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLFrameBuffer';
+
+function uuid(id) {
+    return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLFramebuffer {
+    className = name;
+
+    constructor(id) {
+        this.id = id;
+    }
+
+    static uuid = uuid;
+
+    uuid() {
+        return uuid(this.id);
+    }
+}

+ 298 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/GLenum.js

@@ -0,0 +1,298 @@
+export default {
+    "DEPTH_BUFFER_BIT": 256,
+    "STENCIL_BUFFER_BIT": 1024,
+    "COLOR_BUFFER_BIT": 16384,
+    "POINTS": 0,
+    "LINES": 1,
+    "LINE_LOOP": 2,
+    "LINE_STRIP": 3,
+    "TRIANGLES": 4,
+    "TRIANGLE_STRIP": 5,
+    "TRIANGLE_FAN": 6,
+    "ZERO": 0,
+    "ONE": 1,
+    "SRC_COLOR": 768,
+    "ONE_MINUS_SRC_COLOR": 769,
+    "SRC_ALPHA": 770,
+    "ONE_MINUS_SRC_ALPHA": 771,
+    "DST_ALPHA": 772,
+    "ONE_MINUS_DST_ALPHA": 773,
+    "DST_COLOR": 774,
+    "ONE_MINUS_DST_COLOR": 775,
+    "SRC_ALPHA_SATURATE": 776,
+    "FUNC_ADD": 32774,
+    "BLEND_EQUATION": 32777,
+    "BLEND_EQUATION_RGB": 32777,
+    "BLEND_EQUATION_ALPHA": 34877,
+    "FUNC_SUBTRACT": 32778,
+    "FUNC_REVERSE_SUBTRACT": 32779,
+    "BLEND_DST_RGB": 32968,
+    "BLEND_SRC_RGB": 32969,
+    "BLEND_DST_ALPHA": 32970,
+    "BLEND_SRC_ALPHA": 32971,
+    "CONSTANT_COLOR": 32769,
+    "ONE_MINUS_CONSTANT_COLOR": 32770,
+    "CONSTANT_ALPHA": 32771,
+    "ONE_MINUS_CONSTANT_ALPHA": 32772,
+    "BLEND_COLOR": 32773,
+    "ARRAY_BUFFER": 34962,
+    "ELEMENT_ARRAY_BUFFER": 34963,
+    "ARRAY_BUFFER_BINDING": 34964,
+    "ELEMENT_ARRAY_BUFFER_BINDING": 34965,
+    "STREAM_DRAW": 35040,
+    "STATIC_DRAW": 35044,
+    "DYNAMIC_DRAW": 35048,
+    "BUFFER_SIZE": 34660,
+    "BUFFER_USAGE": 34661,
+    "CURRENT_VERTEX_ATTRIB": 34342,
+    "FRONT": 1028,
+    "BACK": 1029,
+    "FRONT_AND_BACK": 1032,
+    "TEXTURE_2D": 3553,
+    "CULL_FACE": 2884,
+    "BLEND": 3042,
+    "DITHER": 3024,
+    "STENCIL_TEST": 2960,
+    "DEPTH_TEST": 2929,
+    "SCISSOR_TEST": 3089,
+    "POLYGON_OFFSET_FILL": 32823,
+    "SAMPLE_ALPHA_TO_COVERAGE": 32926,
+    "SAMPLE_COVERAGE": 32928,
+    "NO_ERROR": 0,
+    "INVALID_ENUM": 1280,
+    "INVALID_VALUE": 1281,
+    "INVALID_OPERATION": 1282,
+    "OUT_OF_MEMORY": 1285,
+    "CW": 2304,
+    "CCW": 2305,
+    "LINE_WIDTH": 2849,
+    "ALIASED_POINT_SIZE_RANGE": 33901,
+    "ALIASED_LINE_WIDTH_RANGE": 33902,
+    "CULL_FACE_MODE": 2885,
+    "FRONT_FACE": 2886,
+    "DEPTH_RANGE": 2928,
+    "DEPTH_WRITEMASK": 2930,
+    "DEPTH_CLEAR_VALUE": 2931,
+    "DEPTH_FUNC": 2932,
+    "STENCIL_CLEAR_VALUE": 2961,
+    "STENCIL_FUNC": 2962,
+    "STENCIL_FAIL": 2964,
+    "STENCIL_PASS_DEPTH_FAIL": 2965,
+    "STENCIL_PASS_DEPTH_PASS": 2966,
+    "STENCIL_REF": 2967,
+    "STENCIL_VALUE_MASK": 2963,
+    "STENCIL_WRITEMASK": 2968,
+    "STENCIL_BACK_FUNC": 34816,
+    "STENCIL_BACK_FAIL": 34817,
+    "STENCIL_BACK_PASS_DEPTH_FAIL": 34818,
+    "STENCIL_BACK_PASS_DEPTH_PASS": 34819,
+    "STENCIL_BACK_REF": 36003,
+    "STENCIL_BACK_VALUE_MASK": 36004,
+    "STENCIL_BACK_WRITEMASK": 36005,
+    "VIEWPORT": 2978,
+    "SCISSOR_BOX": 3088,
+    "COLOR_CLEAR_VALUE": 3106,
+    "COLOR_WRITEMASK": 3107,
+    "UNPACK_ALIGNMENT": 3317,
+    "PACK_ALIGNMENT": 3333,
+    "MAX_TEXTURE_SIZE": 3379,
+    "MAX_VIEWPORT_DIMS": 3386,
+    "SUBPIXEL_BITS": 3408,
+    "RED_BITS": 3410,
+    "GREEN_BITS": 3411,
+    "BLUE_BITS": 3412,
+    "ALPHA_BITS": 3413,
+    "DEPTH_BITS": 3414,
+    "STENCIL_BITS": 3415,
+    "POLYGON_OFFSET_UNITS": 10752,
+    "POLYGON_OFFSET_FACTOR": 32824,
+    "TEXTURE_BINDING_2D": 32873,
+    "SAMPLE_BUFFERS": 32936,
+    "SAMPLES": 32937,
+    "SAMPLE_COVERAGE_VALUE": 32938,
+    "SAMPLE_COVERAGE_INVERT": 32939,
+    "COMPRESSED_TEXTURE_FORMATS": 34467,
+    "DONT_CARE": 4352,
+    "FASTEST": 4353,
+    "NICEST": 4354,
+    "GENERATE_MIPMAP_HINT": 33170,
+    "BYTE": 5120,
+    "UNSIGNED_BYTE": 5121,
+    "SHORT": 5122,
+    "UNSIGNED_SHORT": 5123,
+    "INT": 5124,
+    "UNSIGNED_INT": 5125,
+    "FLOAT": 5126,
+    "DEPTH_COMPONENT": 6402,
+    "ALPHA": 6406,
+    "RGB": 6407,
+    "RGBA": 6408,
+    "LUMINANCE": 6409,
+    "LUMINANCE_ALPHA": 6410,
+    "UNSIGNED_SHORT_4_4_4_4": 32819,
+    "UNSIGNED_SHORT_5_5_5_1": 32820,
+    "UNSIGNED_SHORT_5_6_5": 33635,
+    "FRAGMENT_SHADER": 35632,
+    "VERTEX_SHADER": 35633,
+    "MAX_VERTEX_ATTRIBS": 34921,
+    "MAX_VERTEX_UNIFORM_VECTORS": 36347,
+    "MAX_VARYING_VECTORS": 36348,
+    "MAX_COMBINED_TEXTURE_IMAGE_UNITS": 35661,
+    "MAX_VERTEX_TEXTURE_IMAGE_UNITS": 35660,
+    "MAX_TEXTURE_IMAGE_UNITS": 34930,
+    "MAX_FRAGMENT_UNIFORM_VECTORS": 36349,
+    "SHADER_TYPE": 35663,
+    "DELETE_STATUS": 35712,
+    "LINK_STATUS": 35714,
+    "VALIDATE_STATUS": 35715,
+    "ATTACHED_SHADERS": 35717,
+    "ACTIVE_UNIFORMS": 35718,
+    "ACTIVE_ATTRIBUTES": 35721,
+    "SHADING_LANGUAGE_VERSION": 35724,
+    "CURRENT_PROGRAM": 35725,
+    "NEVER": 512,
+    "LESS": 513,
+    "EQUAL": 514,
+    "LEQUAL": 515,
+    "GREATER": 516,
+    "NOTEQUAL": 517,
+    "GEQUAL": 518,
+    "ALWAYS": 519,
+    "KEEP": 7680,
+    "REPLACE": 7681,
+    "INCR": 7682,
+    "DECR": 7683,
+    "INVERT": 5386,
+    "INCR_WRAP": 34055,
+    "DECR_WRAP": 34056,
+    "VENDOR": 7936,
+    "RENDERER": 7937,
+    "VERSION": 7938,
+    "NEAREST": 9728,
+    "LINEAR": 9729,
+    "NEAREST_MIPMAP_NEAREST": 9984,
+    "LINEAR_MIPMAP_NEAREST": 9985,
+    "NEAREST_MIPMAP_LINEAR": 9986,
+    "LINEAR_MIPMAP_LINEAR": 9987,
+    "TEXTURE_MAG_FILTER": 10240,
+    "TEXTURE_MIN_FILTER": 10241,
+    "TEXTURE_WRAP_S": 10242,
+    "TEXTURE_WRAP_T": 10243,
+    "TEXTURE": 5890,
+    "TEXTURE_CUBE_MAP": 34067,
+    "TEXTURE_BINDING_CUBE_MAP": 34068,
+    "TEXTURE_CUBE_MAP_POSITIVE_X": 34069,
+    "TEXTURE_CUBE_MAP_NEGATIVE_X": 34070,
+    "TEXTURE_CUBE_MAP_POSITIVE_Y": 34071,
+    "TEXTURE_CUBE_MAP_NEGATIVE_Y": 34072,
+    "TEXTURE_CUBE_MAP_POSITIVE_Z": 34073,
+    "TEXTURE_CUBE_MAP_NEGATIVE_Z": 34074,
+    "MAX_CUBE_MAP_TEXTURE_SIZE": 34076,
+    "TEXTURE0": 33984,
+    "TEXTURE1": 33985,
+    "TEXTURE2": 33986,
+    "TEXTURE3": 33987,
+    "TEXTURE4": 33988,
+    "TEXTURE5": 33989,
+    "TEXTURE6": 33990,
+    "TEXTURE7": 33991,
+    "TEXTURE8": 33992,
+    "TEXTURE9": 33993,
+    "TEXTURE10": 33994,
+    "TEXTURE11": 33995,
+    "TEXTURE12": 33996,
+    "TEXTURE13": 33997,
+    "TEXTURE14": 33998,
+    "TEXTURE15": 33999,
+    "TEXTURE16": 34000,
+    "TEXTURE17": 34001,
+    "TEXTURE18": 34002,
+    "TEXTURE19": 34003,
+    "TEXTURE20": 34004,
+    "TEXTURE21": 34005,
+    "TEXTURE22": 34006,
+    "TEXTURE23": 34007,
+    "TEXTURE24": 34008,
+    "TEXTURE25": 34009,
+    "TEXTURE26": 34010,
+    "TEXTURE27": 34011,
+    "TEXTURE28": 34012,
+    "TEXTURE29": 34013,
+    "TEXTURE30": 34014,
+    "TEXTURE31": 34015,
+    "ACTIVE_TEXTURE": 34016,
+    "REPEAT": 10497,
+    "CLAMP_TO_EDGE": 33071,
+    "MIRRORED_REPEAT": 33648,
+    "FLOAT_VEC2": 35664,
+    "FLOAT_VEC3": 35665,
+    "FLOAT_VEC4": 35666,
+    "INT_VEC2": 35667,
+    "INT_VEC3": 35668,
+    "INT_VEC4": 35669,
+    "BOOL": 35670,
+    "BOOL_VEC2": 35671,
+    "BOOL_VEC3": 35672,
+    "BOOL_VEC4": 35673,
+    "FLOAT_MAT2": 35674,
+    "FLOAT_MAT3": 35675,
+    "FLOAT_MAT4": 35676,
+    "SAMPLER_2D": 35678,
+    "SAMPLER_CUBE": 35680,
+    "VERTEX_ATTRIB_ARRAY_ENABLED": 34338,
+    "VERTEX_ATTRIB_ARRAY_SIZE": 34339,
+    "VERTEX_ATTRIB_ARRAY_STRIDE": 34340,
+    "VERTEX_ATTRIB_ARRAY_TYPE": 34341,
+    "VERTEX_ATTRIB_ARRAY_NORMALIZED": 34922,
+    "VERTEX_ATTRIB_ARRAY_POINTER": 34373,
+    "VERTEX_ATTRIB_ARRAY_BUFFER_BINDING": 34975,
+    "IMPLEMENTATION_COLOR_READ_TYPE": 35738,
+    "IMPLEMENTATION_COLOR_READ_FORMAT": 35739,
+    "COMPILE_STATUS": 35713,
+    "LOW_FLOAT": 36336,
+    "MEDIUM_FLOAT": 36337,
+    "HIGH_FLOAT": 36338,
+    "LOW_INT": 36339,
+    "MEDIUM_INT": 36340,
+    "HIGH_INT": 36341,
+    "FRAMEBUFFER": 36160,
+    "RENDERBUFFER": 36161,
+    "RGBA4": 32854,
+    "RGB5_A1": 32855,
+    "RGB565": 36194,
+    "DEPTH_COMPONENT16": 33189,
+    "STENCIL_INDEX8": 36168,
+    "DEPTH_STENCIL": 34041,
+    "RENDERBUFFER_WIDTH": 36162,
+    "RENDERBUFFER_HEIGHT": 36163,
+    "RENDERBUFFER_INTERNAL_FORMAT": 36164,
+    "RENDERBUFFER_RED_SIZE": 36176,
+    "RENDERBUFFER_GREEN_SIZE": 36177,
+    "RENDERBUFFER_BLUE_SIZE": 36178,
+    "RENDERBUFFER_ALPHA_SIZE": 36179,
+    "RENDERBUFFER_DEPTH_SIZE": 36180,
+    "RENDERBUFFER_STENCIL_SIZE": 36181,
+    "FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE": 36048,
+    "FRAMEBUFFER_ATTACHMENT_OBJECT_NAME": 36049,
+    "FRAMEBUFFER_ATTACHMENT_TEXTURE_LEVEL": 36050,
+    "FRAMEBUFFER_ATTACHMENT_TEXTURE_CUBE_MAP_FACE": 36051,
+    "COLOR_ATTACHMENT0": 36064,
+    "DEPTH_ATTACHMENT": 36096,
+    "STENCIL_ATTACHMENT": 36128,
+    "DEPTH_STENCIL_ATTACHMENT": 33306,
+    "NONE": 0,
+    "FRAMEBUFFER_COMPLETE": 36053,
+    "FRAMEBUFFER_INCOMPLETE_ATTACHMENT": 36054,
+    "FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT": 36055,
+    "FRAMEBUFFER_INCOMPLETE_DIMENSIONS": 36057,
+    "FRAMEBUFFER_UNSUPPORTED": 36061,
+    "FRAMEBUFFER_BINDING": 36006,
+    "RENDERBUFFER_BINDING": 36007,
+    "MAX_RENDERBUFFER_SIZE": 34024,
+    "INVALID_FRAMEBUFFER_OPERATION": 1286,
+    "UNPACK_FLIP_Y_WEBGL": 37440,
+    "UNPACK_PREMULTIPLY_ALPHA_WEBGL": 37441,
+    "CONTEXT_LOST_WEBGL": 37442,
+    "UNPACK_COLORSPACE_CONVERSION_WEBGL": 37443,
+    "BROWSER_DEFAULT_WEBGL": 37444
+};

+ 142 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/GLmethod.js

@@ -0,0 +1,142 @@
+let i = 1;
+
+const GLmethod = {};
+
+GLmethod.activeTexture = i++;         //1
+GLmethod.attachShader = i++;
+GLmethod.bindAttribLocation = i++;
+GLmethod.bindBuffer = i++;
+GLmethod.bindFramebuffer = i++;
+GLmethod.bindRenderbuffer = i++;
+GLmethod.bindTexture = i++;
+GLmethod.blendColor = i++;
+GLmethod.blendEquation = i++;
+GLmethod.blendEquationSeparate = i++; //10
+GLmethod.blendFunc = i++;
+GLmethod.blendFuncSeparate = i++;
+GLmethod.bufferData = i++;
+GLmethod.bufferSubData = i++;
+GLmethod.checkFramebufferStatus = i++;
+GLmethod.clear = i++;
+GLmethod.clearColor = i++;
+GLmethod.clearDepth = i++;
+GLmethod.clearStencil = i++;
+GLmethod.colorMask = i++;              //20
+GLmethod.compileShader = i++;
+GLmethod.compressedTexImage2D = i++;
+GLmethod.compressedTexSubImage2D = i++;
+GLmethod.copyTexImage2D = i++;
+GLmethod.copyTexSubImage2D = i++;
+GLmethod.createBuffer = i++;
+GLmethod.createFramebuffer = i++;
+GLmethod.createProgram = i++;
+GLmethod.createRenderbuffer = i++;
+GLmethod.createShader = i++;           //30
+GLmethod.createTexture = i++;
+GLmethod.cullFace = i++;
+GLmethod.deleteBuffer = i++;
+GLmethod.deleteFramebuffer = i++;
+GLmethod.deleteProgram = i++;
+GLmethod.deleteRenderbuffer = i++;
+GLmethod.deleteShader = i++;
+GLmethod.deleteTexture = i++;
+GLmethod.depthFunc = i++;
+GLmethod.depthMask = i++;              //40
+GLmethod.depthRange = i++;
+GLmethod.detachShader = i++;
+GLmethod.disable = i++;
+GLmethod.disableVertexAttribArray = i++;
+GLmethod.drawArrays = i++;
+GLmethod.drawArraysInstancedANGLE = i++;
+GLmethod.drawElements = i++;
+GLmethod.drawElementsInstancedANGLE = i++;
+GLmethod.enable = i++;
+GLmethod.enableVertexAttribArray = i++;    //50
+GLmethod.flush = i++;
+GLmethod.framebufferRenderbuffer = i++;
+GLmethod.framebufferTexture2D = i++;
+GLmethod.frontFace = i++;
+GLmethod.generateMipmap = i++;
+GLmethod.getActiveAttrib = i++;
+GLmethod.getActiveUniform = i++;
+GLmethod.getAttachedShaders = i++;
+GLmethod.getAttribLocation = i++;
+GLmethod.getBufferParameter = i++;         //60
+GLmethod.getContextAttributes = i++;
+GLmethod.getError = i++;
+GLmethod.getExtension = i++;
+GLmethod.getFramebufferAttachmentParameter = i++;
+GLmethod.getParameter = i++;
+GLmethod.getProgramInfoLog = i++;
+GLmethod.getProgramParameter = i++;
+GLmethod.getRenderbufferParameter = i++;
+GLmethod.getShaderInfoLog = i++;
+GLmethod.getShaderParameter = i++;         //70
+GLmethod.getShaderPrecisionFormat = i++;
+GLmethod.getShaderSource = i++;
+GLmethod.getSupportedExtensions = i++;
+GLmethod.getTexParameter = i++;
+GLmethod.getUniform = i++;
+GLmethod.getUniformLocation = i++;
+GLmethod.getVertexAttrib = i++;
+GLmethod.getVertexAttribOffset = i++;
+GLmethod.isBuffer = i++;
+GLmethod.isContextLost = i++;              //80
+GLmethod.isEnabled = i++;
+GLmethod.isFramebuffer = i++;
+GLmethod.isProgram = i++;
+GLmethod.isRenderbuffer = i++;
+GLmethod.isShader = i++;
+GLmethod.isTexture = i++;
+GLmethod.lineWidth = i++;
+GLmethod.linkProgram = i++;
+GLmethod.pixelStorei = i++;
+GLmethod.polygonOffset = i++;              //90
+GLmethod.readPixels = i++;
+GLmethod.renderbufferStorage = i++;
+GLmethod.sampleCoverage = i++;
+GLmethod.scissor = i++;
+GLmethod.shaderSource = i++;
+GLmethod.stencilFunc = i++;
+GLmethod.stencilFuncSeparate = i++;
+GLmethod.stencilMask = i++;
+GLmethod.stencilMaskSeparate = i++;
+GLmethod.stencilOp = i++;                  //100
+GLmethod.stencilOpSeparate = i++;
+GLmethod.texImage2D = i++;
+GLmethod.texParameterf = i++;
+GLmethod.texParameteri = i++;
+GLmethod.texSubImage2D = i++;
+GLmethod.uniform1f = i++;
+GLmethod.uniform1fv = i++;
+GLmethod.uniform1i = i++;
+GLmethod.uniform1iv = i++;
+GLmethod.uniform2f = i++;                  //110
+GLmethod.uniform2fv = i++;
+GLmethod.uniform2i = i++;
+GLmethod.uniform2iv = i++;
+GLmethod.uniform3f = i++;
+GLmethod.uniform3fv = i++;
+GLmethod.uniform3i = i++;
+GLmethod.uniform3iv = i++;
+GLmethod.uniform4f = i++;
+GLmethod.uniform4fv = i++;
+GLmethod.uniform4i = i++;                  //120
+GLmethod.uniform4iv = i++;
+GLmethod.uniformMatrix2fv = i++;
+GLmethod.uniformMatrix3fv = i++;
+GLmethod.uniformMatrix4fv = i++;
+GLmethod.useProgram = i++;
+GLmethod.validateProgram = i++;
+GLmethod.vertexAttrib1f = i++; //new
+GLmethod.vertexAttrib2f = i++; //new
+GLmethod.vertexAttrib3f = i++; //new
+GLmethod.vertexAttrib4f = i++; //new       //130
+GLmethod.vertexAttrib1fv = i++; //new
+GLmethod.vertexAttrib2fv = i++; //new
+GLmethod.vertexAttrib3fv = i++; //new
+GLmethod.vertexAttrib4fv = i++; //new
+GLmethod.vertexAttribPointer = i++;
+GLmethod.viewport = i++;
+
+export default GLmethod;

+ 23 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/GLtype.js

@@ -0,0 +1,23 @@
+const GLtype = {};
+
+[
+    "GLbitfield",    
+    "GLboolean",
+    "GLbyte",
+    "GLclampf",
+    "GLenum",
+    "GLfloat",
+    "GLint",
+    "GLintptr",
+    "GLsizei",
+    "GLsizeiptr",
+    "GLshort",
+    "GLubyte",
+    "GLuint",
+    "GLushort"
+].sort().map((typeName, i) => GLtype[typeName] = 1 >> (i + 1));
+
+export default GLtype;
+
+
+

+ 21 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Program.js

@@ -0,0 +1,21 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLProgram';
+
+function uuid(id) {
+    return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLProgram {
+    className = name;
+
+    constructor(id) {
+        this.id = id;
+    }
+
+    static uuid = uuid;
+
+    uuid() {
+        return uuid(this.id);
+    }
+}

+ 21 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Renderbuffer.js

@@ -0,0 +1,21 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLRenderBuffer';
+
+function uuid(id) {
+    return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLRenderbuffer {
+    className = name;
+
+    constructor(id) {
+        this.id = id;
+    }
+
+    static uuid = uuid;
+
+    uuid() {
+        return uuid(this.id);
+    }
+}

+ 1191 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/RenderingContext.js

@@ -0,0 +1,1191 @@
+import GLenum from './GLenum';
+import ActiveInfo from './ActiveInfo';
+import Buffer from './Buffer';
+import Framebuffer from './Framebuffer';
+import Renderbuffer from './Renderbuffer';
+import Texture from './Texture';
+import Program from './Program';
+import Shader from './Shader';
+import ShaderPrecisionFormat from './ShaderPrecisionFormat';
+import UniformLocation from './UniformLocation';
+import GLmethod from './GLmethod';
+
+const processArray = (array, checkArrayType = false) => {
+
+    function joinArray(arr, sep) {
+        let res = '';
+        for (let i = 0; i < arr.length; i++) {
+            if (i !== 0) {
+                res += sep;
+            }
+            res += arr[i];
+        }
+        return res;
+    }
+
+    let type = 'Float32Array';
+    if (checkArrayType) {
+        if (array instanceof Uint8Array) {
+            type = 'Uint8Array'
+        } else if (array instanceof Uint16Array) {
+            type = 'Uint16Array';
+        } else if (array instanceof Uint32Array) {
+            type = 'Uint32Array';
+        } else if (array instanceof Float32Array) {
+            type = 'Float32Array';
+        } else {
+            throw new Error('Check array type failed. Array type is ' + typeof array);
+        }
+    }
+
+    const ArrayTypes = {
+        Uint8Array: 1,
+        Uint16Array: 2,
+        Uint32Array: 4,
+        Float32Array: 14
+    };
+    return ArrayTypes[type] + ',' + btoa(joinArray(array, ','))
+}
+
+export default class WebGLRenderingContext {
+
+    // static GBridge = null;
+
+    className = 'WebGLRenderingContext';
+
+    constructor(canvas, type, attrs) {
+        this._canvas = canvas;
+        this._type = type;
+        this._version = 'WebGL 1.0';
+        this._attrs = attrs;
+        this._map = new Map();
+
+        Object.keys(GLenum)
+            .forEach(name => Object.defineProperty(this, name, {
+                value: GLenum[name]
+            }));
+    }
+
+    get canvas() {
+        return this._canvas;
+    }
+
+    activeTexture = function (textureUnit) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.activeTexture + ',' + textureUnit,
+            true
+        );
+    }
+
+    attachShader = function (progarm, shader) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.attachShader + ',' + progarm.id + ',' + shader.id,
+            true
+        );
+    }
+
+    bindAttribLocation = function (program, index, name) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.bindAttribLocation + ',' + program.id + ',' + index + ',' + name,
+            true
+        )
+    }
+
+    bindBuffer = function (target, buffer) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.bindBuffer + ',' + target + ',' + (buffer ? buffer.id : 0),
+            true
+        );
+    }
+
+    bindFramebuffer = function (target, framebuffer) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.bindFramebuffer + ',' + target + ',' + (framebuffer ? framebuffer.id : 0),
+            true
+        )
+    }
+
+    bindRenderbuffer = function (target, renderBuffer) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.bindRenderbuffer + ',' + target + ',' + (renderBuffer ? renderBuffer.id : 0),
+            true
+        )
+    }
+
+    bindTexture = function (target, texture) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.bindTexture + ',' + target + ',' + (texture ? texture.id : 0),
+            true
+        )
+    }
+
+    blendColor = function (r, g, b, a) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.blendColor + ',' + target + ',' + r + ',' + g + ',' + b + ',' + a,
+            true
+        )
+    }
+
+    blendEquation = function (mode) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.blendEquation + ',' + mode,
+            true
+        )
+    }
+
+    blendEquationSeparate = function (modeRGB, modeAlpha) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.blendEquationSeparate + ',' + modeRGB + ',' + modeAlpha,
+            true
+        )
+    }
+
+
+    blendFunc = function (sfactor, dfactor) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.blendFunc + ',' + sfactor + ',' + dfactor,
+            true
+        );
+    }
+
+    blendFuncSeparate = function (srcRGB, dstRGB, srcAlpha, dstAlpha) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.blendFuncSeparate + ',' + srcRGB + ',' + dstRGB + ',' + srcAlpha + ',' + dstAlpha,
+            true
+        );
+    }
+
+    bufferData = function (target, data, usage) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.bufferData + ',' + target + ',' + processArray(data, true) + ',' + usage,
+            true
+        )
+    }
+
+    bufferSubData = function (target, offset, data) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.bufferSubData + ',' + target + ',' + offset + ',' + processArray(data, true),
+            true
+        )
+    }
+
+    checkFramebufferStatus = function (target) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.checkFramebufferStatus + ',' + target
+        );
+        return Number(result);
+    }
+
+    clear = function (mask) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.clear + ',' + mask
+        );
+        this._canvas._needRender = true;
+    }
+
+    clearColor = function (r, g, b, a) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.clearColor + ',' + r + ',' + g + ',' + b,
+            true
+        )
+    }
+
+    clearDepth = function (depth) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.clearDepth + ',' + depth,
+            true
+        )
+    }
+
+    clearStencil = function (s) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.clearStencil + ',' + s
+        );
+    }
+
+    colorMask = function (r, g, b, a) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.colorMask + ',' + r + ',' + g + ',' + b + ',' + a
+        )
+    }
+
+    compileShader = function (shader) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.compileShader + ',' + shader.id,
+            true
+        )
+    }
+
+    compressedTexImage2D = function (target, level, internalformat, width, height, border, pixels) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.compressedTexImage2D + ',' + target + ',' + level + ',' + internalformat + ',' +
+            width + ',' + height + ',' + border + ',' + processArray(pixels),
+            true
+        )
+    }
+
+    compressedTexSubImage2D = function (target, level, xoffset, yoffset, width, height, format, pixels) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.compressedTexSubImage2D + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset + ',' +
+            width + ',' + height + ',' + format + ',' + processArray(pixels),
+            true
+        )
+    }
+
+
+    copyTexImage2D = function (target, level, internalformat, x, y, width, height, border) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.copyTexImage2D + ',' + target + ',' + level + ',' + internalformat + ',' + x + ',' + y + ',' +
+            width + ',' + height + ',' + border,
+            true
+        );
+    }
+
+    copyTexSubImage2D = function (target, level, xoffset, yoffset, x, y, width, height) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.copyTexSubImage2D + ',' + target + ',' + level + ',' + xoffset + ',' + yoffset + ',' + x + ',' + y + ',' +
+            width + ',' + height
+        );
+    }
+
+    createBuffer = function () {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.createBuffer + ''
+        );
+        const buffer = new Buffer(result);
+        this._map.set(buffer.uuid(), buffer);
+        return buffer;
+    }
+
+    createFramebuffer = function () {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.createFramebuffer + ''
+        );
+        const framebuffer = new Framebuffer(result);
+        this._map.set(framebuffer.uuid(), framebuffer);
+        return framebuffer;
+    }
+
+
+    createProgram = function () {
+        const id = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.createProgram + ''
+        );
+        const program = new Program(id);
+        this._map.set(program.uuid(), program);
+        return program;
+    }
+
+    createRenderbuffer = function () {
+        const id = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.createRenderbuffer + ''
+        )
+        const renderBuffer = new Renderbuffer(id);
+        this._map.set(renderBuffer.uuid(), renderBuffer);
+        return renderBuffer;
+    }
+
+    createShader = function (type) {
+        const id = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.createShader + ',' + type
+        )
+        const shader = new Shader(id, type);
+        this._map.set(shader.uuid(), shader);
+        return shader;
+    }
+
+    createTexture = function () {
+        const id = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.createTexture + ''
+        );
+        const texture = new Texture(id);
+        this._map.set(texture.uuid(), texture);
+        return texture;
+    }
+
+    cullFace = function (mode) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.cullFace + ',' + mode,
+            true
+        )
+    }
+
+
+    deleteBuffer = function (buffer) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.deleteBuffer + ',' + buffer.id,
+            true
+        )
+    }
+
+    deleteFramebuffer = function (framebuffer) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.deleteFramebuffer + ',' + framebuffer.id,
+            true
+        )
+    }
+
+    deleteProgram = function (program) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.deleteProgram + ',' + program.id,
+            true
+        )
+    }
+
+    deleteRenderbuffer = function (renderbuffer) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.deleteRenderbuffer + ',' + renderbuffer.id,
+            true
+        )
+    }
+
+    deleteShader = function (shader) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.deleteShader + ',' + shader.id,
+            true
+        )
+    }
+
+    deleteTexture = function (texture) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.deleteTexture + ',' + texture.id,
+            true
+        )
+    }
+
+    depthFunc = function (func) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.depthFunc + ',' + func
+        )
+    }
+
+    depthMask = function (flag) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.depthMask + ',' + Number(flag),
+            true
+        )
+    }
+
+    depthRange = function (zNear, zFar) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.depthRange + ',' + zNear + ',' + zFar,
+            true
+        )
+    }
+
+    detachShader = function (program, shader) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.detachShader + ',' + program.id + ',' + shader.id,
+            true
+        )
+    }
+
+    disable = function (cap) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.disable + ',' + cap,
+            true
+        )
+    }
+
+    disableVertexAttribArray = function (index) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.disableVertexAttribArray + ',' + index,
+            true
+        );
+    }
+
+    drawArrays = function (mode, first, count) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.drawArrays + ',' + mode + ',' + first + ',' + count
+        )
+        this._canvas._needRender = true;
+    }
+
+    drawElements = function (mode, count, type, offset) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.drawElements + ',' + mode + ',' + count + ',' + type + ',' + offset + ';'
+        );
+        this._canvas._needRender = true;
+    }
+
+    enable = function (cap) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.enable + ',' + cap,
+            true
+        );
+    }
+
+    enableVertexAttribArray = function (index) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.enableVertexAttribArray + ',' + index,
+            true
+        )
+    }
+
+
+    flush = function () {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.flush + ''
+        )
+    }
+
+    framebufferRenderbuffer = function (target, attachment, textarget, texture, level) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.framebufferRenderbuffer + ',' + target + ',' + attachment + ',' + textarget + ',' + (texture ? texture.id : 0) + ',' + level,
+            true
+        )
+    }
+
+    framebufferTexture2D = function (target, attachment, textarget, texture, level) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.framebufferTexture2D + ',' + target + ',' + attachment + ',' + textarget + ',' + (texture ? texture.id : 0) + ',' + level,
+            true
+        )
+    }
+
+    frontFace = function (mode) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.frontFace + ',' + mode,
+            true
+        )
+    }
+
+    generateMipmap = function (target) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.generateMipmap + ',' + target,
+            true
+        )
+    }
+
+    getActiveAttrib = function (progarm, index) {
+        const resultString = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getActiveAttrib + ',' + progarm.id + ',' + index
+        )
+        const [type, size, name] = resultString.split(',');
+        return new ActiveInfo({
+            type: Number(type),
+            size: Number(size),
+            name
+        });
+    }
+
+    getActiveUniform = function (progarm, index) {
+        const resultString = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getActiveUniform + ',' + progarm.id + ',' + index
+        );
+        const [type, size, name] = resultString.split(',');
+        return new ActiveInfo({
+            type: Number(type),
+            size: Number(size),
+            name
+        })
+    }
+
+    getAttachedShaders = function (progarm) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getAttachedShaders + ',' + progarm.id
+        );
+        const [type, ...ids] = result;
+        return ids.map(id => this._map.get(Shader.uuid(id)));
+    }
+
+    getAttribLocation = function (progarm, name) {
+        return WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getAttribLocation + ',' + progarm.id + ',' + name
+        )
+    }
+
+    getBufferParameter = function (target, pname) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getBufferParameter + ',' + target + ',' + pname
+        );
+        const [type, res] = getBufferParameter;
+        return res;
+    }
+
+    getError = function () {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getError + ''
+        )
+        return result;
+    }
+
+    getExtension = function (name) {
+        return null;
+    }
+
+    getFramebufferAttachmentParameter = function (target, attachment, pname) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getFramebufferAttachmentParameter + ',' + target + ',' + attachment + ',' + pname
+        )
+        switch (pname) {
+            case GLenum.FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
+                return this._map.get(Renderbuffer.uuid(result)) || this._map.get(Texture.uuid(result)) || null;
+            default:
+                return result;
+        }
+    }
+
+    getParameter = function (pname) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getParameter + ',' + pname
+        )
+        switch (pname) {
+            case GLenum.VERSION:
+                return this._version;
+            case GLenum.ARRAY_BUFFER_BINDING: // buffer
+            case GLenum.ELEMENT_ARRAY_BUFFER_BINDING: // buffer
+                return this._map.get(Buffer.uuid(result)) || null;
+            case GLenum.CURRENT_PROGRAM: // program
+                return this._map.get(Program.uuid(result)) || null;
+            case GLenum.FRAMEBUFFER_BINDING: // framebuffer
+                return this._map.get(Framebuffer.uuid(result)) || null;
+            case GLenum.RENDERBUFFER_BINDING: // renderbuffer
+                return this._map.get(Renderbuffer.uuid(result)) || null;
+            case GLenum.TEXTURE_BINDING_2D: // texture
+            case GLenum.TEXTURE_BINDING_CUBE_MAP: // texture
+                return this._map.get(Texture.uuid(result)) || null;
+            case GLenum.ALIASED_LINE_WIDTH_RANGE: // Float32Array
+            case GLenum.ALIASED_POINT_SIZE_RANGE: // Float32Array
+            case GLenum.BLEND_COLOR: // Float32Array
+            case GLenum.COLOR_CLEAR_VALUE: // Float32Array
+            case GLenum.DEPTH_RANGE: // Float32Array
+            case GLenum.MAX_VIEWPORT_DIMS: // Int32Array
+            case GLenum.SCISSOR_BOX: // Int32Array
+            case GLenum.VIEWPORT: // Int32Array            
+            case GLenum.COMPRESSED_TEXTURE_FORMATS: // Uint32Array
+            default:
+                const [type, ...res] = result.split(',');
+                if (res.length === 1) {
+                    return Number(res[0]);
+                } else {
+                    return res.map(Number);
+                }
+        }
+    }
+
+    getProgramInfoLog = function (progarm) {
+        return WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getProgramInfoLog + ',' + progarm.id
+        )
+    }
+
+    getProgramParameter = function (program, pname) {
+        const res = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getProgramParameter + ',' + program.id + ',' + pname
+        );
+
+        const [type, result] = res.split(',').map(i => parseInt(i));
+
+        if (type === 1) {
+            return Boolean(result);
+        } else if (type === 2) {
+            return result;
+        } else {
+            throw new Error('Unrecongized program paramater ' + res + ', type: ' + typeof res);
+        }
+    }
+
+
+    getRenderbufferParameter = function (target, pname) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getRenderbufferParameter + ',' + target + ',' + pname
+        )
+        return result;
+    }
+
+
+    getShaderInfoLog = function (shader) {
+        return WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getShaderInfoLog + ',' + shader.id
+        );
+    }
+
+    getShaderParameter = function (shader, pname) {
+        return WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getShaderParameter + ',' + shader.id + ',' + pname
+        )
+    }
+
+    getShaderPrecisionFormat = function (shaderType, precisionType) {
+        const [rangeMin, rangeMax, precision] = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getShaderPrecisionFormat + ',' + shaderType + ',' + precisionType
+        );
+        const shaderPrecisionFormat = new ShaderPrecisionFormat({
+            rangeMin: Number(rangeMin),
+            rangeMax: Number(rangeMax),
+            precision: Number(precision)
+        });
+        return shaderPrecisionFormat;
+    }
+
+    getShaderSource = function (shader) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getShaderSource + ',' + shader.id
+        );
+        return result;
+    }
+
+    getSupportedExtensions = function () {
+        return Object.keys({});
+    }
+
+    getTexParameter = function (target, pname) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getTexParameter + ',' + target + ',' + pname
+        )
+        return result;
+    }
+
+    getUniformLocation = function (program, name) {
+        const id = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getUniformLocation + ',' + program.id + ',' + name
+        );
+        if (id === -1) {
+            return null;
+        } else {
+            return new UniformLocation(Number(id));
+        }
+    }
+
+    getVertexAttrib = function (index, pname) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getVertexAttrib + ',' + index + ',' + pname
+        );
+        switch (pname) {
+            case GLenum.VERTEX_ATTRIB_ARRAY_BUFFER_BINDING:
+                return this._map.get(Buffer.uuid(result)) || null;
+            case GLenum.CURRENT_VERTEX_ATTRIB: // Float32Array
+            default:
+                return result;
+        }
+    }
+
+    getVertexAttribOffset = function (index, pname) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.getVertexAttribOffset + ',' + index + ',' + pname
+        )
+        return Number(result);
+    }
+
+    isBuffer = function (buffer) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.isBuffer + ',' + buffer.id
+        )
+        return Boolean(result);
+    }
+
+    isContextLost = function () {
+        return false;
+    }
+
+    isEnabled = function (cap) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.isEnabled + ',' + cap
+        )
+        return Boolean(result);
+    }
+
+    isFramebuffer = function (framebuffer) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.isFramebuffer + ',' + framebuffer.id
+        )
+        return Boolean(result);
+    }
+
+    isProgram = function (program) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.isProgram + ',' + program.id
+        )
+        return Boolean(result);
+    }
+
+    isRenderbuffer = function (renderBuffer) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.isRenderbuffer + ',' + renderbuffer.id
+        )
+        return Boolean(result);
+    }
+
+    isShader = function (shader) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.isShader + ',' + shader.id
+        )
+        return Boolean(result);
+    }
+
+    isTexture = function (texture) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.isTexture + ',' + texture.id
+        );
+        return Boolean(result);
+    }
+
+    lineWidth = function (width) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.lineWidth + ',' + width,
+            true
+        )
+    }
+
+    linkProgram = function (program) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.linkProgram + ',' + program.id,
+            true
+        );
+    }
+
+
+    pixelStorei = function (pname, param) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.pixelStorei + ',' + pname + ',' + Number(param)
+        )
+    }
+
+    polygonOffset = function (factor, units) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.polygonOffset + ',' + factor + ',' + units
+        )
+    }
+
+    readPixels = function (x, y, width, height, format, type, pixels) {
+        const result = WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.readPixels + ',' + x + ',' + y + ',' + width + ',' + height + ',' + format + ',' + type
+        )
+        return result;
+    }
+
+    renderbufferStorage = function (target, internalFormat, width, height) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.renderbufferStorage + ',' + target + ',' + internalFormat + ',' + width + ',' + height,
+            true
+        )
+    }
+
+    sampleCoverage = function (value, invert) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.sampleCoverage + ',' + value + ',' + Number(invert),
+            true
+        )
+    }
+
+    scissor = function (x, y, width, height) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.scissor + ',' + x + ',' + y + ',' + width + ',' + height,
+            true
+        )
+    }
+
+    shaderSource = function (shader, source) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.shaderSource + ',' + shader.id + ',' + source
+        )
+    }
+
+    stencilFunc = function (func, ref, mask) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.stencilFunc + ',' + func + ',' + ref + ',' + mask,
+            true
+        )
+    }
+
+    stencilFuncSeparate = function (face, func, ref, mask) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.stencilFuncSeparate + ',' + face + ',' + func + ',' + ref + ',' + mask,
+            true
+        )
+    }
+
+    stencilMask = function (mask) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.stencilMask + ',' + mask,
+            true
+        )
+    }
+
+    stencilMaskSeparate = function (face, mask) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.stencilMaskSeparate + ',' + face + ',' + mask,
+            true
+        )
+    }
+
+    stencilOp = function (fail, zfail, zpass) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.stencilOp + ',' + fail + ',' + zfail + ',' + zpass
+        )
+    }
+
+    stencilOpSeparate = function (face, fail, zfail, zpass) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.stencilOp + ',' + face + ',' + fail + ',' + zfail + ',' + zpass,
+            true
+        )
+    }
+
+    texImage2D = function (...args) {
+        WebGLRenderingContext.GBridge.texImage2D(this._canvas.id, ...args);
+    }
+
+
+    texParameterf = function (target, pname, param) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.texParameterf + ',' + target + ',' + pname + ',' + param,
+            true
+        )
+    }
+
+    texParameteri = function (target, pname, param) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.texParameteri + ',' + target + ',' + pname + ',' + param
+        )
+    }
+
+    texSubImage2D = function (...args) {
+        WebGLRenderingContext.GBridge.texSubImage2D(this._canvas.id, ...args);
+    }
+
+    uniform1f = function (location, v0) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform1f + ',' + location.id + ',' + v0
+        )
+    }
+
+    uniform1fv = function (location, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform1fv + ',' + location.id + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniform1i = function (location, v0) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform1i + ',' + location.id + ',' + v0,
+            // true
+        )
+    }
+
+    uniform1iv = function (location, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform1iv + ',' + location.id + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniform2f = function (location, v0, v1) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform2f + ',' + location.id + ',' + v0 + ',' + v1,
+            true
+        )
+    }
+
+    uniform2fv = function (location, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform2fv + ',' + location.id + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniform2i = function (location, v0, v1) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform2i + ',' + location.id + ',' + v0 + ',' + v1,
+            true
+        )
+    }
+
+    uniform2iv = function (location, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform2iv + ',' + location.id + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniform3f = function (location, v0, v1, v2) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform3f + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2,
+            true
+        )
+    }
+
+    uniform3fv = function (location, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform3fv + ',' + location.id + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniform3i = function (location, v0, v1, v2) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform3i + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2,
+            true
+        )
+    }
+
+    uniform3iv = function (location, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform3iv + ',' + location.id + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniform4f = function (location, v0, v1, v2, v3) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform4f + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,
+            true
+        )
+    }
+
+    uniform4fv = function (location, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform4fv + ',' + location.id + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniform4i = function (location, v0, v1, v2, v3) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform4i + ',' + location.id + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,
+            true
+        )
+    }
+
+    uniform4iv = function (location, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniform4iv + ',' + location.id + ',' + processArray(value, true),
+            true
+        )
+    }
+
+    uniformMatrix2fv = function (location, transpose, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniformMatrix2fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniformMatrix3fv = function (location, transpose, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniformMatrix3fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),
+            true
+        )
+    }
+
+    uniformMatrix4fv = function (location, transpose, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.uniformMatrix4fv + ',' + location.id + ',' + Number(transpose) + ',' + processArray(value),
+            true
+        );
+    }
+
+    useProgram = function (progarm) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.useProgram + ',' + progarm.id + '',
+            true
+        )
+    }
+
+
+    validateProgram = function (program) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.validateProgram + ',' + program.id,
+            true
+        )
+    }
+
+    vertexAttrib1f = function (index, v0) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttrib1f + ',' + index + ',' + v0,
+            true
+        )
+    }
+
+    vertexAttrib2f = function (index, v0, v1) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttrib2f + ',' + index + ',' + v0 + ',' + v1,
+            true
+        )
+    }
+
+    vertexAttrib3f = function (index, v0, v1, v2) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttrib3f + ',' + index + ',' + v0 + ',' + v1 + ',' + v2,
+            true
+        )
+    }
+
+    vertexAttrib4f = function (index, v0, v1, v2, v3) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttrib4f + ',' + index + ',' + v0 + ',' + v1 + ',' + v2 + ',' + v3,
+            true
+        )
+    }
+
+    vertexAttrib1fv = function (index, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttrib1fv + ',' + index + ',' + processArray(value),
+            true
+        )
+    }
+
+    vertexAttrib2fv = function (index, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttrib2fv + ',' + index + ',' + processArray(value),
+            true
+        )
+    }
+
+    vertexAttrib3fv = function (index, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttrib3fv + ',' + index + ',' + processArray(value),
+            true
+        )
+    }
+
+    vertexAttrib4fv = function (index, value) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttrib4fv + ',' + index + ',' + processArray(value),
+            true
+        )
+    }
+
+    vertexAttribPointer = function (index, size, type, normalized, stride, offset) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.vertexAttribPointer + ',' + index + ',' + size + ',' + type + ',' + Number(normalized) + ',' + stride + ',' + offset,
+            true
+        )
+    }
+
+    viewport = function (x, y, width, height) {
+        WebGLRenderingContext.GBridge.callNative(
+            this._canvas.id,
+            GLmethod.viewport + ',' + x + ',' + y + ',' + width + ',' + height,
+            true
+        )
+    }
+}

+ 22 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Shader.js

@@ -0,0 +1,22 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLShader';
+
+function uuid(id) {
+    return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLShader {
+    className = name;
+
+    constructor(id, type) {
+        this.id = id;
+        this.type = type;
+    }
+
+    static uuid = uuid;
+
+    uuid() {
+        return uuid(this.id);
+    }
+}

+ 11 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/ShaderPrecisionFormat.js

@@ -0,0 +1,11 @@
+export default class WebGLShaderPrecisionFormat {
+    className = 'WebGLShaderPrecisionFormat';
+
+    constructor({
+        rangeMin, rangeMax, precision
+    }) {
+        this.rangeMin = rangeMin;
+        this.rangeMax = rangeMax;
+        this.precision = precision;
+    }
+}

+ 22 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/Texture.js

@@ -0,0 +1,22 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLTexture';
+
+function uuid(id) {
+    return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLTexture {
+    className = name;
+
+    constructor(id, type) {
+        this.id = id;
+        this.type = type;
+    }
+
+    static uuid = uuid;
+
+    uuid() {
+        return uuid(this.id);
+    }
+}

+ 22 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/UniformLocation.js

@@ -0,0 +1,22 @@
+import {getTransferedObjectUUID} from './classUtils';
+
+const name = 'WebGLUniformLocation';
+
+function uuid(id) {
+    return getTransferedObjectUUID(name, id);
+}
+
+export default class WebGLUniformLocation {
+    className = name;
+
+    constructor(id, type) {
+        this.id = id;
+        this.type = type;
+    }
+
+    static uuid = uuid;
+
+    uuid() {
+        return uuid(this.id);
+    }
+}

+ 3 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/context-webgl/classUtils.js

@@ -0,0 +1,3 @@
+export function getTransferedObjectUUID(name, id) {
+    return `${name.toLowerCase()}-${id}`;
+}

+ 74 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/env/canvas.js

@@ -0,0 +1,74 @@
+import GContext2D from '../context-2d/RenderingContext';
+import GContextWebGL from '../context-webgl/RenderingContext';
+
+export default class GCanvas {
+
+    // static GBridge = null;
+
+    id = null;
+
+    _needRender = true;
+
+    constructor(id, { disableAutoSwap }) {
+        this.id = id;
+
+        this._disableAutoSwap = disableAutoSwap;
+        if (disableAutoSwap) {
+            this._swapBuffers = () => {
+                GCanvas.GBridge.render(this.id);
+            }
+        }
+    }
+
+    getContext(type) {
+
+        let context = null;
+
+        if (type.match(/webgl/i)) {
+            context = new GContextWebGL(this);
+
+            context.componentId = this.id;
+
+            if (!this._disableAutoSwap) {
+                const render = () => {
+                    if (this._needRender) {
+                        GCanvas.GBridge.render(this.id);
+                        this._needRender = false;
+                    }
+                }
+                setInterval(render, 16);
+            }
+
+            GCanvas.GBridge.callSetContextType(this.id, 1); // 0 for 2d; 1 for webgl
+        } else if (type.match(/2d/i)) {
+            context = new GContext2D(this);
+
+            context.componentId = this.id;
+
+//             const render = ( callback ) => {
+// 
+//                 const commands = context._drawCommands;
+//                 context._drawCommands = '';
+// 
+//                 GCanvas.GBridge.render2d(this.id, commands, callback);
+//                 this._needRender = false;
+//             }
+// 			//draw方法触发
+// 			context._flush = render;
+//             //setInterval(render, 16);
+
+            GCanvas.GBridge.callSetContextType(this.id, 0);
+        } else {
+            throw new Error('not supported context ' + type);
+        }
+
+        return context;
+
+    }
+
+    reset() {
+        GCanvas.GBridge.callReset(this.id);
+    }
+
+
+}

+ 96 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/env/image.js

@@ -0,0 +1,96 @@
+let incId = 1;
+
+const noop = function () { };
+
+class GImage {
+
+    static GBridge = null;
+
+    constructor() {
+        this._id = incId++;
+        this._width = 0;
+        this._height = 0;
+        this._src = undefined;
+        this._onload = noop;
+        this._onerror = noop;
+        this.complete = false;
+    }
+
+    get width() {
+        return this._width;
+    }
+    set width(v) {
+        this._width = v;
+    }
+
+    get height() {
+        return this._height;
+    }
+
+    set height(v) {
+        this._height = v;
+    }
+
+    get src() {
+        return this._src;
+    }
+
+    set src(v) {
+
+        if (v.startsWith('//')) {
+            v = 'http:' + v;
+        }
+
+        this._src = v;
+
+        GImage.GBridge.perloadImage([this._src, this._id], (data) => {
+            if (typeof data === 'string') {
+                data = JSON.parse(data);
+            }
+            if (data.error) {
+                var evt = { type: 'error', target: this };
+                this.onerror(evt);
+            } else {
+                this.complete = true;
+                this.width = typeof data.width === 'number' ? data.width : 0;
+                this.height = typeof data.height === 'number' ? data.height : 0;
+                var evt = { type: 'load', target: this };
+                this.onload(evt);
+            }
+        });
+    }
+
+    addEventListener(name, listener) {
+        if (name === 'load') {
+            this.onload = listener;
+        } else if (name === 'error') {
+            this.onerror = listener;
+        }
+    }
+
+    removeEventListener(name, listener) {
+        if (name === 'load') {
+            this.onload = noop;
+        } else if (name === 'error') {
+            this.onerror = noop;
+        }
+    }
+
+    get onload() {
+        return this._onload;
+    }
+
+    set onload(v) {
+        this._onload = v;
+    }
+
+    get onerror() {
+        return this._onerror;
+    }
+
+    set onerror(v) {
+        this._onerror = v;
+    }
+}
+
+export default GImage;

+ 24 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/env/tool.js

@@ -0,0 +1,24 @@
+
+export function ArrayBufferToBase64 (buffer) {
+    var binary = '';
+    var bytes = new Uint8ClampedArray(buffer);
+    for (var len = bytes.byteLength, i = 0; i < len; i++) {
+        binary += String.fromCharCode(bytes[i]);
+    }
+    return btoa(binary);
+}
+	
+export function Base64ToUint8ClampedArray(base64String) {
+	const padding = '='.repeat((4 - base64String.length % 4) % 4);
+	const base64 = (base64String + padding)
+		.replace(/\-/g, '+')
+		.replace(/_/g, '/');
+
+	const rawData = atob(base64);
+	const outputArray = new Uint8ClampedArray(rawData.length);
+
+	for (let i = 0; i < rawData.length; ++i) {
+		outputArray[i] = rawData.charCodeAt(i);
+	}
+	return outputArray;
+}

+ 39 - 0
uni_modules/Sansnn-uQRCode/js_sdk/gcanvas/index.js

@@ -0,0 +1,39 @@
+import GCanvas from './env/canvas';
+import GImage from './env/image';
+
+import GWebGLRenderingContext from './context-webgl/RenderingContext';
+import GContext2D from './context-2d/RenderingContext';
+
+import GBridgeWeex from './bridge/bridge-weex';
+
+export let Image = GImage;
+
+export let WeexBridge = GBridgeWeex;
+
+export function enable(el, { bridge, debug, disableAutoSwap, disableComboCommands } = {}) {
+
+    const GBridge = GImage.GBridge = GCanvas.GBridge = GWebGLRenderingContext.GBridge = GContext2D.GBridge = bridge;
+
+    GBridge.callEnable(el.ref, [
+        0,      // renderMode: 0--RENDERMODE_WHEN_DIRTY, 1--RENDERMODE_CONTINUOUSLY
+        -1,     // hybridLayerType:  0--LAYER_TYPE_NONE 1--LAYER_TYPE_SOFTWARE 2--LAYER_TYPE_HARDWARE
+        false,  // supportScroll
+        false,  // newCanvasMode
+        1,      // compatible
+        'white',// clearColor
+        false   // sameLevel: newCanvasMode = true && true => GCanvasView and Webview is same level
+    ]);
+
+    if (debug === true) {
+        GBridge.callEnableDebug();
+    }
+    if (disableComboCommands) {
+        GBridge.callEnableDisableCombo();
+    }
+
+    var canvas = new GCanvas(el.ref, { disableAutoSwap });
+    canvas.width = el.style.width;
+    canvas.height = el.style.height;
+
+    return canvas;
+};

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 33 - 0
uni_modules/Sansnn-uQRCode/js_sdk/uqrcode/uqrcode.js


+ 80 - 0
uni_modules/Sansnn-uQRCode/package.json

@@ -0,0 +1,80 @@
+{
+  "id": "Sansnn-uQRCode",
+  "displayName": "uQRCode 全端二维码生成插件 支持nvue 支持nodejs服务端",
+  "version": "4.0.6",
+  "description": "uQRCode是一款基于Javascript环境开发的二维码生成插件,适用所有Javascript运行环境的前端应用和Node.js。",
+  "keywords": [
+    "二维码",
+    "uQRCode",
+    "qrcode",
+    "qr"
+],
+  "repository": "https://github.com/Sansnn/uQRCode",
+  "engines": {
+    "HBuilderX": "^3.1.0"
+  },
+"dcloudext": {
+    "sale": {
+      "regular": {
+        "price": "0.00"
+      },
+      "sourcecode": {
+        "price": "0.00"
+      }
+    },
+    "contact": {
+      "qq": ""
+    },
+    "declaration": {
+      "ads": "无",
+      "data": "无",
+      "permissions": "无"
+    },
+    "npmurl": "https://www.npmjs.com/package/uqrcodejs",
+    "type": "sdk-js"
+  },
+  "uni_modules": {
+    "dependencies": [],
+    "encrypt": [],
+    "platforms": {
+      "cloud": {
+        "tcb": "y",
+        "aliyun": "y"
+      },
+      "client": {
+        "App": {
+          "app-vue": "y",
+          "app-nvue": "y"
+        },
+        "H5-mobile": {
+          "Safari": "y",
+          "Android Browser": "y",
+          "微信浏览器(Android)": "y",
+          "QQ浏览器(Android)": "y"
+        },
+        "H5-pc": {
+          "Chrome": "y",
+          "IE": "y",
+          "Edge": "y",
+          "Firefox": "y",
+          "Safari": "y"
+        },
+        "小程序": {
+          "微信": "y",
+          "阿里": "y",
+          "百度": "y",
+          "字节跳动": "y",
+          "QQ": "y"
+        },
+        "快应用": {
+          "华为": "y",
+          "联盟": "y"
+        },
+        "Vue": {
+          "vue2": "y",
+          "vue3": "y"
+        }
+      }
+    }
+  }
+}

+ 114 - 0
uni_modules/lsj-upload/changelog.md

@@ -0,0 +1,114 @@
+## 2.2.9(2023-06-01)
+优化:将是否多选与count字段解绑(原逻辑是count>1为允许多选),改为新增multiple属性控制是否多选。
+## 2.2.8(2023-06-01)
+修复上版本提交时accept测试值未删除导致h5端只能选择图片的问题。
+## 2.2.7(2023-05-06)
+应群友建议,当instantly为true时,触发change事件后延迟1000毫秒再自动上传,方便动态修改参数,其实个人还是建议想在change事件动态设置参数的伙伴将instantly设置为false,修改参数后手动调用upload()
+## 2.2.6(2023-02-09)
+修复多个文件同时选择时返回多次change回调的问题
+## 2.2.5(2022-12-27)
+1.修复多选文件时未能正常校验数量的问题;
+2.app端与H5端支持单选或多选文件,通过count数量控制,超过1开启多选。
+## 2.2.4(2022-12-27)
+1.修复多选文件时未能正常校验数量的问题;
+2.app端修复多选只取到第一个文件的问题。
+## 2.2.3(2022-12-06)
+修复手动调用show()导致count失效的问题
+## 2.2.2(2022-12-01)
+Vue3自行修改兼容
+## 2.2.1(2022-10-19)
+修复childId警告提示
+## 2.2.0(2022-10-10)
+更新app端webview窗口参数clidId,默认值添加时间戳保证唯一性
+## 2.1.9(2022-07-13)
+[修复] app端选择文件后初始化设置的文件列表被清空问题
+## 2.1.8(2022-07-13)
+[新增] ref方法初始化文件列表,用于已提交后再次编辑时需带入已上传文件:setFiles(files),可传入数组或Map对象,传入格式请与组件选择返回格式保持一致,且name为必须属性。
+## 2.1.7(2022-07-12)
+修复ios端偶现创建webview初始化参数未生效的问题
+## 2.1.6(2022-07-11)
+[修复]:修复上个版本更新导致nvue窗口组件不能选择文件的问题;
+[新增]:
+1.应群友建议(填写禁止格式太多)格式限制formats由原来填写禁止选择的格式改为填写允许被选择的格式;
+2.应群友建议(增加上传结束回调事件),上传结束回调事件@uploadEnd
+3.如能帮到你请留下你的免费好评,组件使用过程中有问题可以加QQ群交流,至于Map对象怎么使用这类前端基础问题请自行百度
+## 2.1.5(2022-07-01)
+app端组件销毁时添加自动销毁webview功能,避免v-if销毁组件的情况控件还能被点击的问题
+## 2.1.4(2022-07-01)
+修复小程序端回显问题
+## 2.1.3(2022-06-30)
+回调事件返回参数新增path字段(文件临时地址),用于回显
+## 2.1.2(2022-06-16)
+修复APP端Tabbar窗口无法选择文件的问题
+## 2.1.1(2022-06-16)
+优化:
+1.组件优化为允许在v-if中使用;
+2.允许option直接在data赋值,不再强制在onRead中初始化;
+## 2.1.0(2022-06-13)
+h5 pc端更改为单次可多选
+## 2.0.9(2022-06-10)
+更新演示内容,部分同学不知道怎么获取服务端返回的数据
+## 2.0.8(2022-06-09)
+优化动态更新上传参数函数,具体查看下方说明:动态更新参数演示
+## 2.0.7(2022-06-07)
+新增wxFileType属性,用于小程序端选择附件时可选文件类型
+## 2.0.6(2022-06-07)
+修复小程序端真机选择文件提示失败的问题
+## 2.0.5(2022-06-02)
+优化小程序端调用hide()后未阻止触发文件选择问题
+## 2.0.4(2022-06-01)
+优化APP端选择器初始定位
+## 2.0.3(2022-05-31)
+修复nvue窗口选择文件报错问题 
+## 2.0.2(2022-05-20)
+修复ios端opiton设置过早未传入webview导致不自动上传问题
+## 2.0.1(2022-05-19)
+修复APP端子窗口点击选择文件不响应问题
+## 2.0.0(2022-05-18)
+此次组件更新至2.0版本,与1.0版本使用上略有差异,已使用1.0的同学请自行斟酌是否需要升级!
+部分差异:
+一、 2.0新增异步触发上传功能;
+二、2.0新增文件批量上传功能;
+三、2.0优化option,剔除属性,只保留上传接口所需字段,且允许异步更改option的值;
+四、组件增加size(文件大小限制)、count(文件个数限制)、formats(文件后缀限制)、accept(文件类型限制)、instantly(是否立即自动上传)、debug(日志打印)等属性;
+五、回调事件取消input事件、callback事件,新增change事件和progress事件;
+六、ref事件新增upload事件、clear事件;
+七、优化组件代码,show和hide函数改为显示隐藏,不再重复开关webview;
+
+## 1.2.3(2022-03-22)
+修复Demo里传入待完善功能[手动上传属性manual=true]导致不自动上传的问题,手动提交上传待下个版本更新
+## 1.2.2(2022-02-21)
+修复上版本APP优化导致H5和小程序端不自动初始化的问题,此次更新仅修复此问题。异步提交功能下个版本更新~
+## 1.2.1(2022-01-25)
+QQ1群已满,已开放2群:469580165
+## 1.2.0(2021-12-09)
+优化APP端页面中DOM重排后每次需要重新定位的问题
+## 1.1.1(2021-12-09)
+优化,与上版本使用方式有改变,请检查后确认是否需要更新,create更名为show,  close更名为hide,取消初始化时手动create, 传参方式改为props=>option
+## 1.1.0(2021-12-09)
+新增refresh方法,用于DOM发生重排时重新定位控件(APP端)
+## 1.0.9(2021-07-15)
+修复上传进度未同步渲染,直接返回100%的BUG
+## 1.0.8(2021-07-12)
+修复H5端传入height和width未生效的bug
+## 1.0.7(2021-07-07)
+修复h5和小程序端上传完成callback未返回fileName字段问题
+## 1.0.6(2021-07-07)
+修复h5端提示信息debug
+## 1.0.5(2021-06-29)
+感谢小伙伴找出bug,上传成功回调success未置为true,已修复
+## 1.0.4(2021-06-28)
+新增兼容APP,H5,小程序手动关闭控件,关闭后不再弹出文件选择框,需要重新create再次开启
+## 1.0.3(2021-06-28)
+close增加条件编译,除app端外不需要close
+## 1.0.2(2021-06-28)
+1.修复页面滚动位置后再create控件导致控件位置不正确的问题;
+2.修复nvue无法create控件;
+3.示例项目新增nvue使用案例;
+## 1.0.1(2021-06-28)
+因为有的朋友不清楚app端切换tab时应该怎么处理webview,现重新上传一版示例项目,需要做tab切换的朋友可以导入示例项目查看
+## 1.0.0(2021-06-25)
+此插件为l-file插件中上传功能改版,更新内容为:
+1. 按钮内嵌入页面,不再强制固定底部,可跟随页面滚动
+2.无需再单独弹框点击上传,减去中间层
+3.通过slot自定义按钮样式

+ 403 - 0
uni_modules/lsj-upload/components/lsj-upload/LsjFile.js

@@ -0,0 +1,403 @@
+export class LsjFile {
+	constructor(data) {
+		this.dom = null;
+		// files.type = waiting(等待上传)|| loading(上传中)|| success(成功) || fail(失败)
+		this.files = new Map();
+		this.debug = data.debug || false;
+		this.id = data.id;
+		this.width = data.width;
+		this.height = data.height;
+		this.option = data.option;
+		this.instantly = data.instantly;
+		this.prohibited = data.prohibited;
+		this.onchange = data.onchange;
+		this.onprogress = data.onprogress;
+		this.uploadHandle = this._uploadHandle;
+		// #ifdef MP-WEIXIN
+		this.uploadHandle = this._uploadHandleWX;
+		// #endif
+	}
+	
+	
+	/**
+	 * 创建File节点
+	 * @param {string}path webview地址
+	 */
+	create(path) {
+		if (!this.dom) {
+			// #ifdef H5
+				let dom = document.createElement('input');
+				dom.type = 'file'
+				dom.value = ''
+				dom.style.height = this.height
+				dom.style.width = this.width
+				dom.style.position = 'absolute'
+				dom.style.top = 0
+				dom.style.left = 0
+				dom.style.right = 0
+				dom.style.bottom = 0
+				dom.style.opacity = 0
+				dom.style.zIndex = 999
+				dom.accept = this.prohibited.accept;
+				if (this.prohibited.multiple) {
+				dom.multiple = 'multiple';
+				}
+				dom.onchange = event => {
+					for (let file of event.target.files) {
+						if (this.files.size >= this.prohibited.count) {
+							this.toast(`只允许上传${this.prohibited.count}个文件`);
+							this.dom.value = '';
+							break;
+						}
+						this.addFile(file);
+					}
+					
+					this._uploadAfter();
+					
+					this.dom.value = '';
+				};
+				this.dom = dom;
+			// #endif
+		
+			// #ifdef APP-PLUS
+				let styles = {
+					top: '-200px',
+					left: 0,
+					width: '1px',
+					height: '200px',
+					background: 'transparent' 
+				};
+				let extras = {
+					debug: this.debug,
+					instantly: this.instantly,
+					prohibited: this.prohibited,
+				}
+				this.dom = plus.webview.create(path, this.id, styles,extras);
+				this.setData(this.option); 
+				this._overrideUrlLoading();
+			// #endif
+			return this.dom;
+		}
+	}
+	
+	
+	/**
+	 * 设置上传参数
+	 * @param {object|string}name 上传参数,支持a.b 和 a[b]
+	 */
+	setData() {
+		let [name,value = ''] = arguments;
+		if (typeof name === 'object') {
+			Object.assign(this.option,name);
+		}
+		else {
+			this._setValue(this.option,name,value);
+		}
+		
+		this.debug&&console.log(JSON.stringify(this.option));
+		
+		// #ifdef APP-PLUS
+			this.dom.evalJS(`vm.setData('${JSON.stringify(this.option)}')`);
+		// #endif
+	}
+	
+	/**
+	 * 上传
+	 * @param {string}name 文件名称
+	 */
+	async upload(name='') {
+		if (!this.option.url) {
+			throw Error('未设置上传地址');
+		}
+		
+		// #ifndef APP-PLUS
+			if (name && this.files.has(name)) {
+				await this.uploadHandle(this.files.get(name));
+			}
+			else {
+				for (let item of this.files.values()) {
+					if (item.type === 'waiting' || item.type === 'fail') {
+						await this.uploadHandle(item);
+					}
+				}
+			}
+		// #endif
+		
+		// #ifdef APP-PLUS
+			this.dom&&this.dom.evalJS(`vm.upload('${name}')`);
+		// #endif
+	}
+	
+	// 选择文件change
+	addFile(file,isCallChange) {
+		
+		let name = file.name;
+		this.debug&&console.log('文件名称',name,'大小',file.size);
+		
+		if (file) {
+			// 限制文件格式
+			let path = '';
+			let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase();
+			let formats = this.prohibited.formats.toLowerCase();
+			
+			
+			// #ifndef MP-WEIXIN
+				path = URL.createObjectURL(file);
+			// #endif
+			// #ifdef MP-WEIXIN
+				path = file.path;
+			// #endif
+			if (formats&&!formats.includes(suffix)) {
+				this.toast(`不支持上传${suffix.toUpperCase()}格式文件`);
+				return false;
+			}
+			// 限制文件大小
+			if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
+				this.toast(`附件大小请勿超过${this.prohibited.size}M`)
+				return false;
+			}
+			this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'});
+			return true;
+		}
+	}
+	
+	/**
+	 * 移除文件
+	 * @param {string}name 不传name默认移除所有文件,传入name移除指定name的文件
+	 */
+	clear(name='') {
+		// #ifdef APP-PLUS
+		this.dom&&this.dom.evalJS(`vm.clear('${name}')`);
+		// #endif
+		
+		if (!name) {
+			this.files.clear();
+		}
+		else {
+			this.files.delete(name); 
+		}
+		return this.onchange(this.files);
+	}
+	
+	/**
+	 * 提示框
+	 * @param {string}msg 轻提示内容
+	 */
+	toast(msg) {
+		uni.showToast({
+			title: msg,
+			icon: 'none'
+		});
+	}
+	
+	/**
+	 * 微信小程序选择文件
+	 * @param {number}count 可选择文件数量
+	 */
+	chooseMessageFile(type,count,extension) {
+		wx.chooseMessageFile({
+			count: count,
+			type: type,
+			extension: extension,
+			success: ({ tempFiles }) => {
+				for (let file of tempFiles) {
+					this.addFile(file);
+				}
+				this._uploadAfter();
+			},
+			fail: (err) => {
+				console.log(`打开失败`, err)
+				this.toast(`打开失败`);
+			}
+		})
+	}
+	
+	_copyObject(obj) {
+		if (typeof obj !== "undefined") {
+			return JSON.parse(JSON.stringify(obj));
+		} else {
+			return obj;
+		}
+	}
+	
+	/**
+	 * 自动根据字符串路径设置对象中的值 支持.和[]
+	 * @param	{Object} dataObj 数据源
+	 * @param	{String} name 支持a.b 和 a[b]
+	 * @param	{String} value 值
+	 * setValue(dataObj, name, value);
+	 */
+	_setValue(dataObj, name, value) {
+		// 通过正则表达式  查找路径数据
+		let dataValue;
+		if (typeof value === "object") {
+			dataValue = this._copyObject(value);
+		} else {
+			dataValue = value;
+		}
+		let regExp = new RegExp("([\\w$]+)|\\[(:\\d)\\]", "g");
+		const patten = name.match(regExp);
+		// 遍历路径  逐级查找  最后一级用于直接赋值
+		for (let i = 0; i < patten.length - 1; i++) {
+			let keyName = patten[i];
+			if (typeof dataObj[keyName] !== "object") dataObj[keyName] = {};
+			dataObj = dataObj[keyName];
+		}
+		// 最后一级
+		dataObj[patten[patten.length - 1]] = dataValue;
+		this.debug&&console.log('参数更新后',JSON.stringify(this.option));
+	}
+	
+	_uploadAfter() {
+		this.onchange(this.files);
+		setTimeout(()=>{
+			this.instantly&&this.upload();
+		},1000)
+	}
+	
+	_overrideUrlLoading() {
+		this.dom.overrideUrlLoading({ mode: 'reject' }, e => {
+			let {retype,item,files,end} = this._getRequest(
+				e.url
+			);
+			let _this = this;
+			switch (retype) {
+				case 'updateOption':
+					this.dom.evalJS(`vm.setData('${JSON.stringify(_this.option)}')`);
+					break
+				case 'change':
+					try {
+						_this.files = new Map([..._this.files,...JSON.parse(unescape(files))]);
+					} catch (e) {
+						return console.error('出错了,请检查代码')
+					}
+					_this.onchange(_this.files);
+					break
+				case 'progress':
+					try {
+						item = JSON.parse(unescape(item));
+					} catch (e) {
+						return console.error('出错了,请检查代码')
+					}
+					_this._changeFilesItem(item,end);
+					break
+				default:
+					break
+			}
+		})
+	}
+	
+	_getRequest(url) {
+		let theRequest = new Object()
+		let index = url.indexOf('?')
+		if (index != -1) {
+			let str = url.substring(index + 1)
+			let strs = str.split('&')
+			for (let i = 0; i < strs.length; i++) {
+				theRequest[strs[i].split('=')[0]] = unescape(strs[i].split('=')[1])
+			}
+		}
+		return theRequest
+	}
+	
+	_changeFilesItem(item,end=false) {
+		this.debug&&console.log('onprogress',JSON.stringify(item));
+		this.onprogress(item,end);
+		this.files.set(item.name,item);
+	}
+	
+	_uploadHandle(item) {
+		item.type = 'loading';
+		delete item.responseText;
+		return new Promise((resolve,reject)=>{
+			this.debug&&console.log('option',JSON.stringify(this.option));
+			let {url,name,method='POST',header,formData} = this.option;
+			formData.filename = item.name
+			let form = new FormData();
+			for (let keys in formData) {
+				form.append(keys, formData[keys])
+			}
+			form.append(name, item.file);
+			let xmlRequest = new XMLHttpRequest();
+			xmlRequest.open(method, url, true);
+			for (let keys in header) {
+				xmlRequest.setRequestHeader(keys, header[keys])
+			}
+			
+			xmlRequest.upload.addEventListener(
+				'progress',
+				event => {
+					if (event.lengthComputable) {
+						let progress = Math.ceil((event.loaded * 100) / event.total)
+						if (progress <= 100) {
+							item.progress = progress;
+							this._changeFilesItem(item);
+						}
+					}
+				},
+				false
+			);
+			
+			xmlRequest.ontimeout = () => {
+				console.error('请求超时')
+				item.type = 'fail';
+				this._changeFilesItem(item,true);
+				return resolve(false);
+			}
+			
+			xmlRequest.onreadystatechange = ev => {
+				if (xmlRequest.readyState == 4) {
+					if (xmlRequest.status == 200) {
+						this.debug&&console.log('上传完成:' + xmlRequest.responseText)
+						item['responseText'] = xmlRequest.responseText;
+						item.type = 'success';
+						this._changeFilesItem(item,true);
+						return resolve(true);
+					} else if (xmlRequest.status == 0) {
+						console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配,服务端已正确开启跨域,并且nginx未拦截阻止请求')
+					}
+					console.error('--ERROR--:status = ' + xmlRequest.status)
+					item.type = 'fail';
+					this._changeFilesItem(item,true);
+					return resolve(false);
+				}
+			}
+			xmlRequest.send(form)
+		});
+	}
+	
+	_uploadHandleWX(item) {
+		item.type = 'loading';
+		delete item.responseText;
+		return new Promise((resolve,reject)=>{
+			this.debug&&console.log('option',JSON.stringify(this.option));
+			let form = {filePath: item.file.path,...this.option };
+			form['fail'] = ({ errMsg = '' }) => {
+				console.error('--ERROR--:' + errMsg)
+				item.type = 'fail';
+				this._changeFilesItem(item,true);
+				return resolve(false);
+			}
+			form['success'] = res => {
+				if (res.statusCode == 200) {
+					this.debug&&console.log('上传完成,微信端返回不一定是字符串,根据接口返回格式判断是否需要JSON.parse:' + res.data)
+					item['responseText'] = res.data;
+					item.type = 'success';
+					this._changeFilesItem(item,true);
+					return resolve(true);
+				}
+				item.type = 'fail';
+				this._changeFilesItem(item,true);
+				return resolve(false);
+			}
+			form.formData.filename = item.name
+			let xmlRequest = uni.uploadFile(form);
+			xmlRequest.onProgressUpdate(({ progress = 0 }) => {
+				if (progress <= 100) {
+					item.progress = progress;
+					this._changeFilesItem(item);
+				}
+			})
+		});
+	}
+}

+ 323 - 0
uni_modules/lsj-upload/components/lsj-upload/lsj-upload.vue

@@ -0,0 +1,323 @@
+<template>
+	<view class="lsj-file" :style="[getStyles]">
+		<view ref="lsj" class="hFile" :style="[getStyles]" @click="onClick">
+			<slot><view class="defview" :style="[getStyles]">附件上传</view></slot>
+		</view>
+	</view>
+</template>
+
+<script>
+// 查看文档:https://ext.dcloud.net.cn/plugin?id=5459
+import {LsjFile} from './LsjFile.js' 
+export default {
+	name: 'Lsj-upload',
+	props: {
+		// 打印日志
+		debug: {type: Boolean,default: false},
+		// 自动上传
+		instantly: {type: Boolean,default: false},
+		// 上传接口参数设置
+		option: {type: Object,default: ()=>{}},
+		// 文件大小上限
+		size: { type: Number, default: 10 },
+		// 文件选择个数上限,超出后不触发点击
+		count: { type: Number, default: 9 },
+		// 是否允许多选文件
+		multiple: {type:Boolean, default: true},
+		// 允许上传的文件格式(多个以逗号隔开)
+		formats: { type: String, default:''},
+		// input file选择限制
+		accept: {type: String,default: ''},
+		// 微信选择文件类型 
+		//all=从所有文件选择,
+		//video=只能选择视频文件,
+		//image=只能选择图片文件,
+		//file=可以选择除了图片和视频之外的其它的文件
+		wxFileType: { type: String, default: 'all' },
+		extension: {type: Array,default: ()=>['*']},
+		// webviewID需唯一,不同窗口也不要同Id
+		childId: { type: String, default: 'lsjUpload'  },
+		// 文件选择触发面宽度
+		width: { type: String, default: '100%' },
+		// 文件选择触发面高度
+		height: { type: String, default: '80rpx' },
+		
+		// top,left,bottom,right仅position=absolute时才需要传入
+		top: { type: [String, Number], default: '' },
+		left: { type: [String, Number], default: '' },
+		bottom: { type: [String, Number], default: '' },
+		right: { type: [String, Number], default: '' },
+		// nvue不支持跟随窗口滚动
+		position: { 
+			type: String,
+			// #ifdef APP-NVUE
+			 default: 'absolute',
+			// #endif
+			// #ifndef APP-NVUE
+			default: 'static',
+			// #endif
+		},
+	},
+	data() {
+		return {
+			
+		}
+	},
+	watch: {
+		option(v) {
+			// #ifdef APP-PLUS
+			this.lsjFile&&this.show();
+			// #endif
+		}
+	},
+	updated() {
+		// #ifdef APP-PLUS
+			if (this.isShow) {
+				this.lsjFile&&this.show();
+			}
+		// #endif
+	},
+	computed: {
+		getStyles() {
+			let styles = {
+				width: this.width,
+				height: this.height
+			}
+			if (this.position == 'absolute') {
+				styles['top'] = this.top
+				styles['bottom'] = this.bottom
+				styles['left'] = this.left
+				styles['right'] = this.right
+				styles['position'] = 'fixed'
+			}
+
+			return styles
+		}
+	},
+	mounted() {
+		this._size = 0;
+		let WEBID = this.childId + new Date().getTime();
+		this.lsjFile = new LsjFile({
+			id: WEBID,
+			debug: this.debug,
+			width: this.width,
+			height: this.height,
+			option: this.option,
+			instantly: this.instantly,
+			// 限制条件
+			prohibited: {
+				// 大小
+				size: this.size,
+				// 允许上传的格式
+				formats: this.formats,
+				// 限制选择的格式
+				accept: this.accept,
+				count: this.count,
+				// 是否多选
+				multiple: this.multiple,
+			},
+			onchange: this.onchange,
+			onprogress: this.onprogress,
+		});
+		this.create();
+		// 需判断是否当前页显示
+		uni.$on('lsjShow',this.show);
+	},
+	beforeDestroy() {
+		uni.$off('lsjShow',this.show);
+		
+		// #ifdef APP-PLUS
+		this.lsjFile.dom.close();
+		// #endif
+	},
+	methods: {
+		setFiles(array) {
+			if (array instanceof Map) {
+				for (let [key, item] of array) {
+					item['progress'] = 100;
+					item['type'] = 'success';
+					this.lsjFile.files.set(key,item);
+				}
+			}
+			else if (Array.isArray(array)) {
+				array.forEach(item=>{
+					if (item.name) { 
+						item['progress'] = 100;
+						item['type'] = 'success';
+						this.lsjFile.files.set(item.name,item);
+					}
+				});
+			}
+			this.onchange(this.lsjFile.files);
+		},
+		setData() {
+			this.lsjFile&&this.lsjFile.setData(...arguments);
+		},
+		getDomStyles(callback) {
+			// #ifndef APP-NVUE
+			let view = uni
+				.createSelectorQuery()
+				.in(this)
+				.select('.lsj-file')
+			view.fields(
+				{
+					size: true,
+					rect: true
+				},
+				({ height, width, top, left, right, bottom }) => {
+					uni.createSelectorQuery()
+					.selectViewport()
+					.scrollOffset(({ scrollTop }) => {
+						return callback({
+							top: parseInt(top) + parseInt(scrollTop) + 'px',
+							left: parseInt(left) + 'px',
+							width: parseInt(width) + 'px',
+							height: parseInt(height) + 'px'
+						})
+					})
+					.exec()
+				}
+			).exec()
+			// #endif
+			// #ifdef APP-NVUE
+			const dom = weex.requireModule('dom')
+			dom.getComponentRect(this.$refs.lsj, ({ size: { height, width, top, left, right, bottom } }) => {
+				return callback({
+					top: parseInt(top) + 'px',
+					left: parseInt(left) + 'px',
+					width: parseInt(width) + 'px',
+					height: parseInt(height) + 'px',
+					right: parseInt(right) + 'px',
+					bottom: parseInt(bottom) + 'px'
+				})
+			})
+			// #endif
+		},
+		show() {
+			if (this._size && (this._size >= this.count)) {
+				return;
+			}
+			this.isShow = true;
+			// #ifdef APP-PLUS
+			this.lsjFile&&this.getDomStyles(styles => {
+				this.lsjFile.dom.setStyle(styles)
+			});
+			// #endif
+			// #ifdef H5
+			this.lsjFile.dom.style.display = 'inline'
+			// #endif
+		},
+		hide() {
+			this.isShow = false;
+			// #ifdef APP-PLUS
+			this.lsjFile&&this.lsjFile.dom.setStyle({
+				top: '-100px',
+				left:'0px',
+				width: '1px',
+				height: '100px',
+			});
+			// #endif
+			// #ifdef H5
+			this.lsjFile.dom.style.display = 'none'
+			// #endif
+		},
+		/**
+		 * 手动提交上传
+		 * @param {string}name 文件名称,不传则上传所有type等于waiting和fail的文件
+		 */
+		upload(name) {
+			this.lsjFile&&this.lsjFile.upload(name);
+		},
+		/**
+		 * @returns {Map} 已选择的文件Map集
+		 */
+		onchange(files) {
+			this.$emit('change',files);
+			this._size = files.size;
+			return files.size >= this.count ? this.hide() : this.show();
+		},
+		/**
+		 * @returns {object} 当前上传中的对象
+		 */
+		onprogress(item,end=false) {
+			this.$emit('progress',item);
+			if (end) {
+				setTimeout(()=>{
+					this.$emit('uploadEnd',item);
+				},0);
+			}
+		},
+		/**
+		 * 移除组件内缓存的某条数据
+		 * @param {string}name 文件名称,不指定默认清除所有文件
+		 */
+		clear(name) {
+			this.lsjFile.clear(name);
+		},
+		// 创建选择器
+		create() {
+			// 若iOS端服务端处理不了跨域就将hybrid目录内的html放到服务端去,并将此处path改成服务器上的地址
+			let path = '/uni_modules/lsj-upload/hybrid/html/uploadFile.html';
+			let dom = this.lsjFile.create(path);
+			// #ifdef H5
+			this.$refs.lsj.$el.appendChild(dom);
+			// #endif
+			// #ifndef APP-PLUS
+			this.show();
+			// #endif
+			// #ifdef APP-PLUS
+			dom.setStyle({position: this.position});
+			dom.loadURL(path);
+			setTimeout(()=>{
+				// #ifdef APP-NVUE
+				plus.webview.currentWebview().append(dom);
+				// #endif
+				// #ifndef APP-NVUE
+				this.$root.$scope.$getAppWebview().append(dom);
+				// #endif
+				this.show();
+			},300)
+			// #endif
+		},
+		// 点击选择附件
+		onClick() {
+			console.log(111)
+			if (this._size >= this.count) {
+				this.toast(`只允许上传${this.count}个文件`);
+				return;
+			}
+			
+			// #ifdef MP-WEIXIN
+			if (!this.isShow) {return;}
+			let count = this.count - this._size;
+			this.lsjFile.chooseMessageFile(this.wxFileType,count,this.extension);
+			// #endif
+		},
+		toast(msg) {
+			uni.showToast({
+				title: msg,
+				icon: 'none'
+			});
+		}
+	}
+}
+</script>
+
+<style scoped>
+.lsj-file {
+	/* display: inline-block; */
+}
+.defview {
+	background-color: #007aff;
+	color: #fff;
+	border-radius: 10rpx;
+	display: flex;
+	align-items: center;
+	justify-content: center;
+	font-size: 28rpx;
+}
+.hFile {
+	position: relative;
+	overflow: hidden;
+}
+</style>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 5 - 0
uni_modules/lsj-upload/hybrid/html/js/vue.min.js


+ 199 - 0
uni_modules/lsj-upload/hybrid/html/uploadFile.html

@@ -0,0 +1,199 @@
+<!DOCTYPE html>
+<html lang="zh-cn">
+
+	<head>
+		<meta charset="UTF-8">
+		<title class="title">[文件管理器]</title>
+		<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
+		<style type="text/css">
+			.content {background: transparent;}
+			.btn {position: relative;top: 0;left: 0;bottom: 0;right: 0;}
+			.btn .file {position: fixed;z-index: 9;left: 0;right: 0;top: 0;bottom: 0;width: 100%;opacity: 0;}
+		</style>
+	</head>
+
+	<body>
+		
+		<div id="content" class="content">
+			<div class="btn">
+				<input :multiple="multiple" @change="onChange" :accept="accept" ref="file" class="file" type="file" />
+			</div>
+		</div>
+		
+		<script type="text/javascript" src="js/vue.min.js"></script>
+		<script type="text/javascript">
+			let _this;
+			var vm = new Vue({
+				el: '#content',
+				data: {
+					accept: '',
+					multiple: true,
+				},
+				mounted() {
+					console.log('加载webview');
+					_this = this;
+					this.files = new Map();
+					document.addEventListener('plusready', (e)=>{
+					let {debug,instantly,prohibited} = plus.webview.currentWebview();
+					this.debug = debug;
+					this.instantly = instantly;
+					this.prohibited = prohibited;
+					this.accept = prohibited.accept; 
+					if (prohibited.multiple === 'false') {
+						prohibited.multiple = false;
+					}
+					this.multiple = prohibited.multiple;
+					location.href = 'callback?retype=updateOption';
+					}, false);
+				},
+				methods: {
+					toast(msg) {
+						plus.nativeUI.toast(msg);
+					},
+					clear(name) {
+						if (!name) {
+							this.files.clear();
+							return;
+						}
+						this.files.delete(name);
+					},
+					setData(option='{}') {
+						this.debug&&console.log('更新参数:'+option);
+						try{
+							_this.option = JSON.parse(option);
+						}catch(e){
+							console.error('参数设置错误')
+						}
+					},
+					async upload(name=''){
+						if (name && this.files.has(name)) {
+							await this.createUpload(this.files.get(name));
+						}
+						else {
+							for (let item of this.files.values()) {
+								if (item.type === 'waiting' || item.type === 'fail') {
+									await this.createUpload(item);
+								}
+							}
+						}
+					},
+					onChange(e) {
+						let fileDom = this.$refs.file;
+						for (let file of fileDom.files) {
+							if (this.files.size >= this.prohibited.count) {
+								this.toast(`只允许上传${this.prohibited.count}个文件`);
+								fileDom.value = '';
+								break;
+							}
+							this.addFile(file);
+						}
+						this.uploadAfter();
+						fileDom.value = '';
+					},
+					addFile(file) {
+						if (file) {
+							let name = file.name;
+							this.debug&&console.log('文件名称',name,'大小',file.size);
+							// 限制文件格式
+							let suffix = name.substring(name.lastIndexOf(".")+1).toLowerCase();
+							let formats = this.prohibited.formats.toLowerCase();
+							if (formats&&!formats.includes(suffix)) {
+								this.toast(`不支持上传${suffix.toUpperCase()}格式文件`);
+								return;
+							}
+							// 限制文件大小
+							if (file.size > 1024 * 1024 * Math.abs(this.prohibited.size)) {
+								this.toast(`附件大小请勿超过${this.prohibited.size}M`)
+								return;
+							}
+							// let itemBlob = new Blob([file]);
+							let path = URL.createObjectURL(file);
+							this.files.set(file.name,{file,path,name: file.name,size: file.size,progress: 0,type: 'waiting'});
+						}
+					},
+					/**
+					 * @returns {Map} 已选择的文件Map集
+					 */
+					callChange() {
+						location.href = 'callback?retype=change&files=' + escape(JSON.stringify([...this.files]));
+					},
+					/**
+					 * @returns {object} 正在处理的当前对象
+					 */
+					changeFilesItem(item,end='') {
+						this.files.set(item.name,item);
+						location.href = 'callback?retype=progress&end='+ end +'&item=' + escape(JSON.stringify(item));
+					},
+					uploadAfter() {
+						this.callChange();
+						setTimeout(()=>{
+							this.instantly&&this.upload();
+						},1000)
+					},
+					createUpload(item) {
+						this.debug&&console.log('准备上传,option=:'+JSON.stringify(this.option));
+						item.type = 'loading';
+						delete item.responseText;
+						return new Promise((resolve,reject)=>{
+							let {url,name,method='POST',header={},formData={}} = this.option;
+							formData.filename = item.name
+							let form = new FormData();
+							for (let keys in formData) {
+								form.append(keys, formData[keys])
+							}
+							form.append(name, item.file);
+							let xmlRequest = new XMLHttpRequest();
+							xmlRequest.open(method, url, true);
+							for (let keys in header) {
+								xmlRequest.setRequestHeader(keys, header[keys])
+							}
+							xmlRequest.upload.addEventListener(
+								'progress',
+								event => {
+									if (event.lengthComputable) {
+										let progress = Math.ceil((event.loaded * 100) / event.total)
+										if (progress <= 100) {
+											item.progress = progress;
+											this.changeFilesItem(item);
+										}
+									}
+								},
+								false
+							);
+							
+							xmlRequest.ontimeout = () => {
+								console.error('请求超时')
+								item.type = 'fail';
+								this.changeFilesItem(item,true);
+								return resolve(false);
+							}
+							
+							xmlRequest.onreadystatechange = ev => {
+								if (xmlRequest.readyState == 4) {
+									this.debug && console.log('接口是否支持跨域',xmlRequest.withCredentials); 
+									if (xmlRequest.status == 200) {
+										this.debug && console.log('上传完成:' + xmlRequest.responseText)
+										item['responseText'] = xmlRequest.responseText;
+										item.type = 'success';
+										this.changeFilesItem(item,true);
+										return resolve(true);
+									} else if (xmlRequest.status == 0) {
+										console.error('status = 0 :请检查请求头Content-Type与服务端是否匹配,服务端已正确开启跨域,并且nginx未拦截阻止请求')
+									}
+									console.error('--ERROR--:status = ' + xmlRequest.status) 
+									item.type = 'fail';
+									this.changeFilesItem(item,true);
+									return resolve(false);
+								}
+							}
+							xmlRequest.send(form)
+						});
+						
+					}
+				}
+			});
+			
+		</script>
+	</body>
+
+</html>

+ 78 - 0
uni_modules/lsj-upload/package.json

@@ -0,0 +1,78 @@
+{
+    "id": "lsj-upload",
+    "displayName": "全文件上传选择非原生2.0版",
+    "version": "2.2.9",
+    "description": "文件选择上传-支持APP-H5网页-微信小程序",
+    "keywords": [
+        "附件",
+        "file",
+        "upload",
+        "上传",
+        "文件管理器"
+    ],
+    "repository": "",
+    "engines": {
+    },
+    "dcloudext": {
+        "sale": {
+            "regular": {
+                "price": "0.00"
+            },
+            "sourcecode": {
+                "price": "0.00"
+            }
+        },
+        "contact": {
+            "qq": ""
+        },
+        "declaration": {
+            "ads": "无",
+            "data": "无",
+            "permissions": "无"
+        },
+        "npmurl": "",
+        "type": "component-vue"
+    },
+    "uni_modules": {
+        "platforms": {
+            "cloud": {
+                "tcb": "y",
+                "aliyun": "y"
+            },
+            "client": {
+                "App": {
+                    "app-vue": "y",
+                    "app-nvue": "y"
+                },
+                "H5-mobile": {
+                    "Safari": "y",
+                    "Android Browser": "y",
+                    "微信浏览器(Android)": "y",
+                    "QQ浏览器(Android)": "y"
+                },
+                "H5-pc": {
+                    "Chrome": "y",
+                    "IE": "y",
+                    "Edge": "y",
+                    "Firefox": "y",
+                    "Safari": "y"
+                },
+                "小程序": {
+                    "微信": "y",
+                    "阿里": "u",
+                    "百度": "u",
+                    "字节跳动": "u",
+                    "QQ": "u"
+                },
+                "快应用": {
+                    "华为": "y",
+                    "联盟": "y"
+                },
+                "Vue": {
+                    "vue2": "y",
+                    "vue3": "y"
+                }
+            }
+        }
+    }
+}

+ 353 - 0
uni_modules/lsj-upload/readme.md

@@ -0,0 +1,353 @@
+# lsj-upload
+
+### 插件地址:https://ext.dcloud.net.cn/plugin?id=5459
+
+### 不清楚使用方式可点击右侧导入示例项目运行完整示例
+### 此次更新2.0与1.0使用方式略有差异,已使用1.0的同学自行斟酌是否更新到2.0版本!!!
+
+使用插件有任何问题欢迎加入QQ讨论群:
+- 群1:701468256(已满)
+- 群2:469580165(已满)
+- 群3:667530868
+
+若能帮到你请高抬贵手点亮5颗星~
+------
+## 重要提示
+### 组件是窗口级滚动,不要在scroll-view内使用!!
+### 组件是窗口级滚动,不要在scroll-view内使用!!
+### 组件是窗口级滚动,不要在scroll-view内使用!!
+
+### 控件的height高度应与slot自定义内容高度保持一致
+### nvue窗口只能使用固定模式position=absolute
+### show() 当DOM重排后在this.$nextTick内调用show(),控件定位会更加准确
+### hide() APP端webview层级比view高,如不希望触发点击时,应调用hide隐藏控件,反之调用show
+### 若iOS端跨域服务端同学实在配置不好,可把hybrid下html目录放到服务器去,同源则不存在跨域问题。
+### 小程序端因hybrid不能使用本地HTML,所以插件提供的是从微信消息列表拉取文件并选择,请知悉。
+### file对象不是object对象,也不能转json字符串,如果你打印file那就是{},可以打印file.name和file.size。
+### 返回的path是个blob类型,仅供用于文件回显,插件已内置好上传函数,调用上传会自动提交待上传文件,若非要自己拿path去搞上传那你自己处理。
+------
+
+## 使用说明
+| 属性		| 是否必填	|  值类型	| 默认值	| 说明			|
+| --------- | -------- 	| -----: 	| --: 	| :------------:|
+| width		|	否 		| String	|100%	| 容器宽度		|
+| height	|	是 		| String	|80rpx	| 容器高度		|
+| debug		|	否 		| Boolean	|false	| 打印调试日志	|
+| option	|	是 		| Object	|-		| [文件上传接口相关参数](#p1)|
+| instantly	|	否 		| Boolean	|false	| true=自动上传	|
+| count		|	否 		| Number	|10		| 附件选择上限(个)|
+| size		|	否 		| Number	|10		| 附件大小上限(M)|
+| wxFileType	|	否 		| String	|all		| 微信小程序文件选择器格式限制(all=从所有文件选择,video=只能选择视频文件,image=只能选择图片文件,file=可以选择除了图片和视频之外的其它的文件)|
+| accept	|	否 		| String	|-		| 文件选择器input file格式限制(部分机型不兼容,建议使用formats)|
+| formats	|	否 		| String	|-		| 限制允许上传的格式,空串=不限制,默认为空,多个格式以逗号隔开,例如png,jpg,pdf|
+| childId	|	否 		| String	|lsjUpload| 控件的id(仅APP有效,应用内每个控件命名一个唯一Id,不同窗口也不要同名Id)|
+| position	|	否 		| String	|static	| 控件的定位模式(static=控件随页面滚动;absolute=控件在页面中绝对定位,不随窗口内容滚动)|
+| top,left,right,bottom	|	否 		| [Number,String]	|0		| 设置控件绝对位置,position=absolute时有效|
+| @change	|	否 		| Function	|Map	| 选择文件触发,返回所有已选择文件Map集合|
+| @progress	|	否 		| Function	|Object	| 上传过程中发生状态变化的文件对象,需通过set更新至Map集合|
+| @uploadEnd|	否 		| Function	|Object	| 上传结束回调,返回参数与progress一致|
+
+## <a id="p1">option说明</a>
+|参数 | 是否必填 |  说明|
+|---- | ---- | :--: |
+|url  |	是	| 上传接口地址|
+|name| 否	|上传接口文件key,默认为file|
+|header| 否	|上传接口请求头|
+|formData| 否	|上传接口额外参数|
+
+## ref调用
+|作用 | 方法名| 传入参数|  说明|
+|---- | --------- | -------- | :--: |
+|显示控件| show|-| 控件显示状态下可触发点击|
+|隐藏控件| hide|-| 控件隐藏状态下不触发点击|
+|动态设置文件列表| setFiles|[Array,Map] files| 传入格式请与组件选择返回格式保持一致,且name为必须属性,可查看下方演示|
+|动态更新参数| setData|[String] name,[any] value| name支持a.b 和 a[b],可查看下方演示|
+|移除选择的文件| clear|[String] name| 不传参数清空所有文件,传入文件name时删除该name的文件|
+|手动上传| upload|[String] name| 不传参数默认依次上传所有type=waiting的文件,传入文件name时不关心type是否为waiting,单独上传指定name的文件|
+
+## progress返回对象字段说明
+|字段 |  说明|
+|---- | :--: |
+|file | 文件对象|
+|name |文件名称|
+|size |文件大小|
+|type |文件上传状态:waiting(等待上传)、loading(上传中)、success(成功) 、fail(失败)|
+|responseText|上传成功后服务端返回数据(仅type为success时存在)|
+
+## 以下演示为vue窗口使用方式,nvue使用区别是必须传入控件绝对位置如top,bottom,left,right,且position只能为absolute,如不清楚可点击右侧导入示例项目有详细演示代码。
+
+### vue:
+``` javascript
+<lsj-upload 
+	ref="lsjUpload"
+	childId="upload1"
+	:width="width"
+	:height="height"
+	:option="option"
+	:size="size"
+	:formats="formats"
+	:debug="debug"
+	:instantly="instantly"
+	@uploadEnd="onuploadEnd"
+	@progress="onprogre"
+	@change="onChange">
+		<view class="btn" :style="{width: width,height: height}">选择附件</view>
+</lsj-upload>
+
+
+<view class="padding">
+			
+	<view>已选择文件列表:</view>
+	
+	<!-- #ifndef MP-WEIXIN -->
+	<view v-for="(item,index) in files.values()" :key="index">
+		<image style="width: 100rpx;height: 100rpx;" :src="item.path" mode="widthFix"></image>
+		<text>提示:【path主要用于图片视频类文件回显,他用自行处理】:{{item.path}}</text>
+		<text>{{item.name}}</text>
+		<text style="margin-left: 10rpx;">大小:{{item.size}}</text>
+		<text style="margin-left: 10rpx;">状态:{{item.type}}</text>
+		<text style="margin-left: 10rpx;">进度:{{item.progress}}</text>
+		<text style="margin-left: 10rpx;" v-if="item.responseText">服务端返回演示:{{item.responseText}}</text>
+		<text @click="resetUpload(item.name)" v-if="item.type=='fail'" style="margin-left: 10rpx;padding: 0 10rpx;border: 1rpx solid #007AFF;">重新上传</text>
+		<text @click="clear(item.name)" style="margin-left: 10rpx;padding: 0 10rpx;border: 1rpx solid #007AFF;">删除</text>
+	</view>
+	<!-- #endif -->
+	
+	<!-- #ifdef MP-WEIXIN -->
+	<view v-for="(item,index) in wxFiles" :key="index">
+		<text>{{item.name}}</text>
+		<text style="margin-left: 10rpx;">大小:{{item.size}}</text>
+		<text style="margin-left: 10rpx;">状态:{{item.type}}</text>
+		<text style="margin-left: 10rpx;">进度:{{item.progress}}</text>
+		<view>
+			<button @click="resetUpload(item.name)">重新上传</button>
+			<button @click="clear(item.name)">删除</button>
+		</view>
+	</view>
+	<!-- #endif -->
+	
+</view>
+
+
+```
+
+---
+* 函数说明
+
+
+``` javascript
+export default {
+	data() {
+		return {
+			// 上传接口参数
+			option: {
+				// 上传服务器地址,需要替换为你的接口地址
+				url: 'http://hl.j56.com/dropbox/document/upload', // 该地址非真实路径,需替换为你项目自己的接口地址
+				// 上传附件的key
+				name: 'file',
+				// 根据你接口需求自定义请求头,默认不要写content-type,让浏览器自适配
+				header: {
+					// 示例参数可删除
+					'Authorization': 'bearer eyJhbGciOiJSUzI1NiIsI',
+					'uid': '99',
+					'client': 'app',
+					'accountid': 'DP',
+				},
+				// 根据你接口需求自定义body参数
+				formData: {
+					// 'orderId': 1000
+				}
+			},
+			// 选择文件后是否立即自动上传,true=选择后立即上传
+			instantly: true,
+			// 必传宽高且宽高应与slot宽高保持一致
+			width: '180rpx',
+			height: '180rpx',
+			// 限制允许上传的格式,空串=不限制,默认为空
+			formats: '',
+			// 文件上传大小限制
+			size: 30,
+			// 文件数量限制
+			count: 2,
+			// 文件回显列表
+			files: new Map(),
+			// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
+			wxFiles: [],
+			// 是否打印日志
+			debug: true,
+			
+			
+			// 演示用
+			tabIndex: 0,
+			list:[], 
+		}
+	},
+	onReady() {
+		setTimeout(()=>{
+			console.log('----演示动态更新参数-----');
+			this.$refs['lsjUpload'+this.tabIndex].setData('formData.orderId','动态设置的参数'); 
+			
+			console.log('以下注释内容为-动态更新参数更多演示,放开后可查看演示效果');
+			// 修改option对象的name属性
+			// this.$refs.lsjUpload.setData('name','myFile');
+			
+			// 修改option对象的formData内的属性
+			// this.$refs.lsjUpload.setData('formData.appid','1111');
+			
+			// 替换option对象的formData
+			// this.$refs.lsjUpload.setData('formData',{appid:'222'});
+			
+			// option对象的formData新增属性
+			// this.$refs.lsjUpload.setData('formData.newkey','新插入到formData的属性');
+			
+			
+			// ---------演示初始化值,用于已提交后再次编辑时需带入已上传文件-------
+			// 方式1=传入数组
+			// let files1 = [{name: '1.png'},{name: '2.png',}];
+			
+			// 方式2=传入Map对象
+			// let files2 = new Map();
+			// files2.set('1.png',{name: '1.png'})
+			
+			// 此处调用setFiles设置初始files
+			// this.$refs.lsjUpload.setFiles(files1);
+			
+			// 初始化tab
+			this.onTab(0);
+		},1000)
+	},
+	methods: {
+		// 某文件上传结束回调(成功失败都回调)
+		onuploadEnd(item) {
+			console.log(`${item.name}已上传结束,上传状态=${item.type}`);
+			
+			// 更新当前窗口状态变化的文件
+			this.files.set(item.name,item);
+			
+			// ---可删除--演示上传完成后取服务端数据
+			if (item['responseText']) {
+				console.log('演示服务器返回的字符串JSON转Object对象');
+				this.files.get(item.name).responseText = JSON.parse(item.responseText);
+			}
+			
+			// 微信小程序Map对象for循环不显示,所以转成普通数组,
+			// 如果你用不惯Map对象,也可以像这样转普通数组,组件使用Map主要是避免反复文件去重操作
+			// #ifdef MP-WEIXIN
+			this.wxFiles = [...this.files.values()];
+			// #endif
+			
+			// 强制更新视图
+			this.$forceUpdate();
+			
+			
+			// ---可删除--演示判断是否所有文件均已上传成功
+			let isAll = [...this.files.values()].find(item=>item.type!=='success');
+			if (!isAll) {
+				console.log('已全部上传完毕');
+			}
+			else {
+				console.log(isAll.name+'待上传');
+			}
+			
+		},
+		// 上传进度回调,如果网页上md文档没有渲染出事件名称onprogre,请复制代码的小伙伴自行添加上哈,没有哪个事件是只(item)的
+		onprogre(item) {
+			// 更新当前状态变化的文件
+			this.files.set(item.name,item);
+			
+			console.log('打印对象',JSON.stringify(this.files.get(item.name)));
+			// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
+			// #ifdef MP-WEIXIN
+			this.wxFiles = [...this.files.values()];
+			// #endif
+			
+			// 强制更新视图
+			this.$forceUpdate();
+			
+		},
+		// 文件选择回调
+		onChange(files) {
+			console.log('当前选择的文件列表:',JSON.stringify([...files.values()]));
+			// 更新选择的文件 
+			this.files = files;
+			// 强制更新视图
+			this.$forceUpdate();
+			
+			// 微信小程序Map对象for循环不显示,所以转成普通数组,不要问为什么,我也不知道
+			// #ifdef MP-WEIXIN
+			this.wxFiles = [...this.files.values()];
+			// #endif
+			
+			// ---可删除--演示重新定位覆盖层控件
+			this.$nextTick(()=>{
+				console.log('演示重新定位');
+				this.$refs.lsjUpload0.show();
+				this.$refs.lsjUpload1.show();
+				this.$refs.lsjUpload2.show();
+			});
+			
+		},
+		// 手动上传
+		upload() {
+			// name=指定文件名,不指定则上传所有type等于waiting和fail的文件
+			this.$refs['lsjUpload'+this.tabIndex].upload();
+		},
+		// 指定上传某个文件
+		resetUpload(name) {
+			this.$refs['lsjUpload'+this.tabIndex].upload(name);
+		},
+		// 移除某个文件
+		clear(name) {
+			// name=指定文件名,不传name默认移除所有文件
+			this.$refs['lsjUpload'+this.tabIndex].clear(name);
+		},
+		/**
+		 * ---可删除--演示在组件上方添加新内容DOM变化
+		 * DOM重排演示,重排后组件内部updated默认会触发show方法,若特殊情况未能触发updated也可以手动调用一次show()
+		 * 什么是DOM重排?自行百度去
+		 */
+		add() {
+			this.list.push('DOM重排测试');
+		},
+		/**
+		 * ---可删除--演示Tab切换时覆盖层是否能被点击
+		 * APP端因为是webview,层级比view高,此时若不希望点击触发选择文件,需要手动调用hide()
+		 * 手动调用hide后,需要调用show()才能恢复覆盖层的点击
+		 */
+		onTab(tabIndex) {
+			this.$refs.lsjUpload0.hide();
+			this.$refs.lsjUpload1.hide();
+			
+			this.tabIndex = tabIndex;
+			
+			this.$nextTick(()=>{
+				this.$refs['lsjUpload'+this.tabIndex].show();
+			})
+			
+		},
+		/**
+		 * 打开nvue窗口查看非跟随窗口滚动效果
+		 */
+		open() {
+			uni.navigateTo({
+				url: '/pages/nvue-demo/nvue-demo'
+			});
+		}
+	}
+}
+
+```
+
+## 温馨提示
+	
+* 文件上传
+0. 如说明表达还不够清楚,不清楚怎么使用可导入完整示例项目运行体验和查看	
+1. APP端请优先联调Android,上传成功后再运行iOS端,如iOS返回status=0则需要后端开启允许跨域;
+2. header的Content-Type类型需要与服务端要求一致,否则收不到附件(服务端若没有明文规定则可不写,使用默认匹配)
+3. 服务端不清楚怎么配置跨域可加群咨询,具体百度~
+4. 欢迎加入QQ讨论群:701468256(已满)
+5. 欢迎加入QQ讨论群:469580165(已满)
+6. 欢迎加入QQ讨论群:667530868
+7. 若能帮到你还请点亮5颗小星星以作鼓励哈~
+8. 若能帮到你还请点亮5颗小星星以作鼓励哈~
+9. 若能帮到你还请点亮5颗小星星以作鼓励哈~

+ 19 - 0
uni_modules/song-data-picker/changelog.md

@@ -0,0 +1,19 @@
+## 1.0.4(2023-11-19)
+1. 合并到官方`uni-data-picker`版本到1.1.2
+## 1.0.3(2022-08-17)
+1. 添加示例项目
+## 1.0.2(2022-08-17)
+
+1. serchFn 自定义搜索函数添加异步支持
+2. serchFn 自定义搜索函数添加回调支持
+3. 修正 readme 中的描述错误
+4. 包内的`uni-*`组件修改为`song-*`组件,防止冲突
+
+## 1.0.1(2022-06-14)
+
+1. 添加对官方组件 map 属性的支持
+
+## 1.0.0(2022-06-14)
+
+1. 同步 uni-data-picker 版本到 1.0.4,uDataPicker 最新特性已经具备
+2. 升级为 uni_modules 组件

+ 45 - 0
uni_modules/song-data-picker/components/song-data-picker/keypress.js

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

+ 135 - 0
uni_modules/song-data-picker/components/song-data-picker/searchMixin.js

@@ -0,0 +1,135 @@
+export default {
+	props: {
+		openSearch: {
+			type: Boolean,
+			default: false
+		},
+		searchFn: {
+			type: Function,
+			default: null
+		},
+		openInputSearch: {
+			type: [Boolean, Number],
+			default: false
+		}
+	},
+	data() {
+		return {
+			searchWord: '',
+			showSearchResult: false,
+			searchDatas: [],
+			searchResult: []
+		}
+	},
+	watch: {
+		searchWord() {
+			this.inputSearchBegin()
+		}
+	},
+	methods: {
+		closeSearchDom() {
+			this.showSearchResult = false;
+			this.searchWord = '';
+			this.searchResult = [];
+		},
+		// 传入的是搜索时构建的对象,不会因为map改变键
+		clickSearchResult(item) {
+			this.closeSearchDom();
+			const e = [];
+			const valueField = this.map.value;
+			const textField = this.map.text;
+			const loop = (rows, ids) => {
+				if (!ids.length) return
+				let row = rows.find(x => x[valueField] == ids[0]);
+				if (e.length) {
+					row.parent_value = e[e.length - 1][valueField];
+				}
+				e.push({
+					...row,
+					value: row[valueField],
+					text: row[textField]
+				});
+				if (!row.children && !row.children instanceof Array && !row.children.length) return;
+				loop(row.children, ids.splice(1, ids.length - 1));
+			};
+			loop(this.localdata, (item.value + '').split(','));
+			// 支持map
+
+			this.onchange(e);
+		},
+		inputSearchBegin() {
+			if (!this.openInputSearch || !this.searchWord) {
+				return;
+			}
+			if (typeof this.openInputSearch === 'number') {
+				if (this.searchWord.length >= this.openInputSearch) {
+					this.searchBegin();
+				}
+			} else {
+				this.searchBegin();
+			}
+		},
+		searchBegin() {
+			if (!this.searchWord) {
+				this.showSearchResult = false;
+				return;
+			}
+			const setResultFn = (r) => {
+				this.searchResult = r
+				this.showSearchResult = true;
+			};
+			if (this.searchFn && this.searchFn instanceof Function) {
+				/**
+				 * 提供一个callback用于调用者在内部调整显示数据
+				 */
+				let callbackFlag = false;
+				const fnResult = this.searchFn(this.searchDatas, this.searchWord, (result) => {
+					setResultFn(result);
+					callbackFlag = true;
+				});
+				/**
+				 * 如果返回了一个Promise,则获取resolve数据,当为array时,当做查询结果处理
+				 */
+				if (fnResult instanceof Promise) {
+					fnResult.then((result) => {
+						if (!result instanceof Array) return;
+						setResultFn(result);
+					})
+				} else if (fnResult && !callbackFlag) {
+					setResultFn(fnResult);
+				}
+			} else {
+				// 默认实现
+				setResultFn(this.searchDatas.filter(x =>
+					x.searchText.includes(this.searchWord)
+				));
+			}
+		},
+		initSearchDatas() {
+			if (this.openSearch && this.localdata && this.localdata.length > 0) {
+				const valueField = this.map.value;
+				const textField = this.map.text;
+				const loop = (row, values, texts) => {
+					let value = values ? `${values},${row[valueField]}` : row[valueField];
+					let text = texts ? `${texts} ${row[textField]}` : row[textField];
+					let searchText = text.split(' ').join('');
+					if (row.children && row.children.length > 0) {
+						row.children.forEach(x => {
+							loop(x, value, text);
+						});
+					} else {
+						this.searchDatas.push({
+							value,
+							text,
+							searchText
+						});
+					}
+				};
+				this.searchDatas = [];
+				this.localdata.forEach(x => {
+					loop(x);
+				});
+			}
+		},
+	}
+}

+ 579 - 0
uni_modules/song-data-picker/components/song-data-picker/song-data-picker.vue

@@ -0,0 +1,579 @@
+<template>
+	<view class="uni-data-tree">
+		<view class="uni-data-tree-input" @click="handleInput">
+			<slot :options="options" :data="inputSelected" :error="errorMessage">
+				<view class="input-value" :class="{'input-value-border': border}">
+					<text v-if="errorMessage" class="selected-area error-text">{{errorMessage}}</text>
+					<view v-else-if="loading && !isOpened" class="selected-area">
+						<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
+					</view>
+					<scroll-view v-else-if="inputSelected.length" class="selected-area" scroll-x="true">
+						<view class="selected-list">
+							<view class="selected-item" v-for="(item,index) in inputSelected" :key="index">
+								<text>{{item.text}}</text><text v-if="index<inputSelected.length-1"
+									class="input-split-line">{{split}}</text>
+							</view>
+						</view>
+					</scroll-view>
+					<text v-else class="selected-area placeholder">{{placeholder}}</text>
+					<view v-if="clearIcon && !readonly && inputSelected.length" class="icon-clear" @click.stop="clear">
+						<uni-icons type="clear" color="#e1e1e1" size="14"></uni-icons>
+					</view>
+					<view class="arrow-area" v-if="(!clearIcon || !inputSelected.length) && !readonly ">
+						<view class="input-arrow"></view>
+					</view>
+				</view>
+			</slot>
+		</view>
+		<view class="uni-data-tree-cover" v-if="isOpened" @click="handleClose"></view>
+		<view class="uni-data-tree-dialog" v-if="isOpened">
+			<view class="uni-popper__arrow"></view>
+			<view class="dialog-caption">
+				<view class="title-area">
+					<text class="dialog-title">{{popupTitle}}</text>
+				</view>
+				<view class="dialog-close" @click="handleClose">
+					<view class="dialog-close-plus" data-id="close"></view>
+					<view class="dialog-close-plus dialog-close-rotate" data-id="close"></view>
+				</view>
+			</view>
+			<!-- search增强-start -->
+			<view class="dialog-caption" v-if="openSearch">
+				<view style="width: 80%; margin: 10px 10%;">
+					<uni-easyinput type="text" suffixIcon="search" v-model="searchWord" placeholder="输入关键字搜索"
+						@iconClick="searchBegin" @confirm="searchBegin"></uni-easyinput>
+				</view>
+			</view>
+
+			<view class="picker-view" style="overflow-y:auto" v-if="showSearchResult">
+				<view v-show="searchResult.length">
+					<uni-list-item v-for="(item, index) in searchResult" :key="index" :title="item.text" clickable link
+						@click="clickSearchResult(item)"></uni-list-item>
+				</view>
+				<view v-show="!searchResult.length" style="text-align: center;line-height:200px;"><text
+						style="color: #999999">--未发现任何匹配数据,请重新输入--</text></view>
+			</view>
+			<!-- search增强-end -->
+
+			<data-picker-view v-show="!showSearchResult" class="picker-view" ref="pickerView" v-model="dataValue"
+				:localdata="localdata" :preload="preload" :collection="collection" :field="field" :orderby="orderby"
+				:where="where" :step-searh="stepSearh" :self-field="selfField" :parent-field="parentField"
+				:managed-mode="true" :map="map" :ellipsis="ellipsis" @change="onchange" @datachange="ondatachange"
+				@nodeclick="onnodeclick">
+			</data-picker-view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import dataPicker from "../song-data-pickerview/song-data-picker.js"
+	import DataPickerView from "../song-data-pickerview/song-data-pickerview.vue"
+	import searchMixin from './searchMixin.js'
+
+
+	/**
+	 * DataPicker 级联选择
+	 * @description 支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部tab区域会左右滚动。
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=3796
+	 * @property {String} popup-title 弹出窗口标题
+	 * @property {Array} localdata 本地数据,参考
+	 * @property {Boolean} border = [true|false] 是否有边框
+	 * @property {Boolean} readonly = [true|false] 是否仅读
+	 * @property {Boolean} preload = [true|false] 是否预加载数据
+	 * @value true 开启预加载数据,点击弹出窗口后显示已加载数据
+	 * @value false 关闭预加载数据,点击弹出窗口后开始加载数据
+	 * @property {Boolean} step-searh = [true|false] 是否分布查询
+	 * @value true 启用分布查询,仅查询当前选中节点
+	 * @value false 关闭分布查询,一次查询出所有数据
+	 * @property {String|DBFieldString} self-field 分布查询当前字段名称
+	 * @property {String|DBFieldString} parent-field 分布查询父字段名称
+	 * @property {String|DBCollectionString} collection 表名
+	 * @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
+	 * @property {String} orderby 排序字段及正序倒叙设置
+	 * @property {String|JQLString} where 查询条件
+	 * @event {Function} popupshow 弹出的选择窗口打开时触发此事件
+	 * @event {Function} popuphide 弹出的选择窗口关闭时触发此事件
+	 */
+	export default {
+		name: 'UniDataPicker',
+		emits: ['popupopened', 'popupclosed', 'nodeclick', 'input', 'change', 'update:modelValue', 'inputclick'],
+		mixins: [dataPicker, searchMixin],
+		components: {
+			DataPickerView
+		},
+		props: {
+			options: {
+				type: [Object, Array],
+				default () {
+					return {}
+				}
+			},
+			popupTitle: {
+				type: String,
+				default: '请选择'
+			},
+			placeholder: {
+				type: String,
+				default: '请选择'
+			},
+			heightMobile: {
+				type: String,
+				default: ''
+			},
+			readonly: {
+				type: Boolean,
+				default: false
+			},
+			clearIcon: {
+				type: Boolean,
+				default: true
+			},
+			border: {
+				type: Boolean,
+				default: true
+			},
+			split: {
+				type: String,
+				default: '/'
+			},
+			ellipsis: {
+				type: Boolean,
+				default: true
+			}
+		},
+		data() {
+			return {
+				isOpened: false,
+				inputSelected: []
+			}
+		},
+		created() {
+			this.$nextTick(() => {
+				this.load();
+			})
+		},
+		watch: {
+			localdata: {
+				handler() {
+					this.load()
+				},
+				deep: true
+			},
+		},
+		methods: {
+			clear() {
+				this._dispatchEvent([]);
+			},
+			onPropsChange() {
+				this._treeData = [];
+				this.selectedIndex = 0;
+
+				this.load();
+			},
+			load() {
+				if (this.readonly) {
+					this._processReadonly(this.localdata, this.dataValue);
+					return;
+				}
+
+				// 回显本地数据
+				if (this.isLocalData) {
+					this.loadData();
+					this.inputSelected = this.selected.slice(0);
+					// 搜索增强start
+					// 只在本地数据时搜索生效
+					this.initSearchDatas();
+					// 搜索增强end
+				} else if (this.isCloudDataList || this.isCloudDataTree) { // 回显 Cloud 数据
+					this.loading = true;
+					this.getCloudDataValue().then((res) => {
+						this.loading = false;
+						this.inputSelected = res;
+					}).catch((err) => {
+						this.loading = false;
+						this.errorMessage = err;
+					})
+				}
+			},
+			show() {
+				this.isOpened = true
+				setTimeout(() => {
+					this.$refs.pickerView.updateData({
+						treeData: this._treeData,
+						selected: this.selected,
+						selectedIndex: this.selectedIndex
+					})
+				}, 200)
+				this.$emit('popupopened')
+			},
+			hide() {
+				this.isOpened = false
+				this.$emit('popupclosed')
+				// 搜索增强start
+				this.closeSearchDom();
+				// 搜索增强end
+			},
+			handleInput() {
+				if (this.readonly) {
+					this.$emit('inputclick')
+					return
+				}
+				this.show()
+			},
+			handleClose(e) {
+				this.hide()
+			},
+			onnodeclick(e) {
+				this.$emit('nodeclick', e)
+			},
+			ondatachange(e) {
+				this._treeData = this.$refs.pickerView._treeData
+			},
+			onchange(e) {
+				this.hide()
+				this.$nextTick(() => {
+					this.inputSelected = e;
+				})
+				this._dispatchEvent(e)
+			},
+			_processReadonly(dataList, value) {
+				var isTree = dataList.findIndex((item) => {
+					return item.children
+				})
+				if (isTree > -1) {
+					let inputValue
+					if (Array.isArray(value)) {
+						inputValue = value[value.length - 1]
+						if (typeof inputValue === 'object' && inputValue.value) {
+							inputValue = inputValue.value
+						}
+					} else {
+						inputValue = value
+					}
+					this.inputSelected = this._findNodePath(inputValue, this.localdata)
+					return
+				}
+
+				if (!this.hasValue) {
+					this.inputSelected = []
+					return
+				}
+
+				let result = []
+				for (let i = 0; i < value.length; i++) {
+					var val = value[i]
+					var item = dataList.find((v) => {
+						return v.value == val
+					})
+					if (item) {
+						result.push(item)
+					}
+				}
+				if (result.length) {
+					this.inputSelected = result
+				}
+			},
+			_filterForArray(data, valueArray) {
+				var result = []
+				for (let i = 0; i < valueArray.length; i++) {
+					var value = valueArray[i]
+					var found = data.find((item) => {
+						return item.value == value
+					})
+					if (found) {
+						result.push(found)
+					}
+				}
+				return result
+			},
+			_dispatchEvent(selected) {
+				let item = {}
+				if (selected.length) {
+					var value = new Array(selected.length)
+					for (var i = 0; i < selected.length; i++) {
+						value[i] = selected[i].value
+					}
+					item = selected[selected.length - 1]
+				} else {
+					item.value = ''
+				}
+				if (this.formItem) {
+					this.formItem.setValue(item.value)
+				}
+
+				this.$emit('input', item.value)
+				this.$emit('update:modelValue', item.value)
+				this.$emit('change', {
+					detail: {
+						value: selected
+					}
+				})
+			}
+		}
+	}
+</script>
+
+<style>
+	.uni-data-tree {
+		flex: 1;
+		position: relative;
+		font-size: 14px;
+	}
+
+	.error-text {
+		color: #DD524D;
+	}
+
+	.input-value {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		flex-wrap: nowrap;
+		font-size: 14px;
+		/* line-height: 35px; */
+		padding: 0 10px;
+		padding-right: 5px;
+		overflow: hidden;
+		height: 35px;
+		/* #ifndef APP-NVUE */
+		box-sizing: border-box;
+		/* #endif */
+	}
+
+	.input-value-border {
+		border: 1px solid #e5e5e5;
+		border-radius: 5px;
+	}
+
+	.selected-area {
+		flex: 1;
+		overflow: hidden;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+	}
+
+	.load-more {
+		/* #ifndef APP-NVUE */
+		margin-right: auto;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		width: 40px;
+		/* #endif */
+	}
+
+	.selected-list {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		flex-wrap: nowrap;
+		/* padding: 0 5px; */
+	}
+
+	.selected-item {
+		flex-direction: row;
+		/* padding: 0 1px; */
+		/* #ifndef APP-NVUE */
+		white-space: nowrap;
+		/* #endif */
+	}
+
+	.text-color {
+		color: #333;
+	}
+
+	.placeholder {
+		color: grey;
+		font-size: 12px;
+	}
+
+	.input-split-line {
+		opacity: .5;
+	}
+
+	.arrow-area {
+		position: relative;
+		width: 20px;
+		/* #ifndef APP-NVUE */
+		margin-bottom: 5px;
+		margin-left: auto;
+		display: flex;
+		/* #endif */
+		justify-content: center;
+		transform: rotate(-45deg);
+		transform-origin: center;
+	}
+
+	.input-arrow {
+		width: 7px;
+		height: 7px;
+		border-left: 1px solid #999;
+		border-bottom: 1px solid #999;
+	}
+
+	.uni-data-tree-cover {
+		position: fixed;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(0, 0, 0, .4);
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		z-index: 100;
+	}
+
+	.uni-data-tree-dialog {
+		position: fixed;
+		left: 0;
+		/* #ifndef APP-NVUE */
+		top: 20%;
+		/* #endif */
+		/* #ifdef APP-NVUE */
+		top: 200px;
+		/* #endif */
+		right: 0;
+		bottom: 0;
+		background-color: #FFFFFF;
+		border-top-left-radius: 10px;
+		border-top-right-radius: 10px;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		z-index: 102;
+		overflow: hidden;
+		/* #ifdef APP-NVUE */
+		width: 750rpx;
+		/* #endif */
+	}
+
+	.dialog-caption {
+		position: relative;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		/* border-bottom: 1px solid #f0f0f0; */
+	}
+
+	.title-area {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		align-items: center;
+		/* #ifndef APP-NVUE */
+		margin: auto;
+		/* #endif */
+		padding: 0 10px;
+	}
+
+	.dialog-title {
+		/* font-weight: bold; */
+		line-height: 44px;
+	}
+
+	.dialog-close {
+		position: absolute;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		align-items: center;
+		padding: 0 15px;
+	}
+
+	.dialog-close-plus {
+		width: 16px;
+		height: 2px;
+		background-color: #666;
+		border-radius: 2px;
+		transform: rotate(45deg);
+	}
+
+	.dialog-close-rotate {
+		position: absolute;
+		transform: rotate(-45deg);
+	}
+
+	.picker-view {
+		flex: 1;
+		overflow: hidden;
+	}
+
+	.icon-clear {
+		display: flex;
+		align-items: center;
+	}
+
+	/* #ifdef H5 */
+	@media all and (min-width: 768px) {
+		.uni-data-tree-cover {
+			background-color: transparent;
+		}
+
+		.uni-data-tree-dialog {
+			position: absolute;
+			top: 55px;
+			height: auto;
+			min-height: 400px;
+			max-height: 50vh;
+			background-color: #fff;
+			border: 1px solid #EBEEF5;
+			box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+			border-radius: 4px;
+			overflow: unset;
+		}
+
+		.dialog-caption {
+			display: none;
+		}
+
+		.icon-clear {
+			/* margin-right: 5px; */
+		}
+	}
+
+	/* #endif */
+
+	/* picker 弹出层通用的指示小三角, todo:扩展至上下左右方向定位 */
+	/* #ifndef APP-NVUE */
+	.uni-popper__arrow,
+	.uni-popper__arrow::after {
+		position: absolute;
+		display: block;
+		width: 0;
+		height: 0;
+		border-color: transparent;
+		border-style: solid;
+		border-width: 6px;
+	}
+
+	.uni-popper__arrow {
+		filter: drop-shadow(0 2px 12px rgba(0, 0, 0, 0.03));
+		top: -6px;
+		left: 10%;
+		margin-right: 3px;
+		border-top-width: 0;
+		border-bottom-color: #EBEEF5;
+	}
+
+	.uni-popper__arrow::after {
+		content: " ";
+		top: 1px;
+		margin-left: -6px;
+		border-top-width: 0;
+		border-bottom-color: #fff;
+	}
+
+	/* #endif */
+</style>

+ 622 - 0
uni_modules/song-data-picker/components/song-data-pickerview/song-data-picker.js

@@ -0,0 +1,622 @@
+export default {
+  props: {
+    localdata: {
+      type: [Array, Object],
+      default () {
+        return []
+      }
+    },
+    spaceInfo: {
+      type: Object,
+      default () {
+        return {}
+      }
+    },
+    collection: {
+      type: String,
+      default: ''
+    },
+    action: {
+      type: String,
+      default: ''
+    },
+    field: {
+      type: String,
+      default: ''
+    },
+    orderby: {
+      type: String,
+      default: ''
+    },
+    where: {
+      type: [String, Object],
+      default: ''
+    },
+    pageData: {
+      type: String,
+      default: 'add'
+    },
+    pageCurrent: {
+      type: Number,
+      default: 1
+    },
+    pageSize: {
+      type: Number,
+      default: 500
+    },
+    getcount: {
+      type: [Boolean, String],
+      default: false
+    },
+    getone: {
+      type: [Boolean, String],
+      default: false
+    },
+    gettree: {
+      type: [Boolean, String],
+      default: false
+    },
+    manual: {
+      type: Boolean,
+      default: false
+    },
+    value: {
+      type: [Array, String, Number],
+      default () {
+        return []
+      }
+    },
+    modelValue: {
+      type: [Array, String, Number],
+      default () {
+        return []
+      }
+    },
+    preload: {
+      type: Boolean,
+      default: false
+    },
+    stepSearh: {
+      type: Boolean,
+      default: true
+    },
+    selfField: {
+      type: String,
+      default: ''
+    },
+    parentField: {
+      type: String,
+      default: ''
+    },
+    multiple: {
+      type: Boolean,
+      default: false
+    },
+    map: {
+      type: Object,
+      default () {
+        return {
+          text: "text",
+          value: "value"
+        }
+      }
+    }
+  },
+  data() {
+    return {
+      loading: false,
+      errorMessage: '',
+      loadMore: {
+        contentdown: '',
+        contentrefresh: '',
+        contentnomore: ''
+      },
+      dataList: [],
+      selected: [],
+      selectedIndex: 0,
+      page: {
+        current: this.pageCurrent,
+        size: this.pageSize,
+        count: 0
+      }
+    }
+  },
+  computed: {
+    isLocalData() {
+      return !this.collection.length;
+    },
+    isCloudData() {
+      return this.collection.length > 0;
+    },
+    isCloudDataList() {
+      return (this.isCloudData && (!this.parentField && !this.selfField));
+    },
+    isCloudDataTree() {
+      return (this.isCloudData && this.parentField && this.selfField);
+    },
+    dataValue() {
+      let isModelValue = Array.isArray(this.modelValue) ? (this.modelValue.length > 0) : (this.modelValue !== null ||
+        this.modelValue !== undefined);
+      return isModelValue ? this.modelValue : this.value;
+    },
+    hasValue() {
+      if (typeof this.dataValue === 'number') {
+        return true
+      }
+      return (this.dataValue != null) && (this.dataValue.length > 0)
+    }
+  },
+  created() {
+    this.$watch(() => {
+      var al = [];
+      ['pageCurrent',
+        'pageSize',
+        'spaceInfo',
+        'value',
+        'modelValue',
+        'localdata',
+        'collection',
+        'action',
+        'field',
+        'orderby',
+        'where',
+        'getont',
+        'getcount',
+        'gettree'
+      ].forEach(key => {
+        al.push(this[key])
+      });
+      return al
+    }, (newValue, oldValue) => {
+      let needReset = false
+      for (let i = 2; i < newValue.length; i++) {
+        if (newValue[i] != oldValue[i]) {
+          needReset = true
+          break
+        }
+      }
+      if (newValue[0] != oldValue[0]) {
+        this.page.current = this.pageCurrent
+      }
+      this.page.size = this.pageSize
+
+      this.onPropsChange()
+    })
+    this._treeData = []
+  },
+  methods: {
+    onPropsChange() {
+      this._treeData = [];
+    },
+
+    // 填充 pickview 数据
+    async loadData() {
+      if (this.isLocalData) {
+        this.loadLocalData();
+      } else if (this.isCloudDataList) {
+        this.loadCloudDataList();
+      } else if (this.isCloudDataTree) {
+        this.loadCloudDataTree();
+      }
+    },
+
+    // 加载本地数据
+    async loadLocalData() {
+      this._treeData = [];
+      this._extractTree(this.localdata, this._treeData);
+
+      let inputValue = this.dataValue;
+      if (inputValue === undefined) {
+        return;
+      }
+
+      if (Array.isArray(inputValue)) {
+        inputValue = inputValue[inputValue.length - 1];
+        if (typeof inputValue === 'object' && inputValue[this.map.value]) {
+          inputValue = inputValue[this.map.value];
+        }
+      }
+
+      this.selected = this._findNodePath(inputValue, this.localdata);
+    },
+
+    // 加载 Cloud 数据 (单列)
+    async loadCloudDataList() {
+      if (this.loading) {
+        return;
+      }
+      this.loading = true;
+
+      try {
+        let response = await this.getCommand();
+        let responseData = response.result.data;
+
+        this._treeData = responseData;
+
+        this._updateBindData();
+        this._updateSelected();
+
+        this.onDataChange();
+      } catch (e) {
+        this.errorMessage = e;
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    // 加载 Cloud 数据 (树形)
+    async loadCloudDataTree() {
+      if (this.loading) {
+        return;
+      }
+      this.loading = true;
+
+      try {
+        let commandOptions = {
+          field: this._cloudDataPostField(),
+          where: this._cloudDataTreeWhere()
+        };
+        if (this.gettree) {
+          commandOptions.startwith = `${this.selfField}=='${this.dataValue}'`;
+        }
+
+        let response = await this.getCommand(commandOptions);
+        let responseData = response.result.data;
+
+        this._treeData = responseData;
+        this._updateBindData();
+        this._updateSelected();
+
+        this.onDataChange();
+      } catch (e) {
+        this.errorMessage = e;
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    // 加载 Cloud 数据 (节点)
+    async loadCloudDataNode(callback) {
+      if (this.loading) {
+        return;
+      }
+      this.loading = true;
+
+      try {
+        let commandOptions = {
+          field: this._cloudDataPostField(),
+          where: this._cloudDataNodeWhere()
+        };
+
+        let response = await this.getCommand(commandOptions);
+        let responseData = response.result.data;
+
+        callback(responseData);
+      } catch (e) {
+        this.errorMessage = e;
+      } finally {
+        this.loading = false;
+      }
+    },
+
+    // 回显 Cloud 数据
+    getCloudDataValue() {
+      if (this.isCloudDataList) {
+        return this.getCloudDataListValue();
+      }
+
+      if (this.isCloudDataTree) {
+        return this.getCloudDataTreeValue();
+      }
+    },
+
+    // 回显 Cloud 数据 (单列)
+    getCloudDataListValue() {
+      // 根据 field's as value标识匹配 where 条件
+      let where = [];
+      let whereField = this._getForeignKeyByField();
+      if (whereField) {
+        where.push(`${whereField} == '${this.dataValue}'`)
+      }
+
+      where = where.join(' || ');
+
+      if (this.where) {
+        where = `(${this.where}) && (${where})`
+      }
+
+      return this.getCommand({
+        field: this._cloudDataPostField(),
+        where
+      }).then((res) => {
+        this.selected = res.result.data;
+        return res.result.data;
+      });
+    },
+
+    // 回显 Cloud 数据 (树形)
+    getCloudDataTreeValue() {
+      return this.getCommand({
+        field: this._cloudDataPostField(),
+        getTreePath: {
+          startWith: `${this.selfField}=='${this.dataValue}'`
+        }
+      }).then((res) => {
+        let treePath = [];
+        this._extractTreePath(res.result.data, treePath);
+        this.selected = treePath;
+        return treePath;
+      });
+    },
+
+    getCommand(options = {}) {
+      /* eslint-disable no-undef */
+      let db = uniCloud.database(this.spaceInfo)
+
+      const action = options.action || this.action
+      if (action) {
+        db = db.action(action)
+      }
+
+      const collection = options.collection || this.collection
+      db = db.collection(collection)
+
+      const where = options.where || this.where
+      if (!(!where || !Object.keys(where).length)) {
+        db = db.where(where)
+      }
+
+      const field = options.field || this.field
+      if (field) {
+        db = db.field(field)
+      }
+
+      const orderby = options.orderby || this.orderby
+      if (orderby) {
+        db = db.orderBy(orderby)
+      }
+
+      const current = options.pageCurrent !== undefined ? options.pageCurrent : this.page.current
+      const size = options.pageSize !== undefined ? options.pageSize : this.page.size
+      const getCount = options.getcount !== undefined ? options.getcount : this.getcount
+      const getTree = options.gettree !== undefined ? options.gettree : this.gettree
+
+      const getOptions = {
+        getCount,
+        getTree
+      }
+      if (options.getTreePath) {
+        getOptions.getTreePath = options.getTreePath
+      }
+
+      db = db.skip(size * (current - 1)).limit(size).get(getOptions)
+
+      return db
+    },
+
+    _cloudDataPostField() {
+      let fields = [this.field];
+      if (this.parentField) {
+        fields.push(`${this.parentField} as parent_value`);
+      }
+      return fields.join(',');
+    },
+
+    _cloudDataTreeWhere() {
+      let result = []
+      let selected = this.selected
+      let parentField = this.parentField
+      if (parentField) {
+        result.push(`${parentField} == null || ${parentField} == ""`)
+      }
+      if (selected.length) {
+        for (var i = 0; i < selected.length - 1; i++) {
+          result.push(`${parentField} == '${selected[i].value}'`)
+        }
+      }
+
+      let where = []
+      if (this.where) {
+        where.push(`(${this.where})`)
+      }
+
+      if (result.length) {
+        where.push(`(${result.join(' || ')})`)
+      }
+
+      return where.join(' && ')
+    },
+
+    _cloudDataNodeWhere() {
+      let where = []
+      let selected = this.selected;
+      if (selected.length) {
+        where.push(`${this.parentField} == '${selected[selected.length - 1].value}'`);
+      }
+
+      where = where.join(' || ');
+
+      if (this.where) {
+        return `(${this.where}) && (${where})`
+      }
+
+      return where
+    },
+
+    _getWhereByForeignKey() {
+      let result = []
+      let whereField = this._getForeignKeyByField();
+      if (whereField) {
+        result.push(`${whereField} == '${this.dataValue}'`)
+      }
+
+      if (this.where) {
+        return `(${this.where}) && (${result.join(' || ')})`
+      }
+
+      return result.join(' || ')
+    },
+
+    _getForeignKeyByField() {
+      let fields = this.field.split(',');
+      let whereField = null;
+      for (let i = 0; i < fields.length; i++) {
+        const items = fields[i].split('as');
+        if (items.length < 2) {
+          continue;
+        }
+        if (items[1].trim() === 'value') {
+          whereField = items[0].trim();
+          break;
+        }
+      }
+      return whereField;
+    },
+
+    _updateBindData(node) {
+      const {
+        dataList,
+        hasNodes
+      } = this._filterData(this._treeData, this.selected)
+
+      let isleaf = this._stepSearh === false && !hasNodes
+
+      if (node) {
+        node.isleaf = isleaf
+      }
+
+      this.dataList = dataList
+      this.selectedIndex = dataList.length - 1
+
+      if (!isleaf && this.selected.length < dataList.length) {
+        this.selected.push({
+          value: null,
+          text: "请选择"
+        })
+      }
+
+      return {
+        isleaf,
+        hasNodes
+      }
+    },
+
+    _updateSelected() {
+      let dl = this.dataList
+      let sl = this.selected
+      let textField = this.map.text
+      let valueField = this.map.value
+      for (let i = 0; i < sl.length; i++) {
+        let value = sl[i].value
+        let dl2 = dl[i]
+        for (let j = 0; j < dl2.length; j++) {
+          let item2 = dl2[j]
+          if (item2[valueField] === value) {
+            sl[i].text = item2[textField]
+            break
+          }
+        }
+      }
+    },
+
+    _filterData(data, paths) {
+      let dataList = []
+      let hasNodes = true
+
+      dataList.push(data.filter((item) => {
+        return (item.parent_value === null || item.parent_value === undefined || item.parent_value === '')
+      }))
+      for (let i = 0; i < paths.length; i++) {
+        let value = paths[i].value
+        let nodes = data.filter((item) => {
+          return item.parent_value === value
+        })
+
+        if (nodes.length) {
+          dataList.push(nodes)
+        } else {
+          hasNodes = false
+        }
+      }
+
+      return {
+        dataList,
+        hasNodes
+      }
+    },
+
+    _extractTree(nodes, result, parent_value) {
+      let list = result || []
+      let valueField = this.map.value
+      for (let i = 0; i < nodes.length; i++) {
+        let node = nodes[i]
+
+        let child = {}
+        for (let key in node) {
+          if (key !== 'children') {
+            child[key] = node[key]
+          }
+        }
+        if (parent_value !== null && parent_value !== undefined && parent_value !== '') {
+          child.parent_value = parent_value
+        }
+        result.push(child)
+
+        let children = node.children
+        if (children) {
+          this._extractTree(children, result, node[valueField])
+        }
+      }
+    },
+
+    _extractTreePath(nodes, result) {
+      let list = result || []
+      for (let i = 0; i < nodes.length; i++) {
+        let node = nodes[i]
+
+        let child = {}
+        for (let key in node) {
+          if (key !== 'children') {
+            child[key] = node[key]
+          }
+        }
+        result.push(child)
+
+        let children = node.children
+        if (children) {
+          this._extractTreePath(children, result)
+        }
+      }
+    },
+
+    _findNodePath(key, nodes, path = []) {
+      let textField = this.map.text
+      let valueField = this.map.value
+      for (let i = 0; i < nodes.length; i++) {
+        let node = nodes[i]
+        let children = node.children
+        let text = node[textField]
+        let value = node[valueField]
+
+        path.push({
+          value,
+          text
+        })
+
+        if (value === key) {
+          return path
+        }
+
+        if (children) {
+          const p = this._findNodePath(key, children, path)
+          if (p.length) {
+            return p
+          }
+        }
+
+        path.pop()
+      }
+      return []
+    }
+  }
+}

+ 354 - 0
uni_modules/song-data-picker/components/song-data-pickerview/song-data-pickerview.vue

@@ -0,0 +1,354 @@
+<template>
+	<view class="uni-data-pickerview">
+		<scroll-view v-if="!isCloudDataList" class="selected-area" scroll-x="true">
+			<view class="selected-list">
+				<view class="selected-item" v-for="(item,index) in selected" :key="index" :class="{
+              'selected-item-active':index == selectedIndex
+            }" @click="handleSelect(index)">
+					<text>{{item.text || ''}}</text>
+				</view>
+			</view>
+		</scroll-view>
+		<view class="tab-c">
+			<scroll-view class="list" :scroll-y="true">
+				<view class="item" :class="{'is-disabled': !!item.disable}" v-for="(item, j) in dataList[selectedIndex]"
+					:key="j" @click="handleNodeClick(item, selectedIndex, j)">
+					<text class="item-text"
+						:class="selected.length > selectedIndex && item[map.value] == selected[selectedIndex].value ? 'checkColor' : ''">
+						{{item[map.text]}}</text>
+					<view class="check"
+						v-if="selected.length > selectedIndex && item[map.value] == selected[selectedIndex].value">
+					</view>
+				</view>
+			</scroll-view>
+
+			<view class="loading-cover" v-if="loading">
+				<uni-load-more class="load-more" :contentText="loadMore" status="loading"></uni-load-more>
+			</view>
+			<view class="error-message" v-if="errorMessage">
+				<text class="error-text">{{errorMessage}}</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+	import dataPicker from "./song-data-picker.js"
+
+	/**
+	 * DataPickerview
+	 * @description uni-data-pickerview
+	 * @tutorial https://ext.dcloud.net.cn/plugin?id=3796
+	 * @property {Array} localdata 本地数据,参考
+	 * @property {Boolean} step-searh = [true|false] 是否分布查询
+	 * @value true 启用分布查询,仅查询当前选中节点
+	 * @value false 关闭分布查询,一次查询出所有数据
+	 * @property {String|DBFieldString} self-field 分布查询当前字段名称
+	 * @property {String|DBFieldString} parent-field 分布查询父字段名称
+	 * @property {String|DBCollectionString} collection 表名
+	 * @property {String|DBFieldString} field 查询字段,多个字段用 `,` 分割
+	 * @property {String} orderby 排序字段及正序倒叙设置
+	 * @property {String|JQLString} where 查询条件
+	 */
+	export default {
+		name: 'UniDataPickerView',
+		emits: ['nodeclick', 'change', 'datachange', 'update:modelValue'],
+		mixins: [dataPicker],
+		props: {
+			managedMode: {
+				type: Boolean,
+				default: false
+			},
+			ellipsis: {
+				type: Boolean,
+				default: true
+			}
+		},
+		created() {
+			if (!this.managedMode) {
+				this.$nextTick(() => {
+					this.loadData();
+				})
+			}
+		},
+		methods: {
+			onPropsChange() {
+				this._treeData = [];
+				this.selectedIndex = 0;
+				this.$nextTick(() => {
+					this.loadData();
+				})
+			},
+			handleSelect(index) {
+				this.selectedIndex = index;
+			},
+			handleNodeClick(item, i, j) {
+				if (item.disable) {
+					return;
+				}
+
+				const node = this.dataList[i][j];
+				const text = node[this.map.text];
+				const value = node[this.map.value];
+
+				if (i < this.selected.length - 1) {
+					this.selected.splice(i, this.selected.length - i)
+					this.selected.push({
+						text,
+						value
+					})
+				} else if (i === this.selected.length - 1) {
+					this.selected.splice(i, 1, {
+						text,
+						value
+					})
+				}
+
+				if (node.isleaf) {
+					this.onSelectedChange(node, node.isleaf)
+					return
+				}
+
+				const {
+					isleaf,
+					hasNodes
+				} = this._updateBindData()
+
+				// 本地数据
+				if (this.isLocalData) {
+					this.onSelectedChange(node, (!hasNodes || isleaf))
+				} else if (this.isCloudDataList) { // Cloud 数据 (单列)
+					this.onSelectedChange(node, true)
+				} else if (this.isCloudDataTree) { // Cloud 数据 (树形)
+					if (isleaf) {
+						this.onSelectedChange(node, node.isleaf)
+					} else if (!hasNodes) { // 请求一次服务器以确定是否为叶子节点
+						this.loadCloudDataNode((data) => {
+							if (!data.length) {
+								node.isleaf = true
+							} else {
+								this._treeData.push(...data)
+								this._updateBindData(node)
+							}
+							this.onSelectedChange(node, node.isleaf)
+						})
+					}
+				}
+			},
+			updateData(data) {
+				this._treeData = data.treeData
+				this.selected = data.selected
+				if (!this._treeData.length) {
+					this.loadData()
+				} else {
+					//this.selected = data.selected
+					this._updateBindData()
+				}
+			},
+			onDataChange() {
+				this.$emit('datachange');
+			},
+			onSelectedChange(node, isleaf) {
+				if (isleaf) {
+					this._dispatchEvent()
+				}
+
+				if (node) {
+					this.$emit('nodeclick', node)
+				}
+			},
+			_dispatchEvent() {
+				this.$emit('change', this.selected.slice(0))
+			}
+		}
+	}
+</script>
+
+<style lang="scss">
+	.checkColor {
+		color: #FD910C !important;
+	}
+
+	::v-deep .selected-item-active {
+		border-bottom: 2px solid #FD910C !important;
+	}
+
+	::v-deep .check {
+		color: #FD910C !important;
+		border: none !important;
+		display: inline-block !important;
+		width: 10px !important;
+		height: 3px !important;
+		background: #FD910C !important;
+		line-height: 0 !important;
+		font-size: 0 !important;
+		vertical-align: middle !important;
+		-webkit-transform: rotate(45deg) !important;
+		margin-top: 24rpx;
+	}
+
+	::v-deep.check:after {
+		content: '/';
+		display: block;
+		width: 20px;
+		height: 3px;
+		background: #FD910C;
+		// margin-top: 24rpx;
+		-webkit-transform: rotate(-90deg) translateY(-50%) translateX(50%);
+	}
+
+	$uni-primary: #007aff !default;
+
+	.uni-data-pickerview {
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		overflow: hidden;
+		height: 100%;
+	}
+
+	.error-text {
+		color: #DD524D;
+	}
+
+	.loading-cover {
+		position: absolute;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		background-color: rgba(255, 255, 255, .5);
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: column;
+		align-items: center;
+		z-index: 1001;
+	}
+
+	.load-more {
+		/* #ifndef APP-NVUE */
+		margin: auto;
+		/* #endif */
+	}
+
+	.error-message {
+		background-color: #fff;
+		position: absolute;
+		left: 0;
+		top: 0;
+		right: 0;
+		bottom: 0;
+		padding: 15px;
+		opacity: .9;
+		z-index: 102;
+	}
+
+	/* #ifdef APP-NVUE */
+	.selected-area {
+		width: 750rpx;
+	}
+
+	/* #endif */
+
+	.selected-list {
+		/* #ifndef APP-NVUE */
+		display: flex;
+		flex-wrap: nowrap;
+		/* #endif */
+		flex-direction: row;
+		padding: 0 5px;
+		border-bottom: 1px solid #f8f8f8;
+	}
+
+	.selected-item {
+		margin-left: 10px;
+		margin-right: 10px;
+		padding: 12px 0;
+		text-align: center;
+		/* #ifndef APP-NVUE */
+		white-space: nowrap;
+		/* #endif */
+	}
+
+	.selected-item-text-overflow {
+		width: 168px;
+		/* fix nvue */
+		overflow: hidden;
+		/* #ifndef APP-NVUE */
+		width: 6em;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+		-o-text-overflow: ellipsis;
+		/* #endif */
+	}
+
+	.selected-item-active {
+		border-bottom: 2px solid $uni-primary;
+	}
+
+	.selected-item-text {
+		color: $uni-primary;
+	}
+
+	.tab-c {
+		position: relative;
+		flex: 1;
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		overflow: hidden;
+	}
+
+	.list {
+		flex: 1;
+	}
+
+	.item {
+		padding: 12px 15px;
+		/* border-bottom: 1px solid #f0f0f0; */
+		/* #ifndef APP-NVUE */
+		display: flex;
+		/* #endif */
+		flex-direction: row;
+		justify-content: space-between;
+	}
+
+	.is-disabled {
+		opacity: .5;
+	}
+
+	.item-text {
+		/* flex: 1; */
+		color: #333333;
+	}
+
+	.item-text-overflow {
+		width: 280px;
+		/* fix nvue */
+		overflow: hidden;
+		/* #ifndef APP-NVUE */
+		width: 20em;
+		white-space: nowrap;
+		text-overflow: ellipsis;
+		-o-text-overflow: ellipsis;
+		/* #endif */
+	}
+
+	.check {
+		margin-right: 5px;
+		border: 2px solid $uni-primary;
+		border-left: 0;
+		border-top: 0;
+		height: 12px;
+		width: 6px;
+		transform-origin: center;
+		/* #ifndef APP-NVUE */
+		transition: all 0.3s;
+		/* #endif */
+		transform: rotate(45deg);
+	}
+</style>

+ 90 - 0
uni_modules/song-data-picker/package.json

@@ -0,0 +1,90 @@
+{
+	"id": "song-data-picker",
+	"displayName": "基于uDataPicker(uni-data-picker)的带有搜索功能选择器",
+	"version": "1.0.4",
+	"description": "在官方组件uDataPicker(uni-data-picker:1.0.4)基础上添加搜索功能",
+	"keywords": [
+        "级联",
+        "搜索",
+        "picker",
+        "省市区",
+        "地址"
+    ],
+	"repository": "https://github.com/mofeimo110/uni-app-songcomponents",
+    "engines": {
+	},
+    "dcloudext": {
+        "sale": {
+			"regular": {
+				"price": "0.00"
+			},
+			"sourcecode": {
+				"price": "0.00"
+			}
+		},
+		"contact": {
+			"qq": ""
+		},
+		"declaration": {
+			"ads": "无",
+			"data": "无",
+			"permissions": "无"
+		},
+        "npmurl": "",
+        "type": "component-vue"
+	},
+	"uni_modules": {
+		"dependencies": [
+			"uni-load-more",
+			"uni-icons",
+			"uni-scss",
+			"uni-easyinput",
+			"uni-list"
+		],
+		"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",
+					"钉钉": "n",
+					"快手": "n",
+					"飞书": "n",
+                    "京东": "n"
+				},
+				"快应用": {
+					"华为": "y",
+					"联盟": "y"
+				}
+			}
+		}
+	}
+}

+ 147 - 0
uni_modules/song-data-picker/readme.md

@@ -0,0 +1,147 @@
+## DataPicker 级联选择
+
+> 关联组件:`uni-data-pickerview`、`uni-load-more`、`uni-list-item`、`uni-easyinput`、`uni-icons`、`uni-badge`。
+
+`<song-data-picker>` 是一个选择类[datacom 组件](https://uniapp.dcloud.net.cn/component/datacom),是在官方的`uni-data-picker(0.1.8)`的基础上进行的修改,添加了搜索功能。
+
+示例详见[github](https://github.com/mofeimo110/uni-app-songcomponents).
+
+此组件在使用中遇到任何问题,可在[github](https://github.com/mofeimo110/uni-app-songcomponents)下载源码和简易 demo 自行调试,也可以根据下列格式发送邮件到codersong@qq.com:
+
+```
+错误信息:
+使用场景:
+复现顺序:
+
+** 复现数据或者有现成的复现代码,请打包后添加附件发送
+```
+
+此处只列举新增的属性,原有的属性与事件可以在官方[uni-data-picker](https://ext.dcloud.net.cn/plugin?id=3796)中查看
+
+支持单列、和多列级联选择。列数没有限制,如果屏幕显示不全,顶部 tab 区域会左右滚动。
+
+`<song-data-picker>` 组件尤其适用于地址选择、分类选择等选择类。
+
+使用搜索功能时会进行所有层级的递归搜索。
+
+### 平台差异说明
+
+暂不支持在 nvue 页面中使用
+
+### 安装方式
+
+本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`componets`。
+
+## API
+
+### DataPicker Props
+
+> 目前所有的 props 都是在官方的 uni-data-picker(1.0.4)的基础上进行的添加,下边只标注新增的属性
+
+|     属性名      |                   类型                   |      可选值       | 默认值 |                                                                                                                   说明                                                                                                                   |
+| :-------------: | :--------------------------------------: | :---------------: | :----: | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------: |
+|   openSearch    |                 Boolean                  |    true/false     | false  |                                                                                                              是否显示搜索框                                                                                                              |
+|    searchFn     | Function(searchList,searchWord,callback) |     function      |        |                                自定义的搜索函数,需要返回一个数组。如果数据量不大可以不实现此属性,使用内置默认函数即可。如果数据量巨大或者是需要请求服务端等异步场景,建议重新实现此函数,详细见下方示例                                |
+| openInputSearch |              Boolean,Number              | true/false/number | false  | 是否开启输入框的 input 搜索事件,true 表示每次输入都会搜索,数字表示输入的文字长度达到几开始搜索,数字 0 与 false 效果一致。<br />需要注意的是如果候选数据巨大,尽量不要开启此功能,如果一定要开启,建议重写 searchFn 函数,并对函数防抖 |
+
+#### searchFn 示例
+
+```js
+<template>
+  <view>
+    <song-data-picker :localdata="items" popup-title="请选择班级" :openSearch="true" :searchFn="mySearchFn" @change="onchange" @nodeclick="onnodeclick"></song-data-picker>
+  </view>
+</template>
+{
+	methods:{
+		/**
+		 * searchList: 全量列表数据:{searchText:显示字符串,value:值}
+		 * searchWord: 输入的查询字符串
+		 */
+		// 实现一:通常情况下使用返回值
+		mySearchFn(searchList, searchWord) {
+			return searchList.filter(x => x.searchText.includes(searchWord));
+		},
+		// 实现二:在函数内部通过callback设置查询结果,可在异步场景下使用
+		mySearchFn2(searchList, searchWord, callback) {
+			const result = searchList.filter(x => x.searchText.includes(searchWord));
+			callback(result);
+		},
+		// 实现三:返回一个Promise,可在异步场景下使用
+		mySearchFn3(searchList, searchWord, callback) {
+			const result = searchList.filter(x => x.searchText.includes(searchWord));
+			return Promise.resolve(result);
+		},
+		// 实现四:返回一个Promise,可在异步场景下使用(实现三的变体)
+	 	async mySearchFn4(searchList, searchWord, callback) {
+			const result = searchList.filter(x => x.searchText.includes(searchWord));
+			return result;
+		},
+
+
+	}
+}
+```
+
+### DataPicker Events
+
+> 目前所有的 Events 都是在官方的 uni-data-picker(0.1.8)的基础上进行的添加,下边只标注新增的事件
+>
+> 无新增
+
+### 基本用法
+
+#### 本地数据
+
+```html
+<template>
+  <view>
+    <song-data-picker
+      :localdata="items"
+      popup-title="请选择班级"
+      :openSearch="true"
+      @change="onchange"
+      @nodeclick="onnodeclick"
+    ></song-data-picker>
+  </view>
+</template>
+
+<script>
+  export default {
+    data() {
+      return {
+        items: [
+          {
+            text: '一年级',
+            value: '1-0',
+            children: [
+              {
+                text: '1.1班',
+                value: '1-1',
+              },
+              {
+                text: '1.2班',
+                value: '1-2',
+              },
+            ],
+          },
+          {
+            text: '二年级',
+            value: '2-0',
+          },
+          {
+            text: '三年级',
+            value: '3-0',
+          },
+        ],
+      };
+    },
+    methods: {
+      onchange(e) {
+        const value = e.detail.value;
+      },
+      onnodeclick(node) {},
+    },
+  };
+</script>
+```

+ 21 - 0
uni_modules/uview-ui/LICENSE

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott