Jelajahi Sumber

UI框架升级

fanghuisheng 2 tahun lalu
induk
melakukan
1ec1bdfb34
91 mengubah file dengan 2235 tambahan dan 1618 penghapusan
  1. 46 0
      src/uni_modules/uview-plus/changelog.md
  2. 6 2
      src/uni_modules/uview-plus/components/u--input/u--input.vue
  3. 1 1
      src/uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue
  4. 1 1
      src/uni_modules/uview-plus/components/u-album/u-album.vue
  5. 1 1
      src/uni_modules/uview-plus/components/u-alert/u-alert.vue
  6. 1 1
      src/uni_modules/uview-plus/components/u-avatar-group/u-avatar-group.vue
  7. 1 1
      src/uni_modules/uview-plus/components/u-avatar/u-avatar.vue
  8. 1 1
      src/uni_modules/uview-plus/components/u-button/u-button.vue
  9. 1 1
      src/uni_modules/uview-plus/components/u-calendar/month.vue
  10. 4 4
      src/uni_modules/uview-plus/components/u-calendar/u-calendar.vue
  11. 14 5
      src/uni_modules/uview-plus/components/u-checkbox-group/props.js
  12. 39 11
      src/uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue
  13. 15 2
      src/uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue
  14. 1 1
      src/uni_modules/uview-plus/components/u-circle-progress/u-circle-progress.vue
  15. 1 1
      src/uni_modules/uview-plus/components/u-code-input/u-code-input.vue
  16. 1 1
      src/uni_modules/uview-plus/components/u-code/u-code.vue
  17. 1 1
      src/uni_modules/uview-plus/components/u-col/u-col.vue
  18. 2 2
      src/uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue
  19. 1 1
      src/uni_modules/uview-plus/components/u-collapse/u-collapse.vue
  20. 1 1
      src/uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue
  21. 1 1
      src/uni_modules/uview-plus/components/u-count-to/u-count-to.vue
  22. 2 2
      src/uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue
  23. 1 1
      src/uni_modules/uview-plus/components/u-divider/u-divider.vue
  24. 7 7
      src/uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue
  25. 1 1
      src/uni_modules/uview-plus/components/u-empty/u-empty.vue
  26. 1 1
      src/uni_modules/uview-plus/components/u-form-item/u-form-item.vue
  27. 1 1
      src/uni_modules/uview-plus/components/u-form/u-form.vue
  28. 1 1
      src/uni_modules/uview-plus/components/u-gap/u-gap.vue
  29. 6 1
      src/uni_modules/uview-plus/components/u-grid-item/u-grid-item.vue
  30. 1 1
      src/uni_modules/uview-plus/components/u-grid/u-grid.vue
  31. 1 1
      src/uni_modules/uview-plus/components/u-icon/icons.js
  32. 1 1
      src/uni_modules/uview-plus/components/u-icon/u-icon.vue
  33. 8 4
      src/uni_modules/uview-plus/components/u-input/props.js
  34. 1 1
      src/uni_modules/uview-plus/components/u-keyboard/u-keyboard.vue
  35. 1 1
      src/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue
  36. 1 1
      src/uni_modules/uview-plus/components/u-line/u-line.vue
  37. 1 1
      src/uni_modules/uview-plus/components/u-link/u-link.vue
  38. 1 1
      src/uni_modules/uview-plus/components/u-list-item/u-list-item.vue
  39. 1 1
      src/uni_modules/uview-plus/components/u-list/u-list.vue
  40. 1 1
      src/uni_modules/uview-plus/components/u-loading-icon/u-loading-icon.vue
  41. 1 1
      src/uni_modules/uview-plus/components/u-loading-page/u-loading-page.vue
  42. 1 1
      src/uni_modules/uview-plus/components/u-loadmore/u-loadmore.vue
  43. 1 1
      src/uni_modules/uview-plus/components/u-modal/u-modal.vue
  44. 1 1
      src/uni_modules/uview-plus/components/u-navbar/u-navbar.vue
  45. 1 1
      src/uni_modules/uview-plus/components/u-no-network/u-no-network.vue
  46. 1 1
      src/uni_modules/uview-plus/components/u-notice-bar/u-notice-bar.vue
  47. 1 1
      src/uni_modules/uview-plus/components/u-number-box/u-number-box.vue
  48. 1 1
      src/uni_modules/uview-plus/components/u-overlay/u-overlay.vue
  49. 192 115
      src/uni_modules/uview-plus/components/u-parse/node/node.vue
  50. 1129 871
      src/uni_modules/uview-plus/components/u-parse/parser.js
  51. 4 3
      src/uni_modules/uview-plus/components/u-parse/props.js
  52. 461 328
      src/uni_modules/uview-plus/components/u-parse/u-parse.vue
  53. 3 0
      src/uni_modules/uview-plus/components/u-picker/u-picker.vue
  54. 1 1
      src/uni_modules/uview-plus/components/u-popup/u-popup.vue
  55. 5 2
      src/uni_modules/uview-plus/components/u-radio-group/u-radio-group.vue
  56. 5 0
      src/uni_modules/uview-plus/components/u-radio/props.js
  57. 4 2
      src/uni_modules/uview-plus/components/u-radio/u-radio.vue
  58. 6 1
      src/uni_modules/uview-plus/components/u-rate/u-rate.vue
  59. 1 1
      src/uni_modules/uview-plus/components/u-read-more/u-read-more.vue
  60. 1 1
      src/uni_modules/uview-plus/components/u-row-notice/u-row-notice.vue
  61. 1 1
      src/uni_modules/uview-plus/components/u-row/u-row.vue
  62. 1 1
      src/uni_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue
  63. 1 1
      src/uni_modules/uview-plus/components/u-scroll-list/u-scroll-list.vue
  64. 1 1
      src/uni_modules/uview-plus/components/u-search/u-search.vue
  65. 2 2
      src/uni_modules/uview-plus/components/u-skeleton/u-skeleton.vue
  66. 1 1
      src/uni_modules/uview-plus/components/u-sticky/u-sticky.vue
  67. 1 1
      src/uni_modules/uview-plus/components/u-subsection/u-subsection.vue
  68. 2 1
      src/uni_modules/uview-plus/components/u-swipe-action-item/u-swipe-action-item.vue
  69. 1 1
      src/uni_modules/uview-plus/components/u-swipe-action/u-swipe-action.vue
  70. 1 1
      src/uni_modules/uview-plus/components/u-swiper-indicator/u-swiper-indicator.vue
  71. 1 1
      src/uni_modules/uview-plus/components/u-swiper/u-swiper.vue
  72. 13 3
      src/uni_modules/uview-plus/components/u-switch/u-switch.vue
  73. 5 1
      src/uni_modules/uview-plus/components/u-tabbar-item/u-tabbar-item.vue
  74. 1 1
      src/uni_modules/uview-plus/components/u-tabbar/u-tabbar.vue
  75. 1 1
      src/uni_modules/uview-plus/components/u-table/u-table.vue
  76. 1 1
      src/uni_modules/uview-plus/components/u-tabs-item/u-tabs-item.vue
  77. 2 3
      src/uni_modules/uview-plus/components/u-tabs/u-tabs.vue
  78. 1 1
      src/uni_modules/uview-plus/components/u-tag/u-tag.vue
  79. 1 1
      src/uni_modules/uview-plus/components/u-text/u-text.vue
  80. 5 3
      src/uni_modules/uview-plus/components/u-textarea/u-textarea.vue
  81. 1 1
      src/uni_modules/uview-plus/components/u-toast/u-toast.vue
  82. 1 1
      src/uni_modules/uview-plus/components/u-toolbar/u-toolbar.vue
  83. 2 2
      src/uni_modules/uview-plus/components/u-tooltip/u-tooltip.vue
  84. 3 1
      src/uni_modules/uview-plus/components/u-upload/u-upload.vue
  85. 1 1
      src/uni_modules/uview-plus/index.js
  86. 176 176
      src/uni_modules/uview-plus/libs/config/props.js
  87. 1 1
      src/uni_modules/uview-plus/libs/config/props/textarea.js
  88. 6 3
      src/uni_modules/uview-plus/libs/mixin/mixin.js
  89. 0 0
      src/uni_modules/uview-plus/libs/util/dayjs.min.js
  90. 1 1
      src/uni_modules/uview-plus/libs/util/route.js
  91. 2 2
      src/uni_modules/uview-plus/package.json

+ 46 - 0
src/uni_modules/uview-plus/changelog.md

@@ -1,3 +1,49 @@
+## 3.1.26(2023-02-04)
+修复安卓Multiple conflicting contents for sourcemap
+## 3.1.25(2023-02-04)
+修复dayjs引入问题导致小程序编译报错
+## 3.1.24(2023-01-17)
+修复dayjs报错does not provide an export named 'default'
+## 3.1.23(2023-01-15)
+修复 build:h5 cell 和 notice-bar 点击失效的问题 
+修复小程序端报 alert 和 upload 重复定义的bug
+感谢@dodu2014
+## 3.1.22(2023-01-14)
+增加transpileDependencies检测
+## 3.1.21(2023-01-14)
+修复路由配置合并问题
+## 3.1.20(2022-12-16)
+修复拼写
+## 3.1.19(2022-11-16)
+修复u-rate的v-model绑定值不生效#48
+修复u-grid-item click事件会执行两次 #40
+修复u--input组建Property "value" was accessed during render but is not defined on instance
+修复textarea组建confirmType报错
+## 3.1.18(2022-10-23)
+新增页面模板
+## 3.1.17(2022-10-23)
+演示项目增加国家化支持及多个页面模板
+## 3.1.16(2022-10-21)
+修复u-switch、u-picker等
+## 3.1.15(2022-10-13)
+修复:数据更新后,上传组件的视图没有更新
+复选框 超出不换行
+单选框 超出不换行
+## 3.1.14(2022-10-11)
+swipe-action-item组件增加emits定义
+## 3.1.13(2022-09-19)
+修复富文本解析
+## 3.1.12(2022-09-15)
+check-box组件清空数组时无法更新视图的问题
+## 3.1.11(2022-09-14)
+修复u-checkbox-group等
+## 3.1.10(2022-09-14)
+修复u-checkbox-box双向绑定
+## 3.1.9(2022-09-08)
+紧急修复缺失endif
+## 3.1.8(2022-09-07)
+修复u-checkbox-group双向绑定
+修复picker缺少emits定义
 ## 3.1.7(2022-08-28)
 修复布局在微信小程序失效
 ## 3.1.6(2022-08-25)

+ 6 - 2
src/uni_modules/uview-plus/components/u--input/u--input.vue

@@ -1,7 +1,13 @@
 <template>
 	<uvInput 
+		<!-- #ifdef VUE2 -->
 		:value="value"
+		@input="e => $emit('input', e)"
+		<!-- #endif -->
+		<!-- #ifdef VUE3 -->
 		:modelValue="modelValue"
+		@update:modelValue="e => $emit('update:modelValue', e)"
+		<!-- #endif -->
 		:type="type"
 		:fixed="fixed"
 		:disabled="disabled"
@@ -37,8 +43,6 @@
 		:customStyle="customStyle"
 		:formatter="formatter"
 		:ignoreCompositionEvent="ignoreCompositionEvent"
-		@input="e => $emit('input', e)"
-		@update:modelValue="e => $emit('update:modelValue', e)"
 	>
 		<!-- #ifdef MP -->
 		<slot name="prefix"></slot>

+ 1 - 1
src/uni_modules/uview-plus/components/u-action-sheet/u-action-sheet.vue

@@ -114,7 +114,7 @@
 	/**
 	 * ActionSheet 操作菜单
 	 * @description 本组件用于从底部弹出一个操作菜单,供用户选择并返回结果。本组件功能类似于uni的uni.showActionSheetAPI,配置更加灵活,所有平台都表现一致。
-	 * @tutorial https://www.uviewui.com/components/actionSheet.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/actionSheet.html
 	 * 
 	 * @property {Boolean}			show				操作菜单是否展示 (默认 false )
 	 * @property {String}			title				操作菜单标题

+ 1 - 1
src/uni_modules/uview-plus/components/u-album/u-album.vue

@@ -64,7 +64,7 @@ const dom = uni.requireNativePlugin('dom')
 /**
  * Album 相册
  * @description 本组件提供一个类似相册的功能,让开发者开发起来更加得心应手。减少重复的模板代码
- * @tutorial https://www.uviewui.com/components/album.html
+ * @tutorial https://ijry.github.io/uview-plus/components/album.html
  *
  * @property {Array}           urls             图片地址列表 Array<String>|Array<Object>形式
  * @property {String}          keyName          指定从数组的对象元素中读取哪个属性作为图片地址

+ 1 - 1
src/uni_modules/uview-plus/components/u-alert/u-alert.vue

@@ -66,7 +66,7 @@
 	/**
 	 * Alert  警告提示
 	 * @description 警告提示,展现需要关注的信息。
-	 * @tutorial https://www.uviewui.com/components/alertTips.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/alertTips.html
 	 * 
 	 * @property {String}			title       显示的文字 
 	 * @property {String}			type        使用预设的颜色  (默认 'warning' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-avatar-group/u-avatar-group.vue

@@ -38,7 +38,7 @@
 	/**
 	 * AvatarGroup  头像组
 	 * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
-	 * @tutorial https://www.uviewui.com/components/avatar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/avatar.html
 	 * 
 	 * @property {Array}           urls     头像图片组 (默认 [] )
 	 * @property {String | Number} maxCount 最多展示的头像数量 ( 默认 5 )

+ 1 - 1
src/uni_modules/uview-plus/components/u-avatar/u-avatar.vue

@@ -62,7 +62,7 @@
 	/**
 	 * Avatar  头像
 	 * @description 本组件一般用于展示头像的地方,如个人中心,或者评论列表页的用户头像展示等场所。
-	 * @tutorial https://www.uviewui.com/components/avatar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/avatar.html
 	 *
 	 * @property {String}			src				头像路径,如加载失败,将会显示默认头像(不能为相对路径)
 	 * @property {String}			shape			头像形状  ( circle (默认) | square)

+ 1 - 1
src/uni_modules/uview-plus/components/u-button/u-button.vue

@@ -117,7 +117,7 @@ import props from "./props.js";
 /**
  * button 按钮
  * @description Button 按钮
- * @tutorial https://www.uviewui.com/components/button.html
+ * @tutorial https://ijry.github.io/uview-plus/components/button.html
  *
  * @property {Boolean}			hairline				是否显示按钮的细边框 (默认 true )
  * @property {String}			type					按钮的预置样式,info,primary,error,warning,success (默认 'info' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-calendar/month.vue

@@ -333,7 +333,7 @@
 			// 获取每个月份区域的尺寸
 			getMonthRectByPromise(el) {
 				// #ifndef APP-NVUE
-				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://ijry.github.io/uview-plus/js/getRect.html
 				// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
 				return new Promise(resolve => {
 					this.$uGetRect(`.${el}`).then(size => {

+ 4 - 4
src/uni_modules/uview-plus/components/u-calendar/u-calendar.vue

@@ -68,15 +68,15 @@ import uHeader from './header.vue'
 import uMonth from './month.vue'
 import props from './props.js'
 import util from './util.js'
-// import dayjs from '../../libs/util/dayjs.js'
-import dayjs from 'dayjs'
-import Calendar from '../../libs/util/calendar.js'
+import dayjs from '../../libs/util/dayjs.min.js'
+// import dayjs from 'dayjs'
+// import Calendar from '../../libs/util/calendar.js'
 import mpMixin from '../../libs/mixin/mpMixin.js'
 import mixin from '../../libs/mixin/mixin.js'
 /**
  * Calendar 日历
  * @description  此组件用于单个选择日期,范围选择日期等,日历被包裹在底部弹起的容器中.
- * @tutorial https://www.uviewui.com/components/calendar.html
+ * @tutorial https://ijry.github.io/uview-plus/components/calendar.html
  *
  * @property {String}				title				标题内容 (默认 日期选择 )
  * @property {Boolean}				showTitle			是否显示标题  (默认 true )

+ 14 - 5
src/uni_modules/uview-plus/components/u-checkbox-group/props.js

@@ -6,11 +6,20 @@ export default {
             type: String,
             default: defprops.checkboxGroup.name
         },
-        // 绑定的值
-        value: {
-            type: Array,
-            default: defprops.checkboxGroup.value
-        },
+		// #ifdef VUE3
+		// 绑定的值
+		modelValue: {
+		    type: Array,
+		    default: defprops.checkboxGroup.value
+		},
+		// #endif
+		// #ifdef VUE2
+		// 绑定的值
+		value: {
+		    type: Array,
+		    default: defprops.checkboxGroup.value
+		},
+		// #endif
         // 形状,circle-圆形,square-方形
         shape: {
             type: String,

+ 39 - 11
src/uni_modules/uview-plus/components/u-checkbox-group/u-checkbox-group.vue

@@ -14,7 +14,7 @@
 	/**
 	 * checkboxGroup 复选框组
 	 * @description 复选框组件一般用于需要多个选择的场景,该组件功能完整,使用方便
-	 * @tutorial https://www.uviewui.com/components/checkbox.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/checkbox.html
 	 * @property {String}			name			标识符 
 	 * @property {Array}			value			绑定的值
 	 * @property {String}			shape			形状,circle-圆形,square-方形 (默认 'square' )
@@ -42,9 +42,23 @@
 			// 所以需要手动通知子组件,这里返回一个parentData变量,供watch监听,在其中去通知每一个子组件重新从父组件(u-checkbox-group)
 			// 拉取父组件新的变化后的参数
 			parentData() {
-				return [this.value, this.disabled, this.inactiveColor, this.activeColor, this.size, this.labelDisabled, this.shape,
-					this.iconSize, this.borderBottom, this.placement
-				]
+			  return [
+				// #ifdef VUE2
+				this.value,
+				// #endif
+				// #ifdef VUE3
+				this.modelValue,
+				// #endif
+				this.disabled,
+				this.inactiveColor,
+				this.activeColor,
+				this.size,
+				this.labelDisabled,
+				this.shape,
+				this.iconSize,
+				this.borderBottom,
+				this.placement,
+			  ];
 			},
 			bemClass() {
 				// this.bem为一个computed变量,在mixin中
@@ -53,13 +67,16 @@
 		},
 		watch: {
 			// 当父组件需要子组件需要共享的参数发生了变化,手动通知子组件
-			parentData() {
+			parentData: {
+			  handler() {
 				if (this.children.length) {
-					this.children.map(child => {
-						// 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
-						typeof(child.init) === 'function' && child.init()
-					})
+				  this.children.map((child) => {
+					// 判断子组件(u-checkbox)如果有init方法的话,就就执行(执行的结果是子组件重新从父组件拉取了最新的值)
+					typeof child.init === "function" && child.init();
+				  });
 				}
+			  },
+			  deep: true,
 			},
 		},
 		data() {
@@ -70,6 +87,9 @@
 		created() {
 			this.children = []
 		},
+		// #ifdef VUE3
+		emits: ['update:modelValue', 'change'],
+		// #endif
 		methods: {
 			// 将其他的checkbox设置为未选中的状态
 			unCheckedOther(childInstance) {
@@ -83,7 +103,12 @@
 				// 发出事件
 				this.$emit('change', values)
 				// 修改通过v-model绑定的值
-				this.$emit('input', values)
+				// #ifdef VUE3
+				this.$emit("update:modelValue", values);
+				// #endif
+				// #ifdef VUE2
+				this.$emit("input", values);
+				// #endif
 			},
 		}
 	}
@@ -95,7 +120,10 @@
 	.u-checkbox-group {
 
 		&--row {
-			@include flex;
+			/* #ifndef APP-NVUE */
+			display: flex;
+			/* #endif */
+			flex-flow: row wrap;
 		}
 
 		&--column {

+ 15 - 2
src/uni_modules/uview-plus/components/u-checkbox/u-checkbox.vue

@@ -73,7 +73,12 @@
 					activeColor: null,
 					inactiveColor: null,
 					size: 18,
+					// #ifdef VUE2
 					value: null,
+					// #endif
+					// #ifdef VUE3
+					modelValue: null,
+					// #endif
 					iconColor: null,
 					placement: 'row',
 					borderBottom: false,
@@ -183,12 +188,18 @@
 				if (!this.parent) {
 					uni.$u.error('u-checkbox必须搭配u-checkbox-group组件使用')
 				}
+				// #ifdef VUE2
+				const value = this.parentData.value
+				// #endif
+				// #ifdef VUE3
+				const value = this.parentData.modelValue
+				// #endif
 				// 设置初始化时,是否默认选中的状态,父组件u-checkbox-group的value可能是array,所以额外判断
 				if (this.checked) {
 					this.isChecked = true
-				} else if (uni.$u.test.array(this.parentData.value)) {
+				} else if (uni.$u.test.array(value)) {
 					// 查找数组是是否存在this.name元素值
-					this.isChecked = this.parentData.value.some(item => {
+					this.isChecked = value.some(item => {
 						return item === this.name
 					})
 				}
@@ -268,6 +279,8 @@
 		overflow: hidden;
 		flex-direction: row;
 		align-items: center;
+		margin-bottom: 5px;
+		margin-top: 5px;
 
 		&-label--left {
 			flex-direction: row

+ 1 - 1
src/uni_modules/uview-plus/components/u-circle-progress/u-circle-progress.vue

@@ -36,7 +36,7 @@
 	/**
 	 * CircleProgress 圆形进度条 TODO: 待完善 
 	 * @description 展示操作或任务的当前进度,比如上传文件,是一个圆形的进度环。
-	 * @tutorial https://www.uviewui.com/components/circleProgress.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/circleProgress.html
 	 * @property {String | Number}	percentage	圆环进度百分比值,为数值类型,0-100 (默认 30 )
 	 * @example
 	 */

+ 1 - 1
src/uni_modules/uview-plus/components/u-code-input/u-code-input.vue

@@ -52,7 +52,7 @@
 	/**
 	 * CodeInput 验证码输入
 	 * @description 该组件一般用于验证用户短信验证码的场景,也可以结合uView的键盘组件使用
-	 * @tutorial https://www.uviewui.com/components/codeInput.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/codeInput.html
 	 * @property {String | Number}	maxlength			最大输入长度 (默认 6 )
 	 * @property {Boolean}			dot					是否用圆点填充 (默认 false )
 	 * @property {String}			mode				显示模式,box-盒子模式,line-底部横线模式 (默认 'box' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-code/u-code.vue

@@ -11,7 +11,7 @@
 	/**
 	 * Code 验证码输入框
 	 * @description 考虑到用户实际发送验证码的场景,可能是一个按钮,也可能是一段文字,提示语各有不同,所以本组件 不提供界面显示,只提供提示语,由用户将提示语嵌入到具体的场景
-	 * @tutorial https://www.uviewui.com/components/code.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/code.html
 	 * @property {String | Number}	seconds			倒计时所需的秒数(默认 60 )
 	 * @property {String}			startText		开始前的提示语,见官网说明(默认 '获取验证码' )
 	 * @property {String}			changeText		倒计时期间的提示语,必须带有字母"x",见官网说明(默认 'X秒重新获取' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-col/u-col.vue

@@ -19,7 +19,7 @@
 	/**
 	 * CodeInput 栅格系统的列 
 	 * @description 该组件一般用于Layout 布局 通过基础的 12 分栏,迅速简便地创建布局
-	 * @tutorial https://www.uviewui.com/components/Layout.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/Layout.html
 	 * @property {String | Number}	span		栅格占据的列数,总12等份 (默认 12 ) 
 	 * @property {String | Number}	offset		分栏左边偏移,计算方式与span相同 (默认 0 ) 
 	 * @property {String}			justify		水平排列方式,可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)  (默认 'start' ) 

+ 2 - 2
src/uni_modules/uview-plus/components/u-collapse-item/u-collapse-item.vue

@@ -55,7 +55,7 @@
 	/**
 	 * collapseItem 折叠面板Item
 	 * @description 通过折叠面板收纳内容区域(搭配u-collapse使用)
-	 * @tutorial https://www.uviewui.com/components/collapse.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/collapse.html
 	 * @property {String}			title 		标题
 	 * @property {String}			value 		标题右侧内容
 	 * @property {String}			label 		标题下方的描述信息
@@ -184,7 +184,7 @@
 			// 查询内容高度
 			queryRect() {
 				// #ifndef APP-NVUE
-				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://ijry.github.io/uview-plus/js/getRect.html
 				// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
 				return new Promise(resolve => {
 					this.$uGetRect(`#${this.elId}`).then(size => {

+ 1 - 1
src/uni_modules/uview-plus/components/u-collapse/u-collapse.vue

@@ -12,7 +12,7 @@
 	/**
 	 * collapse 折叠面板 
 	 * @description 通过折叠面板收纳内容区域
-	 * @tutorial https://www.uviewui.com/components/collapse.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/collapse.html
 	 * @property {String | Number | Array}	value		当前展开面板的name,非手风琴模式:[<string | number>],手风琴模式:string | number
 	 * @property {Boolean}					accordion	是否手风琴模式( 默认 false )
 	 * @property {Boolean}					border		是否显示外边框 ( 默认 true )

+ 1 - 1
src/uni_modules/uview-plus/components/u-column-notice/u-column-notice.vue

@@ -63,7 +63,7 @@
 	/**
 	 * ColumnNotice 滚动通知中的垂直滚动 内部组件
 	 * @description 该组件用于滚动通告场景,是其中的垂直滚动方式
-	 * @tutorial https://www.uviewui.com/components/noticeBar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/noticeBar.html
 	 * @property {Array}			text 			显示的内容,字符串
 	 * @property {String}			icon 			是否显示左侧的音量图标 ( 默认 'volume' )
 	 * @property {String}			mode 			通告模式,link-显示右箭头,closable-显示右侧关闭图标

+ 1 - 1
src/uni_modules/uview-plus/components/u-count-to/u-count-to.vue

@@ -16,7 +16,7 @@
 /**
  * countTo 数字滚动
  * @description 该组件一般用于需要滚动数字到某一个值的场景,目标要求是一个递增的值。
- * @tutorial https://www.uviewui.com/components/countTo.html
+ * @tutorial https://ijry.github.io/uview-plus/components/countTo.html
  * @property {String | Number}	startVal	开始的数值,默认从0增长到某一个数(默认 0 )
  * @property {String | Number}	endVal		要滚动的目标数值,必须 (默认 0 )
  * @property {String | Number}	duration	滚动到目标数值的动画持续时间,单位为毫秒(ms) (默认 2000 )

+ 2 - 2
src/uni_modules/uview-plus/components/u-datetime-picker/u-datetime-picker.vue

@@ -39,7 +39,7 @@
 	/**
 	 * DatetimePicker 时间日期选择器
 	 * @description 此选择器用于时间日期
-	 * @tutorial https://www.uviewui.com/components/datetimePicker.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/datetimePicker.html
 	 * @property {Boolean}			show				用于控制选择器的弹出与收起 ( 默认 false )
 	 * @property {Boolean}			showToolbar			是否显示顶部的操作栏  ( 默认 true )
 	 * @property {String | Number}	value				绑定值
@@ -98,7 +98,7 @@
 			this.init()
 		},
 		// #ifdef VUE3
-		emits: ['close', 'canel', 'confirm', 'change'],
+		emits: ['close', 'cancel', 'confirm', 'change'],
 		// #endif
 		methods: {
 			init() {

+ 1 - 1
src/uni_modules/uview-plus/components/u-divider/u-divider.vue

@@ -35,7 +35,7 @@
 	/**
 	 * divider 分割线
 	 * @description 区隔内容的分割线,一般用于页面底部"没有更多"的提示。
-	 * @tutorial https://www.uviewui.com/components/divider.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/divider.html
 	 * @property {Boolean}			dashed			是否虚线 (默认 false )
 	 * @property {Boolean}			hairline		是否细线 (默认  true )
 	 * @property {Boolean}			dot				是否以点替代文字,优先于text字段起作用 (默认 false )

+ 7 - 7
src/uni_modules/uview-plus/components/u-dropdown/u-dropdown.vue

@@ -53,7 +53,7 @@
 		mixins: [mixin, props],
 		data() {
 			return {
-				// 菜单数组
+				// 锟剿碉拷锟斤拷锟斤拷
 				menuList: [],
 				current: 0
 			}
@@ -62,7 +62,7 @@
 		
 		},
 		created() {
-			// 引用所有子组件(u-dropdown-item)的this,不能在data中声明变量,否则在微信小程序会造成循环引用而报错
+			// 锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟�(u-dropdown-item)锟斤拷this锟斤拷锟斤拷锟斤拷锟斤拷data锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷微锟斤拷小锟斤拷锟斤拷锟斤拷锟斤拷循锟斤拷锟斤拷锟矫讹拷锟斤拷锟斤拷
 			this.children = [];
 		},
 		methods: {
@@ -80,11 +80,11 @@
 					}
 				})
 			},
-			// 获取标签的尺寸位置
+			// 锟斤拷取锟斤拷签锟侥尺达拷位锟斤拷
 			queryRect(el) {
 				// #ifndef APP-NVUE
-				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
-				// 组件内部一般用this.$uGetRect,对外的为this.$u.getRect,二者功能一致,名称不
+				// $uGetRect为uView锟皆达拷锟侥节碉拷锟窖�拷蚧�锟斤拷锟絟ttps://ijry.github.io/uview-plus/.uviewui.com/js/getRect.html
+				// 锟斤拷锟斤拷诓锟揭伙拷锟斤拷锟絫his.$uGetRect锟斤拷锟斤拷锟斤拷锟轿猼his.$u.getRect锟斤拷锟斤拷锟竭癸拷锟斤拷一锟铰o拷锟斤拷锟狡诧拷
 				return new Promise(resolve => {
 					this.$uGetRect(`.${el}`).then(size => {
 						resolve(size)
@@ -93,8 +93,8 @@
 				// #endif
 			
 				// #ifdef APP-NVUE 
-				// nvue下,使用dom模块查询元素高度
-				// 返回一个promise,让调用此方法的主体能使用then回调
+				// nvue锟铰o拷使锟斤拷dom模锟斤拷锟窖��拷馗叨锟�
+				// 锟斤拷锟斤拷一锟斤拷promise锟斤拷锟矫碉拷锟矫此凤拷锟斤拷锟斤拷锟斤拷锟斤拷锟斤拷使锟斤拷then锟截碉拷
 				return new Promise(resolve => {
 					dom.getComponentRect(this.$refs[el], res => {
 						resolve(res.size)

+ 1 - 1
src/uni_modules/uview-plus/components/u-empty/u-empty.vue

@@ -37,7 +37,7 @@
 	/**
 	 * empty 内容为空
 	 * @description 该组件用于需要加载内容,但是加载的第一页数据就为空,提示一个"没有内容"的场景, 我们精心挑选了十几个场景的图标,方便您使用。
-	 * @tutorial https://www.uviewui.com/components/empty.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/empty.html
 	 * @property {String}			icon		内置图标名称,或图片路径,建议绝对路径
 	 * @property {String}			text		提示文字
 	 * @property {String}			textColor	文字颜色 (默认 '#c0c4cc' )

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

@@ -81,7 +81,7 @@
 	/**
 	 * Form 表单
 	 * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
-	 * @tutorial https://www.uviewui.com/components/form.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/form.html
 	 * @property {String}			label			input的label提示语
 	 * @property {String}			prop			绑定的值
 	 * @property {String | Boolean}	borderBottom	是否显示表单域的下划线边框

+ 1 - 1
src/uni_modules/uview-plus/components/u-form/u-form.vue

@@ -14,7 +14,7 @@
 	/**
 	 * Form 表单
 	 * @description 此组件一般用于表单场景,可以配置Input输入框,Select弹出框,进行表单验证等。
-	 * @tutorial https://www.uviewui.com/components/form.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/form.html
 	 * @property {Object}						model			当前form的需要验证字段的集合
 	 * @property {Object | Function | Array}	rules			验证规则
 	 * @property {String}						errorType		错误的提示方式,见上方说明 ( 默认 message )

+ 1 - 1
src/uni_modules/uview-plus/components/u-gap/u-gap.vue

@@ -9,7 +9,7 @@
 	/**
 	 * gap 间隔槽
 	 * @description 该组件一般用于内容块之间的用一个灰色块隔开的场景,方便用户风格统一,减少工作量
-	 * @tutorial https://www.uviewui.com/components/gap.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/gap.html
 	 * @property {String}			bgColor			背景颜色 (默认 'transparent' )
 	 * @property {String | Number}	height			分割槽高度,单位px (默认 20 )
 	 * @property {String | Number}	marginTop		与前一个组件的距离,单位px( 默认 0 )

+ 6 - 1
src/uni_modules/uview-plus/components/u-grid-item/u-grid-item.vue

@@ -31,7 +31,7 @@
 	/**
 	 * gridItem 提示
 	 * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。搭配u-grid使用
-	 * @tutorial https://www.uviewui.com/components/grid.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/grid.html
 	 * @property {String | Number}	name		宫格的name ( 默认 null )
 	 * @property {String}			bgColor		宫格的背景颜色 (默认 'transparent' )
 	 * @property {Object}			customStyle	自定义样式,对象形式
@@ -56,6 +56,11 @@
 		mounted() {
 			this.init()
 		},
+		emits: ['click'],
+		//  微信小程序中 options 选项
+		options: {
+		    virtualHost: true //将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等
+		},
 		computed: {
 			// #ifndef APP-NVUE
 			// vue下放到computed中,否则会因为延时造成闪烁

+ 1 - 1
src/uni_modules/uview-plus/components/u-grid/u-grid.vue

@@ -15,7 +15,7 @@
 	/**
 	 * grid 宫格布局
 	 * @description 宫格组件一般用于同时展示多个同类项目的场景,可以给宫格的项目设置徽标组件(badge),或者图标等,也可以扩展为左右滑动的轮播形式。
-	 * @tutorial https://www.uviewui.com/components/grid.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/grid.html
 	 * @property {String | Number}	col			宫格的列数(默认 3 )
 	 * @property {Boolean}			border		是否显示宫格的边框(默认 false )
 	 * @property {String}			align		宫格对齐方式,表现为数量少的时候,靠左,居中,还是靠右 (默认 'left' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-icon/icons.js

@@ -210,5 +210,5 @@ export default {
     'uicon-man-delete': '\ue61a',
     'uicon-man-delete-fill': '\ue66a',
     'uicon-zh': '\ue70a',
-    'uicon-en': '\ue692',
+    'uicon-en': '\ue692'
 }

+ 1 - 1
src/uni_modules/uview-plus/components/u-icon/u-icon.vue

@@ -56,7 +56,7 @@
 	/**
 	 * icon 图标
 	 * @description 基于字体的图标集,包含了大多数常见场景的图标。
-	 * @tutorial https://www.uviewui.com/components/icon.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/icon.html
 	 * @property {String}			name			图标名称,见示例图标集
 	 * @property {String}			color			图标颜色,可接受主题色 (默认 color['u-content-color'] )
 	 * @property {String | Number}	size			图标字体大小,单位px (默认 '16px' )

+ 8 - 4
src/uni_modules/uview-plus/components/u-input/props.js

@@ -1,16 +1,20 @@
 import defprops from '../../libs/config/props';
 export default {
 	props: {
-		// 输入的值
-		value: {
+		// #ifdef VUE3
+		// 绑定的值
+		modelValue: {
 			type: [String, Number],
 			default: defprops.input.value
 		},
-		modelValue: {
+		// #endif
+		// #ifdef VUE2
+		// 绑定的值
+		value: {
 			type: [String, Number],
 			default: defprops.input.value
 		},
-		// 输入框类型
+		// #endif
 		// number-数字输入键盘,app-vue下可以输入浮点数,app-nvue和小程序平台下只能输入整数
 		// idcard-身份证输入键盘,微信、支付宝、百度、QQ小程序
 		// digit-带小数点的数字键盘,App的nvue页面、微信、支付宝、百度、头条、QQ小程序

+ 1 - 1
src/uni_modules/uview-plus/components/u-keyboard/u-keyboard.vue

@@ -75,7 +75,7 @@
 	/**
 	 * keyboard 键盘
 	 * @description 此为uViw自定义的键盘面板,内含了数字键盘,车牌号键,身份证号键盘3中模式,都有可以打乱按键顺序的选项。
-	 * @tutorial https://www.uviewui.com/components/keyboard.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/keyboard.html
 	 * @property {String}			mode				键盘类型,见官网基本使用的说明 (默认 'number' )
 	 * @property {Boolean}			dotDisabled			是否显示"."按键,只在mode=number时有效 (默认 false )
 	 * @property {Boolean}			tooltip				是否显示键盘顶部工具条 (默认 true )

+ 1 - 1
src/uni_modules/uview-plus/components/u-line-progress/u-line-progress.vue

@@ -33,7 +33,7 @@
 	/**
 	 * lineProgress 线型进度条
 	 * @description 展示操作或任务的当前进度,比如上传文件,是一个线形的进度条。
-	 * @tutorial https://www.uviewui.com/components/lineProgress.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/lineProgress.html
 	 * @property {String}			activeColor		激活部分的颜色 ( 默认 '#19be6b' )
 	 * @property {String}			inactiveColor	背景色 ( 默认 '#ececec' )
 	 * @property {String | Number}	percentage		进度百分比,数值 ( 默认 0 )

+ 1 - 1
src/uni_modules/uview-plus/components/u-line/u-line.vue

@@ -14,7 +14,7 @@
 	/**
 	 * line 线条
 	 * @description 此组件一般用于显示一根线条,用于分隔内容块,有横向和竖向两种模式,且能设置0.5px线条,使用也很简单
-	 * @tutorial https://www.uviewui.com/components/line.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/line.html
 	 * @property {String}			color		线条的颜色 ( 默认 '#d6d7d9' )
 	 * @property {String | Number}	length		长度,竖向时表现为高度,横向时表现为长度,可以为百分比,带px单位的值等 ( 默认 '100%' )
 	 * @property {String}			direction	线条的方向,row-横向,col-竖向 (默认 'row' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-link/u-link.vue

@@ -14,7 +14,7 @@
 	/**
 	 * link 超链接
 	 * @description 该组件为超链接组件,在不同平台有不同表现形式:在APP平台会通过plus环境打开内置浏览器,在小程序中把链接复制到粘贴板,同时提示信息,在H5中通过window.open打开链接。
-	 * @tutorial https://www.uviewui.com/components/link.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/link.html
 	 * @property {String}			color		文字颜色 (默认 color['u-primary'] )
 	 * @property {String | Number}	fontSize	字体大小,单位px (默认 15 )
 	 * @property {Boolean}			underLine	是否显示下划线 (默认 false )

+ 1 - 1
src/uni_modules/uview-plus/components/u-list-item/u-list-item.vue

@@ -25,7 +25,7 @@
 	/**
 	 * List 列表
 	 * @description 该组件为高性能列表组件
-	 * @tutorial https://www.uviewui.com/components/list.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/list.html
 	 * @property {String | Number}	anchor	用于滚动到指定item
 	 * @example <u-list-ite v-for="(item, index) in indexList" :key="index" ></u-list-item>
 	 */

+ 1 - 1
src/uni_modules/uview-plus/components/u-list/u-list.vue

@@ -46,7 +46,7 @@
 	/**
 	 * List 列表
 	 * @description 该组件为高性能列表组件
-	 * @tutorial https://www.uviewui.com/components/list.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/list.html
 	 * @property {Boolean}			showScrollbar		控制是否出现滚动条,仅nvue有效 (默认 false )
 	 * @property {String | Number}	lowerThreshold		距底部多少时触发scrolltolower事件 (默认 50 )
 	 * @property {String | Number}	upperThreshold		距顶部多少时触发scrolltoupper事件,非nvue有效 (默认 0 )

+ 1 - 1
src/uni_modules/uview-plus/components/u-loading-icon/u-loading-icon.vue

@@ -67,7 +67,7 @@
 	/**
 	 * loading 加载动画
 	 * @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。
-	 * @tutorial https://www.uviewui.com/components/loading.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/loading.html
 	 * @property {Boolean}			show			是否显示组件  (默认 true)
 	 * @property {String}			color			动画活动区域的颜色,只对 mode = flower 模式有效(默认color['u-tips-color'])
 	 * @property {String}			textColor		提示文本的颜色(默认color['u-tips-color'])

+ 1 - 1
src/uni_modules/uview-plus/components/u-loading-page/u-loading-page.vue

@@ -53,7 +53,7 @@ import mixin from '../../libs/mixin/mixin.js';
 /**
  * loadingPage 加载动画
  * @description 警此组件为一个小动画,目前用在uView的loadmore加载更多和switch开关等组件的正在加载状态场景。
- * @tutorial https://www.uviewui.com/components/loading.html
+ * @tutorial https://ijry.github.io/uview-plus/components/loading.html
  * @property {String | Number}	loadingText		提示内容  (默认 '正在加载' )
  * @property {String}			image			文字上方用于替换loading动画的图片
  * @property {String}			loadingMode		加载动画的模式,circle-圆形,spinner-花朵形,semicircle-半圆形 (默认 'circle' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-loadmore/u-loadmore.vue

@@ -59,7 +59,7 @@
 	/**
 	 * loadmore 加载更多
 	 * @description 此组件一般用于标识页面底部加载数据时的状态。
-	 * @tutorial https://www.uviewui.com/components/loadMore.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/loadMore.html
 	 * @property {String}			status			组件状态(默认 'loadmore' )
 	 * @property {String}			bgColor			组件背景颜色,在页面是非白色时会用到(默认 'transparent' )
 	 * @property {Boolean}			icon			加载中时是否显示图标(默认 true )

+ 1 - 1
src/uni_modules/uview-plus/components/u-modal/u-modal.vue

@@ -96,7 +96,7 @@
 	/**
 	 * Modal 模态框
 	 * @description 弹出模态框,常用于消息提示、消息确认、在当前页面内完成特定的交互操作。
-	 * @tutorial https://www.uviewui.com/components/modul.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/modul.html
 	 * @property {Boolean}			show				是否显示模态框,请赋值给show (默认 false )
 	 * @property {String}			title				标题内容
 	 * @property {String}			content				模态框内容,如传入slot内容,则此参数无效

+ 1 - 1
src/uni_modules/uview-plus/components/u-navbar/u-navbar.vue

@@ -79,7 +79,7 @@
 	/**
 	 * Navbar 自定义导航栏
 	 * @description 此组件一般用于在特殊情况下,需要自定义导航栏的时候用到,一般建议使用uni-app带的导航栏。
-	 * @tutorial https://www.uviewui.com/components/navbar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/navbar.html
 	 * @property {Boolean}			safeAreaInsetTop	是否开启顶部安全区适配  (默认 true )
 	 * @property {Boolean}			placeholder			固定在顶部时,是否生成一个等高元素,以防止塌陷 (默认 false )
 	 * @property {Boolean}			fixed				导航栏是否固定在顶部 (默认 false )

+ 1 - 1
src/uni_modules/uview-plus/components/u-no-network/u-no-network.vue

@@ -50,7 +50,7 @@
 	/**
 	 * noNetwork 无网络提示
 	 * @description 该组件无需任何配置,引入即可,内部自动处理所有功能和事件。
-	 * @tutorial https://www.uviewui.com/components/noNetwork.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/noNetwork.html
 	 * @property {String}			tips 	没有网络时的提示语 (默认:'哎呀,网络信号丢失' )
 	 * @property {String | Number}	zIndex	组件的z-index值 
 	 * @property {String}			image	无网络的图片提示,可用的src地址或base64图片 

+ 1 - 1
src/uni_modules/uview-plus/components/u-notice-bar/u-notice-bar.vue

@@ -46,7 +46,7 @@
 	/**
 	 * noticeBar 滚动通知
 	 * @description 该组件用于滚动通告场景,有多种模式可供选择
-	 * @tutorial https://www.uviewui.com/components/noticeBar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/noticeBar.html
 	 * @property {Array | String}	text			显示的内容,数组
 	 * @property {String}			direction		通告滚动模式,row-横向滚动,column-竖向滚动 ( 默认 'row' )
 	 * @property {Boolean}			step			direction = row时,是否使用步进形式滚动  ( 默认 false )

+ 1 - 1
src/uni_modules/uview-plus/components/u-number-box/u-number-box.vue

@@ -200,7 +200,7 @@
 			this.init()
 		},
 		// #ifdef VUE3
-		emits: ['update:modelValue', 'focus', 'blur', 'overlimit'],
+		emits: ['update:modelValue', 'focus', 'blur', 'overlimit', 'change', 'plus', 'minus'],
 		// #endif
 		methods: {
 			init() {

+ 1 - 1
src/uni_modules/uview-plus/components/u-overlay/u-overlay.vue

@@ -18,7 +18,7 @@
 	/**
 	 * overlay 遮罩
 	 * @description 创建一个遮罩层,用于强调特定的页面元素,并阻止用户对遮罩下层的内容进行操作,一般用于弹窗场景
-	 * @tutorial https://www.uviewui.com/components/overlay.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/overlay.html
 	 * @property {Boolean}			show		是否显示遮罩(默认 false )
 	 * @property {String | Number}	zIndex		zIndex 层级(默认 10070 )
 	 * @property {String | Number}	duration	动画时长,单位毫秒(默认 300 )

+ 192 - 115
src/uni_modules/uview-plus/components/u-parse/node/node.vue

@@ -1,46 +1,56 @@
 <template>
-  <view :id="attrs.id" :class="'_'+name+' '+attrs.class" :style="attrs.style">
+  <view :id="attrs.id" :class="'_block _'+name+' '+attrs.class" :style="attrs.style">
     <block v-for="(n, i) in childs" v-bind:key="i">
       <!-- 图片 -->
       <!-- 占位图 -->
-      <image v-if="n.name=='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
+      <image v-if="n.name==='img'&&!n.t&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style" :src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
       <!-- 显示图片 -->
-      <!-- #ifdef H5 || APP-PLUS -->
-      <img v-if="n.name=='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]==-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap"/>
+      <!-- #ifdef H5 || (APP-PLUS && VUE2) -->
+      <img v-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- #ifndef H5 || (APP-PLUS && VUE2) -->
+      <!-- 表格中的图片,使用 rich-text 防止大小不正确 -->
+      <rich-text v-if="n.name==='img'&&n.t" :style="'display:'+n.t" :nodes="'<img class=\'_img\' style=\''+n.attrs.style+'\' src=\''+n.attrs.src+'\'>'" :data-i="i" @tap.stop="imgTap" />
       <!-- #endif -->
       <!-- #ifndef H5 || APP-PLUS -->
-      <image v-if="n.name=='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]==-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="n.h?'':'widthFix'" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style" :src="n.attrs.src" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :lazy-load="opts[0]" :webp="n.webp" :show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+      <!-- #endif -->
+      <!-- #ifdef APP-PLUS && VUE3 -->
+      <image v-else-if="n.name==='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class" :style="(ctrl[i]===-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;'+n.attrs.style" :src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :mode="!n.h?'widthFix':(!n.w?'heightFix':'')" :data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
       <!-- #endif -->
       <!-- 文本 -->
-      <!-- #ifndef MP-BAIDU -->
-      <text v-else-if="n.type=='text'" decode>{{n.text}}</text>
+      <!-- #ifdef MP-WEIXIN -->
+      <text v-else-if="n.text" :user-select="opts[4]=='force'&&isiOS" decode>{{n.text}}</text>
       <!-- #endif -->
-      <text v-else-if="n.name=='br'">\n</text>
+      <!-- #ifndef MP-WEIXIN || MP-BAIDU || MP-ALIPAY || MP-TOUTIAO -->
+      <text v-else-if="n.text" decode>{{n.text}}</text>
+      <!-- #endif -->
+      <text v-else-if="n.name==='br'">\n</text>
       <!-- 链接 -->
-      <view v-else-if="n.name=='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
+      <view v-else-if="n.name==='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class" hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @tap.stop="linkTap">
         <node name="span" :childs="n.children" :opts="opts" style="display:inherit" />
       </view>
       <!-- 视频 -->
       <!-- #ifdef APP-PLUS -->
-      <view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" />
+      <view v-else-if="n.html" :id="n.attrs.id" :class="'_video '+n.attrs.class" :style="n.attrs.style" v-html="n.html" @vplay.stop="play" />
       <!-- #endif -->
       <!-- #ifndef APP-PLUS -->
-      <video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+      <video v-else-if="n.name==='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted" :object-fit="n.attrs['object-fit']" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
       <!-- #endif -->
       <!-- #ifdef H5 || APP-PLUS -->
-      <iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
-      <embed v-else-if="n.name=='embed'" :style="n.attrs.style" :src="n.attrs.src" />
+      <iframe v-else-if="n.name==='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen" :frameborder="n.attrs.frameborder" :src="n.attrs.src" />
+      <embed v-else-if="n.name==='embed'" :style="n.attrs.style" :src="n.attrs.src" />
       <!-- #endif -->
-      <!-- #ifndef MP-TOUTIAO -->
+      <!-- #ifndef MP-TOUTIAO || ((H5 || APP-PLUS) && VUE3) -->
       <!-- 音频 -->
-      <!-- <audio v-else-if="n.name=='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" /> -->
+      <audio v-else-if="n.name==='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" :author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name" :poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
       <!-- #endif -->
-      <view v-else-if="(n.name=='table'&&n.c)||n.name=='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
-        <node v-if="n.name=='li'" :childs="n.children" :opts="opts" />
+      <view v-else-if="(n.name==='table'&&n.c)||n.name==='li'" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
+        <node v-if="n.name==='li'" :childs="n.children" :opts="opts" />
         <view v-else v-for="(tbody, x) in n.children" v-bind:key="x" :class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
-          <node v-if="tbody.name=='td'||tbody.name=='th'" :childs="tbody.children" :opts="opts" />
+          <node v-if="tbody.name==='td'||tbody.name==='th'" :childs="tbody.children" :opts="opts" />
           <block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
-            <view v-if="tr.name=='td'||tr.name=='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
+            <view v-if="tr.name==='td'||tr.name==='th'" :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
               <node :childs="tr.children" :opts="opts" />
             </view>
             <view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
@@ -51,16 +61,16 @@
           </block>
         </view>
       </view>
-      
+
       <!-- 富文本 -->
-      <!-- #ifdef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->
-      <rich-text v-else-if="handler.use(n)" :id="n.attrs.id" :style="n.f" :nodes="[n]" />
+      <!-- #ifdef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
+      <rich-text v-else-if="!n.c&&!handler.isInline(n.name, n.attrs.style)" :id="n.attrs.id" :style="n.f" :user-select="opts[4]" :nodes="[n]" />
       <!-- #endif -->
-      <!-- #ifndef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->
-      <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :nodes="[n]" />
+      <!-- #ifndef H5 || ((MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE2) -->
+      <rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :selectable="opts[4]" :user-select="opts[4]" :nodes="[n]" />
       <!-- #endif -->
       <!-- 继续递归 -->
-      <view v-else-if="n.c==2" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
+      <view v-else-if="n.c===2" :id="n.attrs.id" :class="'_block _'+n.name+' '+n.attrs.class" :style="n.f+';'+n.attrs.style">
         <node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs" :childs="n2.children" :opts="opts" />
       </view>
       <node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
@@ -87,14 +97,11 @@ var inlineTags = {
   sup: true
 }
 /**
- * @description 是否使用 rich-text 显示剩余内容
+ * @description 判断是否为行内标签
  */
 module.exports = {
-  use: function (item) {
-  // 微信和 QQ 的 rich-text inline 布局无效
-  if (inlineTags[item.name] || (item.attrs.style || '').indexOf('display:inline') != -1)
-    return false
-  return !item.c
+  isInline: function (tagName, style) {
+    return inlineTags[tagName] || (style || '').indexOf('display:inline') !== -1
   }
 }
 </script>
@@ -103,21 +110,27 @@ module.exports = {
 import node from './node'
 export default {
   name: 'node',
-  // #ifdef MP-WEIXIN
   options: {
-    virtualHost: true
+    // #ifdef MP-WEIXIN
+    virtualHost: true,
+    // #endif
+    // #ifdef MP-TOUTIAO
+    addGlobalClass: false
+    // #endif
   },
-  // #endif
-  data() {
+  data () {
     return {
-      ctrl: {}
+      ctrl: {},
+      // #ifdef MP-WEIXIN
+      isiOS: uni.getSystemInfoSync().system.includes('iOS')
+      // #endif
     }
   },
   props: {
     name: String,
     attrs: {
       type: Object,
-      default() {
+      default () {
         return {}
       }
     },
@@ -126,16 +139,21 @@ export default {
   },
   components: {
 
+    // #ifndef (H5 || APP-PLUS) && VUE3
     node
+    // #endif
   },
-  mounted() {
-    for (this.root = this.$parent; this.root.$options.name != 'mp-html'; this.root = this.root.$parent);
+  mounted () {
+    this.$nextTick(() => {
+      for (this.root = this.$parent; this.root.$options.name !== 'u-parse'; this.root = this.root.$parent);
+    })
     // #ifdef H5 || APP-PLUS
     if (this.opts[0]) {
-      for (var i = this.childs.length; i--;)
-        if (this.childs[i].name == 'img')
-          break
-      if (i != -1) {
+      let i
+      for (i = this.childs.length; i--;) {
+        if (this.childs[i].name === 'img') break
+      }
+      if (i !== -1) {
         this.observer = uni.createIntersectionObserver(this).relativeToViewport({
           top: 500,
           bottom: 500
@@ -150,38 +168,45 @@ export default {
     }
     // #endif
   },
-  beforeDestroy() {
+  beforeDestroy () {
     // #ifdef H5 || APP-PLUS
-    if (this.observer)
+    if (this.observer) {
       this.observer.disconnect()
+    }
     // #endif
   },
   methods:{
     // #ifdef MP-WEIXIN
-    toJSON() { },
+    toJSON () { return this },
     // #endif
     /**
      * @description 播放视频事件
-     * @param {Event} e 
+     * @param {Event} e
      */
-    play(e) {
+    play (e) {
+      this.root.$emit('play')
       // #ifndef APP-PLUS
       if (this.root.pauseVideo) {
-        var flag = false, id = e.target.id
-        for (var i = this.root._videos.length; i--;) {
-          if (this.root._videos[i].id == id)
+        let flag = false
+        const id = e.target.id
+        for (let i = this.root._videos.length; i--;) {
+          if (this.root._videos[i].id === id) {
             flag = true
-          else
+          } else {
             this.root._videos[i].pause() // 自动暂停其他视频
+          }
         }
         // 将自己加入列表
         if (!flag) {
-          var ctx = uni.createVideoContext(id
+          const ctx = uni.createVideoContext(id
             // #ifndef MP-BAIDU
             , this
             // #endif
           )
           ctx.id = id
+          if (this.root.playbackRate) {
+            ctx.playbackRate(this.root.playbackRate)
+          }
           this.root._videos.push(ctx)
         }
       }
@@ -190,85 +215,121 @@ export default {
 
     /**
      * @description 图片点击事件
-     * @param {Event} e 
+     * @param {Event} e
      */
-    imgTap(e) {
-      var node = this.childs[e.currentTarget.dataset.i]
-      if (node.a)
-        return this.linkTap(node.a)
-      if (node.attrs.ignore)
+    imgTap (e) {
+      const node = this.childs[e.currentTarget.dataset.i]
+      if (node.a) {
+        this.linkTap(node.a)
         return
+      }
+      if (node.attrs.ignore) return
       // #ifdef H5 || APP-PLUS
       node.attrs.src = node.attrs.src || node.attrs['data-src']
       // #endif
-      this.root.$emit('imgTap', node.attrs)
+      this.root.$emit('imgtap', node.attrs)
       // 自动预览图片
-      if (this.root.previewImg)
+      if (this.root.previewImg) {
         uni.previewImage({
+          // #ifdef MP-WEIXIN
+          showmenu: this.root.showImgMenu,
+          // #endif
+          // #ifdef MP-ALIPAY
+          enablesavephoto: this.root.showImgMenu,
+          enableShowPhotoDownload: this.root.showImgMenu,
+          // #endif
           current: parseInt(node.attrs.i),
           urls: this.root.imgList
         })
+      }
     },
 
     /**
      * @description 图片长按
      */
-    imgLongTap(e) {
+    imgLongTap (e) {
       // #ifdef APP-PLUS
-      var attrs = this.childs[e.currentTarget.dataset.i].attrs
-      if (!attrs.ignore)
+      const attrs = this.childs[e.currentTarget.dataset.i].attrs
+      if (this.opts[3] && !attrs.ignore) {
         uni.showActionSheet({
           itemList: ['保存图片'],
           success: () => {
-            uni.downloadFile({
-              url: this.root.imgList[attrs.i],
-              success: res => {
-                uni.saveImageToPhotosAlbum({
-                  filePath: res.tempFilePath,
-                  success() {
-                    uni.showToast({
-                      title: '保存成功'
-                    })
-                  }
-                })
-              }
-            })
+            const save = path => {
+              uni.saveImageToPhotosAlbum({
+                filePath: path,
+                success () {
+                  uni.showToast({
+                    title: '保存成功'
+                  })
+                }
+              })
+            }
+            if (this.root.imgList[attrs.i].startsWith('http')) {
+              uni.downloadFile({
+                url: this.root.imgList[attrs.i],
+                success: res => save(res.tempFilePath)
+              })
+            } else {
+              save(this.root.imgList[attrs.i])
+            }
           }
         })
+      }
       // #endif
     },
 
     /**
      * @description 图片加载完成事件
-     * @param {Event} e 
+     * @param {Event} e
      */
-    imgLoad(e) {
-      var i = e.currentTarget.dataset.i
-      // #ifndef H5 || APP-PLUS
-      // 设置原宽度
-      if (!this.childs[i].w)
+    imgLoad (e) {
+      const i = e.currentTarget.dataset.i
+      /* #ifndef H5 || (APP-PLUS && VUE2) */
+      if (!this.childs[i].w) {
+        // 设置原宽度
         this.$set(this.ctrl, i, e.detail.width)
-      else
-        // #endif
+      } else /* #endif */ if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] === -1) {
         // 加载完毕,取消加载中占位图
-        if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] == -1)
-          this.$set(this.ctrl, i, 1)
+        this.$set(this.ctrl, i, 1)
+      }
+      this.checkReady()
+    },
+
+    /**
+     * @description 检查是否所有图片加载完毕
+     */
+    checkReady () {
+      if (!this.root.lazyLoad) {
+        this.root._unloadimgs -= 1
+        if (!this.root._unloadimgs) {
+          setTimeout(() => {
+            this.root.getRect().then(rect => {
+              this.root.$emit('ready', rect)
+            }).catch(() => {
+              this.root.$emit('ready', {})
+            })
+          }, 350)
+        }
+      }
     },
 
     /**
      * @description 链接点击事件
-     * @param {Event} e 
+     * @param {Event} e
      */
-    linkTap(e) {
-      var attrs = e.currentTarget ? this.childs[e.currentTarget.dataset.i].attrs : e,
-        href = attrs.href
-      this.root.$emit('linkTap', attrs)
+    linkTap (e) {
+      const node = e.currentTarget ? this.childs[e.currentTarget.dataset.i] : {}
+      const attrs = node.attrs || e
+      const href = attrs.href
+      this.root.$emit('linktap', Object.assign({
+        innerText: this.root.getText(node.children || []) // 链接内的文本内容
+      }, attrs))
       if (href) {
-        // 跳转锚点
-        if (href[0] == '#')
+        if (href[0] === '#') {
+          // 跳转锚点
           this.root.navigateTo(href.substring(1)).catch(() => { })
-        // 复制外部链接
-        else if (href.includes('://')) {
+        } else if (href.split('?')[0].includes('://')) {
+          // 复制外部链接
           if (this.root.copyLink) {
             // #ifdef H5
             window.open(href)
@@ -286,45 +347,57 @@ export default {
             plus.runtime.openWeb(href)
             // #endif
           }
-        }
-        // 跳转页面
-        else
+        } else {
+          // 跳转页面
           uni.navigateTo({
             url: href,
-            fail() {
+            fail () {
               uni.switchTab({
                 url: href,
-                fail() { }
+                fail () { }
               })
             }
           })
+        }
       }
     },
 
     /**
      * @description 错误事件
-     * @param {Event} e 
+     * @param {Event} e
      */
-    mediaError(e) {
-      var i = e.currentTarget.dataset.i,
-        node = this.childs[i]
+    mediaError (e) {
+      const i = e.currentTarget.dataset.i
+      const node = this.childs[i]
       // 加载其他源
-      if (node.name == 'video' || node.name == 'audio') {
-        var index = (this.ctrl[i] || 0) + 1
-        if (index > node.src.length)
+      if (node.name === 'video' || node.name === 'audio') {
+        let index = (this.ctrl[i] || 0) + 1
+        if (index > node.src.length) {
           index = 0
-        if (index < node.src.length)
-          return this.$set(this.ctrl, i, index)
+        }
+        if (index < node.src.length) {
+          this.$set(this.ctrl, i, index)
+          return
+        }
+      } else if (node.name === 'img') {
+        // #ifdef H5 && VUE3
+        if (this.opts[0] && !this.ctrl.load) return
+        // #endif
+        // 显示错误占位图
+        if (this.opts[2]) {
+          this.$set(this.ctrl, i, -1)
+        }
+        this.checkReady()
       }
-      // 显示错误占位图
-      else if (node.name == 'img' && this.opts[2])
-        this.$set(this.ctrl, i, -1)
-      if (this.root)
+      if (this.root) {
         this.root.$emit('error', {
           source: node.name,
           attrs: node.attrs,
+          // #ifndef H5 && VUE3
           errMsg: e.detail.errMsg
+          // #endif
         })
+      }
     }
   }
 }
@@ -351,6 +424,10 @@ export default {
 
 /* 内部样式 */
 
+._block {
+  display: block;
+}
+
 ._b,
 ._strong {
   font-weight: bold;
@@ -496,4 +573,4 @@ export default {
   height: 225px;
 }
 /* #endif */
-</style>
+</style>

+ 1129 - 871
src/uni_modules/uview-plus/components/u-parse/parser.js

@@ -1,1075 +1,1333 @@
-'use strict'
-
 /**
  * @fileoverview html 解析器
  */
+
 // 配置
 const config = {
-    // 信任的标签(保持标签名不变)
-    trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
-    // 块级标签(转为 div,其他的非信任标签转为 span)
-    blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
-    // 要移除的标签
-    ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),
-    // 自闭合的标签
-    voidTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
-    // html 实体
-    entities: {
-        lt: '<',
-        gt: '>',
-        quot: '"',
-        apos: "'",
-        ensp: '\u2002',
-        emsp: '\u2003',
-        nbsp: '\xA0',
-        semi: ';',
-        ndash: '–',
-        mdash: '—',
-        middot: '·',
-        lsquo: '‘',
-        rsquo: '’',
-        ldquo: '“',
-        rdquo: '”',
-        bull: '•',
-        hellip: '…'
-    },
-    // 默认的标签样式
-    tagStyle: {
+  // 信任的标签(保持标签名不变)
+  trustTags: makeMap('a,abbr,ad,audio,b,blockquote,br,code,col,colgroup,dd,del,dl,dt,div,em,fieldset,h1,h2,h3,h4,h5,h6,hr,i,img,ins,label,legend,li,ol,p,q,ruby,rt,source,span,strong,sub,sup,table,tbody,td,tfoot,th,thead,tr,title,ul,video'),
+
+  // 块级标签(转为 div,其他的非信任标签转为 span)
+  blockTags: makeMap('address,article,aside,body,caption,center,cite,footer,header,html,nav,pre,section'),
+
+  // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
+  // 行内标签
+  inlineTags: makeMap('abbr,b,big,code,del,em,i,ins,label,q,small,span,strong,sub,sup'),
+  // #endif
+
+  // 要移除的标签
+  ignoreTags: makeMap('area,base,canvas,embed,frame,head,iframe,input,link,map,meta,param,rp,script,source,style,textarea,title,track,wbr'),
+
+  // 自闭合的标签
+  voidTags: makeMap('area,base,br,col,circle,ellipse,embed,frame,hr,img,input,line,link,meta,param,path,polygon,rect,source,track,use,wbr'),
+
+  // html 实体
+  entities: {
+    lt: '<',
+    gt: '>',
+    quot: '"',
+    apos: "'",
+    ensp: '\u2002',
+    emsp: '\u2003',
+    nbsp: '\xA0',
+    semi: ';',
+    ndash: '–',
+    mdash: '—',
+    middot: '·',
+    lsquo: '‘',
+    rsquo: '’',
+    ldquo: '“',
+    rdquo: '”',
+    bull: '•',
+    hellip: '…',
+    larr: '←',
+    uarr: '↑',
+    rarr: '→',
+    darr: '↓'
+  },
+
+  // 默认的标签样式
+  tagStyle: {
     // #ifndef APP-PLUS-NVUE
-        address: 'font-style:italic',
-        big: 'display:inline;font-size:1.2em',
-        caption: 'display:table-caption;text-align:center',
-        center: 'text-align:center',
-        cite: 'font-style:italic',
-        dd: 'margin-left:40px',
-        mark: 'background-color:yellow',
-        pre: 'font-family:monospace;white-space:pre',
-        s: 'text-decoration:line-through',
-        small: 'display:inline;font-size:0.8em',
-        u: 'text-decoration:underline' // #endif
-
-    }
+    address: 'font-style:italic',
+    big: 'display:inline;font-size:1.2em',
+    caption: 'display:table-caption;text-align:center',
+    center: 'text-align:center',
+    cite: 'font-style:italic',
+    dd: 'margin-left:40px',
+    mark: 'background-color:yellow',
+    pre: 'font-family:monospace;white-space:pre',
+    s: 'text-decoration:line-through',
+    small: 'display:inline;font-size:0.8em',
+    strike: 'text-decoration:line-through',
+    u: 'text-decoration:underline'
+    // #endif
+  },
+
+  // svg 大小写对照表
+  svgDict: {
+    animatetransform: 'animateTransform',
+    lineargradient: 'linearGradient',
+    viewbox: 'viewBox',
+    attributename: 'attributeName',
+    repeatcount: 'repeatCount',
+    repeatdur: 'repeatDur'
+  }
 }
-const { windowWidth } = uni.getSystemInfoSync()
+const tagSelector={}
+const {
+  windowWidth,
+  // #ifdef MP-WEIXIN
+  system
+  // #endif
+} = uni.getSystemInfoSync()
 const blankChar = makeMap(' ,\r,\n,\t,\f')
-let idIndex = 0 // #ifdef H5 || APP-PLUS
+let idIndex = 0
 
-config.ignoreTags.iframe = void 0
+// #ifdef H5 || APP-PLUS
+config.ignoreTags.iframe = undefined
 config.trustTags.iframe = true
-config.ignoreTags.embed = void 0
-config.trustTags.embed = true // #endif
+config.ignoreTags.embed = undefined
+config.trustTags.embed = true
+// #endif
 // #ifdef APP-PLUS-NVUE
-
-config.ignoreTags.source = void 0
-config.ignoreTags.style = void 0 // #endif
+config.ignoreTags.source = undefined
+config.ignoreTags.style = undefined
+// #endif
 
 /**
  * @description 创建 map
  * @param {String} str 逗号分隔
  */
-
-function makeMap(str) {
-    const map = Object.create(null)
-    const list = str.split(',')
-
-    for (let i = list.length; i--;) {
-        map[list[i]] = true
-    }
-
-    return map
+function makeMap (str) {
+  const map = Object.create(null)
+  const list = str.split(',')
+  for (let i = list.length; i--;) {
+    map[list[i]] = true
+  }
+  return map
 }
+
 /**
  * @description 解码 html 实体
  * @param {String} str 要解码的字符串
  * @param {Boolean} amp 要不要解码 &amp;
  * @returns {String} 解码后的字符串
  */
-
-function decodeEntity(str, amp) {
-    let i = str.indexOf('&')
-
-    while (i != -1) {
-        const j = str.indexOf(';', i + 3)
-        let code = void 0
-        if (j == -1) break
-
-        if (str[i + 1] == '#') {
-            // &#123; 形式的实体
-            code = parseInt((str[i + 2] == 'x' ? '0' : '') + str.substring(i + 2, j))
-            if (!isNaN(code)) str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)
-        } else {
-            // &nbsp; 形式的实体
-            code = str.substring(i + 1, j)
-            if (config.entities[code] || code == 'amp' && amp) str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)
-        }
-
-        i = str.indexOf('&', i + 1)
+function decodeEntity (str, amp) {
+  let i = str.indexOf('&')
+  while (i !== -1) {
+    const j = str.indexOf(';', i + 3)
+    let code
+    if (j === -1) break
+    if (str[i + 1] === '#') {
+      // &#123; 形式的实体
+      code = parseInt((str[i + 2] === 'x' ? '0' : '') + str.substring(i + 2, j))
+      if (!isNaN(code)) {
+        str = str.substr(0, i) + String.fromCharCode(code) + str.substr(j + 1)
+      }
+    } else {
+      // &nbsp; 形式的实体
+      code = str.substring(i + 1, j)
+      if (config.entities[code] || (code === 'amp' && amp)) {
+        str = str.substr(0, i) + (config.entities[code] || '&') + str.substr(j + 1)
+      }
     }
+    i = str.indexOf('&', i + 1)
+  }
+  return str
+}
 
-    return str
+/**
+ * @description 合并多个块级标签,加快长内容渲染
+ * @param {Array} nodes 要合并的标签数组
+ */
+function mergeNodes (nodes) {
+  let i = nodes.length - 1
+  for (let j = i; j >= -1; j--) {
+    if (j === -1 || nodes[j].c || !nodes[j].name || (nodes[j].name !== 'div' && nodes[j].name !== 'p' && nodes[j].name[0] !== 'h') || (nodes[j].attrs.style || '').includes('inline')) {
+      if (i - j >= 5) {
+        nodes.splice(j + 1, i - j, {
+          name: 'div',
+          attrs: {},
+          children: nodes.slice(j + 1, i + 1)
+        })
+      }
+      i = j - 1
+    }
+  }
 }
+
 /**
  * @description html 解析器
  * @param {Object} vm 组件实例
  */
-
-function parser(vm) {
-    this.options = vm || {}
-    this.tagStyle = Object.assign(config.tagStyle, this.options.tagStyle)
-    this.imgList = vm.imgList || []
-    this.plugins = vm.plugins || []
-    this.attrs = Object.create(null)
-    this.stack = []
-    this.nodes = []
+function Parser (vm) {
+  this.options = vm || {}
+  this.tagStyle = Object.assign({}, config.tagStyle, this.options.tagStyle)
+  this.imgList = vm.imgList || []
+  this.imgList._unloadimgs = 0
+  this.plugins = vm.plugins || []
+  this.attrs = Object.create(null)
+  this.stack = []
+  this.nodes = []
+  this.pre = (this.options.containerStyle || '').includes('white-space') && this.options.containerStyle.includes('pre') ? 2 : 0
 }
+
 /**
  * @description 执行解析
  * @param {String} content 要解析的文本
  */
-
-parser.prototype.parse = function (content) {
-    // 插件处理
-    for (let i = this.plugins.length; i--;) {
-        if (this.plugins[i].onUpdate) content = this.plugins[i].onUpdate(content, config) || content
+Parser.prototype.parse = function (content) {
+  // 插件处理
+  for (let i = this.plugins.length; i--;) {
+    if (this.plugins[i].onUpdate) {
+      content = this.plugins[i].onUpdate(content, config) || content
     }
-
-    new lexer(this).parse(content) // 出栈未闭合的标签
-
-    while (this.stack.length) {
-        this.popNode()
-    }
-
-    return this.nodes
+  }
+
+  new Lexer(this).parse(content)
+  // 出栈未闭合的标签
+  while (this.stack.length) {
+    this.popNode()
+  }
+  if (this.nodes.length > 50) {
+    mergeNodes(this.nodes)
+  }
+  return this.nodes
 }
+
 /**
  * @description 将标签暴露出来(不被 rich-text 包含)
  */
-
-parser.prototype.expose = function () {
-    // #ifndef APP-PLUS-NVUE
-    for (let i = this.stack.length; i--;) {
-        const item = this.stack[i]
-        if (item.name == 'a' || item.c) return
-        item.c = 1
-    } // #endif
+Parser.prototype.expose = function () {
+  // #ifndef APP-PLUS-NVUE
+  for (let i = this.stack.length; i--;) {
+    const item = this.stack[i]
+    if (item.c || item.name === 'a' || item.name === 'video' || item.name === 'audio') return
+    item.c = 1
+  }
+  // #endif
 }
+
 /**
  * @description 处理插件
  * @param {Object} node 要处理的标签
  * @returns {Boolean} 是否要移除此标签
  */
-
-parser.prototype.hook = function (node) {
-    for (let i = this.plugins.length; i--;) {
-        if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) == false) return false
+Parser.prototype.hook = function (node) {
+  for (let i = this.plugins.length; i--;) {
+    if (this.plugins[i].onParse && this.plugins[i].onParse(node, this) === false) {
+      return false
     }
-
-    return true
+  }
+  return true
 }
+
 /**
  * @description 将链接拼接上主域名
  * @param {String} url 需要拼接的链接
  * @returns {String} 拼接后的链接
  */
-
-parser.prototype.getUrl = function (url) {
-    const { domain } = this.options
-
-    if (url[0] == '/') {
-    // // 开头的补充协议名
-        if (url[1] == '/') url = `${domain ? domain.split('://')[0] : 'http'}:${url}` // 否则补充整个域名
-        else if (domain) url = domain + url
-    } else if (domain && !url.includes('data:') && !url.includes('://')) url = `${domain}/${url}`
-
-    return url
+Parser.prototype.getUrl = function (url) {
+  const domain = this.options.domain
+  if (url[0] === '/') {
+    if (url[1] === '/') {
+      // // 开头的补充协议名
+      url = (domain ? domain.split('://')[0] : 'http') + ':' + url
+    } else if (domain) {
+      // 否则补充整个域名
+      url = domain + url
+    } /* #ifdef APP-PLUS */ else {
+      url = plus.io.convertLocalFileSystemURL(url)
+    } /* #endif */
+  } else if (!url.includes('data:') && !url.includes('://')) {
+    if (domain) {
+      url = domain + '/' + url
+    } /* #ifdef APP-PLUS */ else {
+      url = plus.io.convertLocalFileSystemURL(url)
+    } /* #endif */
+  }
+  return url
 }
+
 /**
  * @description 解析样式表
  * @param {Object} node 标签
  * @returns {Object}
  */
+Parser.prototype.parseStyle = function (node) {
+  const attrs = node.attrs
+  const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))
+  const styleObj = {}
+  let tmp = ''
 
-parser.prototype.parseStyle = function (node) {
-    const { attrs } = node
-    const list = (this.tagStyle[node.name] || '').split(';').concat((attrs.style || '').split(';'))
-    const styleObj = {}
-    let tmp = ''
-
-    if (attrs.id) {
+  if (attrs.id && !this.xml) {
     // 暴露锚点
-        if (this.options.useAnchor) this.expose(); else if (node.name != 'img' && node.name != 'a' && node.name != 'video' && node.name != 'audio') attrs.id = void 0
-    } // 转换 width 和 height 属性
-
-    if (attrs.width) {
-        styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')
-        attrs.width = void 0
-    }
-
-    if (attrs.height) {
-        styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')
-        attrs.height = void 0
+    if (this.options.useAnchor) {
+      this.expose()
+    } else if (node.name !== 'img' && node.name !== 'a' && node.name !== 'video' && node.name !== 'audio') {
+      attrs.id = undefined
     }
-
-    for (let i = 0, len = list.length; i < len; i++) {
-        const info = list[i].split(':')
-        if (info.length < 2) continue
-        const key = info.shift().trim().toLowerCase()
-        let value = info.join(':').trim() // 兼容性的 css 不压缩
-
-        if (value[0] == '-' && value.lastIndexOf('-') > 0 || value.includes('safe')) tmp += ';'.concat(key, ':').concat(value) // 重复的样式进行覆盖
-        else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {
+  }
+
+  // 转换 width 和 height 属性
+  if (attrs.width) {
+    styleObj.width = parseFloat(attrs.width) + (attrs.width.includes('%') ? '%' : 'px')
+    attrs.width = undefined
+  }
+  if (attrs.height) {
+    styleObj.height = parseFloat(attrs.height) + (attrs.height.includes('%') ? '%' : 'px')
+    attrs.height = undefined
+  }
+
+  for (let i = 0, len = list.length; i < len; i++) {
+    const info = list[i].split(':')
+    if (info.length < 2) continue
+    const key = info.shift().trim().toLowerCase()
+    let value = info.join(':').trim()
+    if ((value[0] === '-' && value.lastIndexOf('-') > 0) || value.includes('safe')) {
+      // 兼容性的 css 不压缩
+      tmp += `;${key}:${value}`
+    } else if (!styleObj[key] || value.includes('import') || !styleObj[key].includes('import')) {
+      // 重复的样式进行覆盖
+      if (value.includes('url')) {
         // 填充链接
-            if (value.includes('url')) {
-                let j = value.indexOf('(') + 1
-
-                if (j) {
-                    while (value[j] == '"' || value[j] == "'" || blankChar[value[j]]) {
-                        j++
-                    }
-
-                    value = value.substr(0, j) + this.getUrl(value.substr(j))
-                }
-            } // 转换 rpx(rich-text 内部不支持 rpx)
-            else if (value.includes('rpx')) {
-                value = value.replace(/[0-9.]+\s*rpx/g, ($) => `${parseFloat($) * windowWidth / 750}px`)
-            }
-
-            styleObj[key] = value
+        let j = value.indexOf('(') + 1
+        if (j) {
+          while (value[j] === '"' || value[j] === "'" || blankChar[value[j]]) {
+            j++
+          }
+          value = value.substr(0, j) + this.getUrl(value.substr(j))
         }
+      } else if (value.includes('rpx')) {
+        // 转换 rpx(rich-text 内部不支持 rpx)
+        value = value.replace(/[0-9.]+\s*rpx/g, $ => parseFloat($) * windowWidth / 750 + 'px')
+      }
+      styleObj[key] = value
     }
+  }
 
-    node.attrs.style = tmp
-    return styleObj
+  node.attrs.style = tmp
+  return styleObj
 }
+
 /**
  * @description 解析到标签名
  * @param {String} name 标签名
  * @private
  */
-
-parser.prototype.onTagName = function (name) {
-    this.tagName = this.xml ? name : name.toLowerCase()
-    if (this.tagName == 'svg') this.xml = true // svg 标签内大小写敏感
+Parser.prototype.onTagName = function (name) {
+  this.tagName = this.xml ? name : name.toLowerCase()
+  if (this.tagName === 'svg') {
+    this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
+  }
 }
+
 /**
  * @description 解析到属性名
  * @param {String} name 属性名
  * @private
  */
-
-parser.prototype.onAttrName = function (name) {
-    name = this.xml ? name : name.toLowerCase()
-
-    if (name.substr(0, 5) == 'data-') {
-    // data-src 自动转为 src
-        if (name == 'data-src' && !this.attrs.src) this.attrName = 'src' // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用
-        else if (this.tagName == 'img' || this.tagName == 'a') this.attrName = name // 剩余的移除以减小大小
-        else this.attrName = void 0
+Parser.prototype.onAttrName = function (name) {
+  name = this.xml ? name : name.toLowerCase()
+  if (name.substr(0, 5) === 'data-') {
+    if (name === 'data-src' && !this.attrs.src) {
+      // data-src 自动转为 src
+      this.attrName = 'src'
+    } else if (this.tagName === 'img' || this.tagName === 'a') {
+      // a 和 img 标签保留 data- 的属性,可以在 imgtap 和 linktap 事件中使用
+      this.attrName = name
     } else {
-        this.attrName = name
-        this.attrs[name] = 'T' // boolean 型属性缺省设置
+      // 剩余的移除以减小大小
+      this.attrName = undefined
     }
+  } else {
+    this.attrName = name
+    this.attrs[name] = 'T' // boolean 型属性缺省设置
+  }
 }
+
 /**
  * @description 解析到属性值
  * @param {String} val 属性值
  * @private
  */
-
-parser.prototype.onAttrVal = function (val) {
-    const name = this.attrName || '' // 部分属性进行实体解码
-
-    if (name == 'style' || name == 'href') this.attrs[name] = decodeEntity(val, true) // 拼接主域名
-    else if (name.includes('src')) this.attrs[name] = this.getUrl(decodeEntity(val, true)); else if (name) this.attrs[name] = val
+Parser.prototype.onAttrVal = function (val) {
+  const name = this.attrName || ''
+  if (name === 'style' || name === 'href') {
+    // 部分属性进行实体解码
+    this.attrs[name] = decodeEntity(val, true)
+  } else if (name.includes('src')) {
+    // 拼接主域名
+    this.attrs[name] = this.getUrl(decodeEntity(val, true))
+  } else if (name) {
+    this.attrs[name] = val
+  }
 }
+
 /**
  * @description 解析到标签开始
  * @param {Boolean} selfClose 是否有自闭合标识 />
  * @private
  */
-
-parser.prototype.onOpenTag = function (selfClose) {
-    // 拼装 node
-    const node = Object.create(null)
-    node.name = this.tagName
-    node.attrs = this.attrs
-    this.attrs = Object.create(null)
-    const { attrs } = node
-    const parent = this.stack[this.stack.length - 1]
-    const siblings = parent ? parent.children : this.nodes
-    const close = this.xml ? selfClose : config.voidTags[node.name] // 转换 embed 标签
-
-    if (node.name == 'embed') {
+Parser.prototype.onOpenTag = function (selfClose) {
+  // 拼装 node
+  const node = Object.create(null)
+  node.name = this.tagName
+  node.attrs = this.attrs
+  // 避免因为自动 diff 使得 type 被设置为 null 导致部分内容不显示
+  if (this.options.nodes.length) {
+    node.type = 'node'
+  }
+  this.attrs = Object.create(null)
+
+  const attrs = node.attrs
+  const parent = this.stack[this.stack.length - 1]
+  const siblings = parent ? parent.children : this.nodes
+  const close = this.xml ? selfClose : config.voidTags[node.name]
+
+  // 替换标签名选择器
+  if (tagSelector[node.name]) {
+    attrs.class = tagSelector[node.name] + (attrs.class ? ' ' + attrs.class : '')
+  }
+
+  // 转换 embed 标签
+  if (node.name === 'embed') {
     // #ifndef H5 || APP-PLUS
-        const src = attrs.src || '' // 按照后缀名和 type 将 embed 转为 video 或 audio
-
-        if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) node.name = 'video'; else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) node.name = 'audio'
-        if (attrs.autostart) attrs.autoplay = 'T'
-        attrs.controls = 'T' // #endif
-        // #ifdef H5 || APP-PLUS
-
-        this.expose() // #endif
-    } // #ifndef APP-PLUS-NVUE
-    // 处理音视频
-
-    if (node.name == 'video' || node.name == 'audio') {
+    const src = attrs.src || ''
+    // 按照后缀名和 type 将 embed 转为 video 或 audio
+    if (src.includes('.mp4') || src.includes('.3gp') || src.includes('.m3u8') || (attrs.type || '').includes('video')) {
+      node.name = 'video'
+    } else if (src.includes('.mp3') || src.includes('.wav') || src.includes('.aac') || src.includes('.m4a') || (attrs.type || '').includes('audio')) {
+      node.name = 'audio'
+    }
+    if (attrs.autostart) {
+      attrs.autoplay = 'T'
+    }
+    attrs.controls = 'T'
+    // #endif
+    // #ifdef H5 || APP-PLUS
+    this.expose()
+    // #endif
+  }
+
+  // #ifndef APP-PLUS-NVUE
+  // 处理音视频
+  if (node.name === 'video' || node.name === 'audio') {
     // 设置 id 以便获取 context
-        if (node.name == 'video' && !attrs.id) attrs.id = `v${idIndex++}` // 没有设置 controls 也没有设置 autoplay 的自动设置 controls
+    if (node.name === 'video' && !attrs.id) {
+      attrs.id = 'v' + idIndex++
+    }
+    // 没有设置 controls 也没有设置 autoplay 的自动设置 controls
+    if (!attrs.controls && !attrs.autoplay) {
+      attrs.controls = 'T'
+    }
+    // 用数组存储所有可用的 source
+    node.src = []
+    if (attrs.src) {
+      node.src.push(attrs.src)
+      attrs.src = undefined
+    }
+    this.expose()
+  }
+  // #endif
 
-        if (!attrs.controls && !attrs.autoplay) attrs.controls = 'T' // 用数组存储所有可用的 source
+  // 处理自闭合标签
+  if (close) {
+    if (!this.hook(node) || config.ignoreTags[node.name]) {
+      // 通过 base 标签设置主域名
+      if (node.name === 'base' && !this.options.domain) {
+        this.options.domain = attrs.href
+      } /* #ifndef APP-PLUS-NVUE */ else if (node.name === 'source' && parent && (parent.name === 'video' || parent.name === 'audio') && attrs.src) {
+        // 设置 source 标签(仅父节点为 video 或 audio 时有效)
+        parent.src.push(attrs.src)
+      } /* #endif */
+      return
+    }
 
-        node.src = []
+    // 解析 style
+    const styleObj = this.parseStyle(node)
 
-        if (attrs.src) {
-            node.src.push(attrs.src)
-            attrs.src = void 0
+    // 处理图片
+    if (node.name === 'img') {
+      if (attrs.src) {
+        // 标记 webp
+        if (attrs.src.includes('webp')) {
+          node.webp = 'T'
         }
-
-        this.expose()
-    } // #endif
-    // 处理自闭合标签
-
-    if (close) {
-        if (!this.hook(node) || config.ignoreTags[node.name]) {
-            // 通过 base 标签设置主域名
-            if (node.name == 'base' && !this.options.domain) this.options.domain = attrs.href // #ifndef APP-PLUS-NVUE
-            // 设置 source 标签(仅父节点为 video 或 audio 时有效)
-            else if (node.name == 'source' && parent && (parent.name == 'video' || parent.name == 'audio') && attrs.src) parent.src.push(attrs.src) // #endif
-
-            return
-        } // 解析 style
-
-        const styleObj = this.parseStyle(node) // 处理图片
-
-        if (node.name == 'img') {
-            if (attrs.src) {
-                // 标记 webp
-                if (attrs.src.includes('webp')) node.webp = 'T' // data url 图片如果没有设置 original-src 默认为不可预览的小图片
-
-                if (attrs.src.includes('data:') && !attrs['original-src']) attrs.ignore = 'T'
-
-                if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
-                    for (let i = this.stack.length; i--;) {
-                        const item = this.stack[i]
-
-                        if (item.name == 'a') {
-                            node.a = item.attrs
-                            break
-                        } // #ifndef H5 || APP-PLUS
-
-                        const style = item.attrs.style || ''
-
-                        if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || !styleObj.width.includes('%'))) {
-                            styleObj.width = '100% !important'
-                            styleObj.height = ''
-
-                            for (let j = i + 1; j < this.stack.length; j++) {
-                                this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')
-                            }
-                        } else if (style.includes('flex') && styleObj.width == '100%') {
-                            for (let _j = i + 1; _j < this.stack.length; _j++) {
-                                const _style = this.stack[_j].attrs.style || ''
-
-                                if (!_style.includes(';width') && !_style.includes(' width') && _style.indexOf('width') != 0) {
-                                    styleObj.width = ''
-                                    break
-                                }
-                            }
-                        } else if (style.includes('inline-block')) {
-                            if (styleObj.width && styleObj.width[styleObj.width.length - 1] == '%') {
-                                item.attrs.style += `;max-width:${styleObj.width}`
-                                styleObj.width = ''
-                            } else item.attrs.style += ';max-width:100%'
-                        } // #endif
-
-                        item.c = 1
-                    }
-
-                    attrs.i = this.imgList.length.toString()
-
-                    let _src = attrs['original-src'] || attrs.src // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360
-
-                    if (this.imgList.includes(_src)) {
-                        // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位
-                        let _i = _src.indexOf('://')
-
-                        if (_i != -1) {
-                            _i += 3
-
-                            let newSrc = _src.substr(0, _i)
-
-                            for (; _i < _src.length; _i++) {
-                                if (_src[_i] == '/') break
-                                newSrc += Math.random() > 0.5 ? _src[_i].toUpperCase() : _src[_i]
-                            }
-
-                            newSrc += _src.substr(_i)
-                            _src = newSrc
-                        }
-                    } // #endif
-
-                    this.imgList.push(_src) // #ifdef H5 || APP-PLUS
-
-                    if (this.options.lazyLoad) {
-                        attrs['data-src'] = attrs.src
-                        attrs.src = void 0
-                    } // #endif
-                }
+        // data url 图片如果没有设置 original-src 默认为不可预览的小图片
+        if (attrs.src.includes('data:') && !attrs['original-src']) {
+          attrs.ignore = 'T'
+        }
+        if (!attrs.ignore || node.webp || attrs.src.includes('cloud://')) {
+          for (let i = this.stack.length; i--;) {
+            const item = this.stack[i]
+            if (item.name === 'a') {
+              node.a = item.attrs
             }
-
-            if (styleObj.display == 'inline') styleObj.display = '' // #ifndef APP-PLUS-NVUE
-
-            if (attrs.ignore) {
-                styleObj['max-width'] = styleObj['max-width'] || '100%'
-                attrs.style += ';-webkit-touch-callout:none'
-            } // #endif
-            // 设置的宽度超出屏幕,为避免变形,高度转为自动
-
-            if (parseInt(styleObj.width) > windowWidth) styleObj.height = void 0 // 记录是否设置了宽高
-
-            if (styleObj.width) {
-                if (styleObj.width.includes('auto')) styleObj.width = ''; else {
-                    node.w = 'T'
-                    if (styleObj.height && !styleObj.height.includes('auto')) node.h = 'T'
+            if (item.name === 'table' && !node.webp && !attrs.src.includes('cloud://')) {
+              if (!styleObj.display || styleObj.display.includes('inline')) {
+                node.t = 'inline-block'
+              } else {
+                node.t = styleObj.display
+              }
+              styleObj.display = undefined
+            }
+            // #ifndef H5 || APP-PLUS
+            const style = item.attrs.style || ''
+            if (style.includes('flex:') && !style.includes('flex:0') && !style.includes('flex: 0') && (!styleObj.width || parseInt(styleObj.width) > 100)) {
+              styleObj.width = '100% !important'
+              styleObj.height = ''
+              for (let j = i + 1; j < this.stack.length; j++) {
+                this.stack[j].attrs.style = (this.stack[j].attrs.style || '').replace('inline-', '')
+              }
+            } else if (style.includes('flex') && styleObj.width === '100%') {
+              for (let j = i + 1; j < this.stack.length; j++) {
+                const style = this.stack[j].attrs.style || ''
+                if (!style.includes(';width') && !style.includes(' width') && style.indexOf('width') !== 0) {
+                  styleObj.width = ''
+                  break
                 }
+              }
+            } else if (style.includes('inline-block')) {
+              if (styleObj.width && styleObj.width[styleObj.width.length - 1] === '%') {
+                item.attrs.style += ';max-width:' + styleObj.width
+                styleObj.width = ''
+              } else {
+                item.attrs.style += ';max-width:100%'
+              }
             }
-        } else if (node.name == 'svg') {
-            siblings.push(node)
-            this.stack.push(node)
-            this.popNode()
-            return
-        }
-
-        for (const key in styleObj) {
-            if (styleObj[key]) attrs.style += ';'.concat(key, ':').concat(styleObj[key].replace(' !important', ''))
+            // #endif
+            item.c = 1
+          }
+          attrs.i = this.imgList.length.toString()
+          let src = attrs['original-src'] || attrs.src
+          // #ifndef H5 || MP-ALIPAY || APP-PLUS || MP-360
+          if (this.imgList.includes(src)) {
+            // 如果有重复的链接则对域名进行随机大小写变换避免预览时错位
+            let i = src.indexOf('://')
+            if (i !== -1) {
+              i += 3
+              let newSrc = src.substr(0, i)
+              for (; i < src.length; i++) {
+                if (src[i] === '/') break
+                newSrc += Math.random() > 0.5 ? src[i].toUpperCase() : src[i]
+              }
+              newSrc += src.substr(i)
+              src = newSrc
+            }
+          }
+          // #endif
+          this.imgList.push(src)
+          if (!node.t) {
+            this.imgList._unloadimgs += 1
+          }
+          // #ifdef H5 || APP-PLUS
+          if (this.options.lazyLoad) {
+            attrs['data-src'] = attrs.src
+            attrs.src = undefined
+          }
+          // #endif
         }
+      }
+      if (styleObj.display === 'inline') {
+        styleObj.display = ''
+      }
+      // #ifndef APP-PLUS-NVUE
+      if (attrs.ignore) {
+        styleObj['max-width'] = styleObj['max-width'] || '100%'
+        attrs.style += ';-webkit-touch-callout:none'
+      }
+      // #endif
+      // 设置的宽度超出屏幕,为避免变形,高度转为自动
+      if (parseInt(styleObj.width) > windowWidth) {
+        styleObj.height = undefined
+      }
+      // 记录是否设置了宽高
+      if (!isNaN(parseInt(styleObj.width))) {
+        node.w = 'T'
+      }
+      if (!isNaN(parseInt(styleObj.height)) && (!styleObj.height.includes('%') || (parent && (parent.attrs.style || '').includes('height')))) {
+        node.h = 'T'
+      }
+    } else if (node.name === 'svg') {
+      siblings.push(node)
+      this.stack.push(node)
+      this.popNode()
+      return
+    }
+    for (const key in styleObj) {
+      if (styleObj[key]) {
+        attrs.style += `;${key}:${styleObj[key].replace(' !important', '')}`
+      }
+    }
+    attrs.style = attrs.style.substr(1) || undefined
+    // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
+    if (!attrs.style) {
+      delete attrs.style
+    }
+    // #endif
+  } else {
+    if ((node.name === 'pre' || ((attrs.style || '').includes('white-space') && attrs.style.includes('pre'))) && this.pre !== 2) {
+      this.pre = node.pre = 1
+    }
+    node.children = []
+    this.stack.push(node)
+  }
 
-        attrs.style = attrs.style.substr(1) || void 0
-    } else {
-        if (node.name == 'pre' || (attrs.style || '').includes('white-space') && attrs.style.includes('pre')) this.pre = node.pre = true
-        node.children = []
-        this.stack.push(node)
-    } // 加入节点树
-
-    siblings.push(node)
+  // 加入节点树
+  siblings.push(node)
 }
+
 /**
  * @description 解析到标签结束
  * @param {String} name 标签名
  * @private
  */
-
-parser.prototype.onCloseTag = function (name) {
-    // 依次出栈到匹配为止
-    name = this.xml ? name : name.toLowerCase()
-    let i
-
-    for (i = this.stack.length; i--;) {
-        if (this.stack[i].name == name) break
-    }
-
-    if (i != -1) {
-        while (this.stack.length > i) {
-            this.popNode()
-        }
-    } else if (name == 'p' || name == 'br') {
-        const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
-        siblings.push({
-            name,
-            attrs: {}
-        })
+Parser.prototype.onCloseTag = function (name) {
+  // 依次出栈到匹配为止
+  name = this.xml ? name : name.toLowerCase()
+  let i
+  for (i = this.stack.length; i--;) {
+    if (this.stack[i].name === name) break
+  }
+  if (i !== -1) {
+    while (this.stack.length > i) {
+      this.popNode()
     }
+  } else if (name === 'p' || name === 'br') {
+    const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
+    siblings.push({
+      name,
+      attrs: {
+        class: tagSelector[name] || '',
+        style: this.tagStyle[name] || ''
+      }
+    })
+  }
 }
+
 /**
  * @description 处理标签出栈
  * @private
  */
-
-parser.prototype.popNode = function () {
-    const node = this.stack.pop()
-    let { attrs } = node
-    const { children } = node
-    const parent = this.stack[this.stack.length - 1]
-    const siblings = parent ? parent.children : this.nodes
-
-    if (!this.hook(node) || config.ignoreTags[node.name]) {
+Parser.prototype.popNode = function () {
+  const node = this.stack.pop()
+  let attrs = node.attrs
+  const children = node.children
+  const parent = this.stack[this.stack.length - 1]
+  const siblings = parent ? parent.children : this.nodes
+
+  if (!this.hook(node) || config.ignoreTags[node.name]) {
     // 获取标题
-        if (node.name == 'title' && children.length && children[0].type == 'text' && this.options.setTitle) {
-            uni.setNavigationBarTitle({
-                title: children[0].text
-            })
-        }
-        siblings.pop()
-        return
+    if (node.name === 'title' && children.length && children[0].type === 'text' && this.options.setTitle) {
+      uni.setNavigationBarTitle({
+        title: children[0].text
+      })
     }
+    siblings.pop()
+    return
+  }
 
-    if (node.pre) {
+  if (node.pre && this.pre !== 2) {
     // 是否合并空白符标识
-        node.pre = this.pre = void 0
-
-        for (let i = this.stack.length; i--;) {
-            if (this.stack[i].pre) this.pre = true
-        }
+    this.pre = node.pre = undefined
+    for (let i = this.stack.length; i--;) {
+      if (this.stack[i].pre) {
+        this.pre = 1
+      }
     }
+  }
 
-    const styleObj = {} // 转换 svg
-
-    if (node.name == 'svg') {
-    // #ifndef APP-PLUS-NVUE
-        let src = ''
-        const { style } = attrs
-        attrs.style = ''
-        attrs.xmlns = 'http://www.w3.org/2000/svg';
-
-        (function traversal(node) {
-            src += `<${node.name}`
-
-            for (let item in node.attrs) {
-                const val = node.attrs[item]
-
-                if (val) {
-                    if (item == 'viewbox') item = 'viewBox'
-                    src += ' '.concat(item, '="').concat(val, '"')
-                }
-            }
-
-            if (!node.children) src += '/>'; else {
-                src += '>'
-
-                for (let _i2 = 0; _i2 < node.children.length; _i2++) {
-                    traversal(node.children[_i2])
-                }
-
-                src += `</${node.name}>`
-            }
-        }(node))
-
-        node.name = 'img'
-        node.attrs = {
-            src: `data:image/svg+xml;utf8,${src.replace(/#/g, '%23')}`,
-            style,
-            ignore: 'T'
-        }
-        node.children = void 0 // #endif
-
-        this.xml = false
-        return
-    } // #ifndef APP-PLUS-NVUE
-    // 转换 align 属性
+  const styleObj = {}
 
-    if (attrs.align) {
-        if (node.name == 'table') {
-            if (attrs.align == 'center') styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'; else styleObj.float = attrs.align
-        } else styleObj['text-align'] = attrs.align
-
-        attrs.align = void 0
-    } // 转换 font 标签的属性
-
-    if (node.name == 'font') {
-        if (attrs.color) {
-            styleObj.color = attrs.color
-            attrs.color = void 0
+  // 转换 svg
+  if (node.name === 'svg') {
+    if (this.xml > 1) {
+      // 多层 svg 嵌套
+      this.xml--
+      return
+    }
+    // #ifdef APP-PLUS-NVUE
+    (function traversal (node) {
+      if (node.name) {
+        // 调整 svg 的大小写
+        node.name = config.svgDict[node.name] || node.name
+        for (const item in node.attrs) {
+          if (config.svgDict[item]) {
+            node.attrs[config.svgDict[item]] = node.attrs[item]
+            node.attrs[item] = undefined
+          }
         }
-
-        if (attrs.face) {
-            styleObj['font-family'] = attrs.face
-            attrs.face = void 0
+        for (let i = 0; i < (node.children || []).length; i++) {
+          traversal(node.children[i])
         }
-
-        if (attrs.size) {
-            let size = parseInt(attrs.size)
-
-            if (!isNaN(size)) {
-                if (size < 1) size = 1; else if (size > 7) size = 7
-                styleObj['font-size'] = ['xx-small', 'x-small', 'small', 'medium', 'large', 'x-large', 'xx-large'][size - 1]
-            }
-
-            attrs.size = void 0
+      }
+    })(node)
+    // #endif
+    // #ifndef APP-PLUS-NVUE
+    let src = ''
+    const style = attrs.style
+    attrs.style = ''
+    attrs.xmlns = 'http://www.w3.org/2000/svg';
+    (function traversal (node) {
+      if (node.type === 'text') {
+        src += node.text
+        return
+      }
+      const name = config.svgDict[node.name] || node.name
+      src += '<' + name
+      for (const item in node.attrs) {
+        const val = node.attrs[item]
+        if (val) {
+          src += ` ${config.svgDict[item] || item}="${val}"`
         }
-    } // #endif
-    // 一些编辑器的自带 class
-
-    if ((attrs.class || '').includes('align-center')) styleObj['text-align'] = 'center'
-    Object.assign(styleObj, this.parseStyle(node))
-
-    if (parseInt(styleObj.width) > windowWidth) {
-        styleObj['max-width'] = '100%'
-        styleObj['box-sizing'] = 'border-box'
-    } // #ifndef APP-PLUS-NVUE
-
-    if (config.blockTags[node.name]) node.name = 'div' // 未知标签转为 span,避免无法显示
-    else if (!config.trustTags[node.name] && !this.xml) node.name = 'span'
-    if (node.name == 'a' || node.name == 'ad' // #ifdef H5 || APP-PLUS
-  || node.name == 'iframe' // #endif
-    ) this.expose() // #ifdef APP-PLUS
-    else if (node.name == 'video') {
-        let str = '<video style="width:100%;height:100%"' // 空白图占位
-
-        if (!attrs.poster && !attrs.autoplay) attrs.poster = "data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg'/>"
-
-        for (const item in attrs) {
-            if (attrs[item]) str += ` ${item}="${attrs[item]}"`
+      }
+      if (!node.children) {
+        src += '/>'
+      } else {
+        src += '>'
+        for (let i = 0; i < node.children.length; i++) {
+          traversal(node.children[i])
         }
-
-        if (this.options.pauseVideo) str += ' onplay="for(var e=document.getElementsByTagName(\'video\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()"'
-        str += '>'
-
-        for (let _i3 = 0; _i3 < node.src.length; _i3++) {
-            str += `<source src="${node.src[_i3]}">`
+        src += '</' + name + '>'
+      }
+    })(node)
+    node.name = 'img'
+    node.attrs = {
+      src: 'data:image/svg+xml;utf8,' + src.replace(/#/g, '%23'),
+      style,
+      ignore: 'T'
+    }
+    node.children = undefined
+    // #endif
+    this.xml = false
+    return
+  }
+
+  // #ifndef APP-PLUS-NVUE
+  // 转换 align 属性
+  if (attrs.align) {
+    if (node.name === 'table') {
+      if (attrs.align === 'center') {
+        styleObj['margin-inline-start'] = styleObj['margin-inline-end'] = 'auto'
+      } else {
+        styleObj.float = attrs.align
+      }
+    } else {
+      styleObj['text-align'] = attrs.align
+    }
+    attrs.align = undefined
+  }
+
+  // 转换 dir 属性
+  if (attrs.dir) {
+    styleObj.direction = attrs.dir
+    attrs.dir = undefined
+  }
+
+  // 转换 font 标签的属性
+  if (node.name === 'font') {
+    if (attrs.color) {
+      styleObj.color = attrs.color
+      attrs.color = undefined
+    }
+    if (attrs.face) {
+      styleObj['font-family'] = attrs.face
+      attrs.face = undefined
+    }
+    if (attrs.size) {
+      let size = parseInt(attrs.size)
+      if (!isNaN(size)) {
+        if (size < 1) {
+          size = 1
+        } else if (size > 7) {
+          size = 7
         }
-
-        str += '</video>'
-        node.html = str
-    } // #endif
+        styleObj['font-size'] = ['x-small', 'small', 'medium', 'large', 'x-large', 'xx-large', 'xxx-large'][size - 1]
+      }
+      attrs.size = undefined
+    }
+  }
+  // #endif
+
+  // 一些编辑器的自带 class
+  if ((attrs.class || '').includes('align-center')) {
+    styleObj['text-align'] = 'center'
+  }
+
+  Object.assign(styleObj, this.parseStyle(node))
+
+  if (node.name !== 'table' && parseInt(styleObj.width) > windowWidth) {
+    styleObj['max-width'] = '100%'
+    styleObj['box-sizing'] = 'border-box'
+  }
+
+  // #ifndef APP-PLUS-NVUE
+  if (config.blockTags[node.name]) {
+    node.name = 'div'
+  } else if (!config.trustTags[node.name] && !this.xml) {
+    // 未知标签转为 span,避免无法显示
+    node.name = 'span'
+  }
+
+  if (node.name === 'a' || node.name === 'ad'
+    // #ifdef H5 || APP-PLUS
+    || node.name === 'iframe' // eslint-disable-line
+    // #endif
+  ) {
+    this.expose()
+  } else if (node.name === 'video') {
+    if ((styleObj.height || '').includes('auto')) {
+      styleObj.height = undefined
+    }
+    /* #ifdef APP-PLUS */
+    let str = '<video style="width:100%;height:100%"'
+    for (const item in attrs) {
+      if (attrs[item]) {
+        str += ' ' + item + '="' + attrs[item] + '"'
+      }
+    }
+    if (this.options.pauseVideo) {
+      str += ' onplay="this.dispatchEvent(new CustomEvent(\'vplay\',{bubbles:!0}));for(var e=document.getElementsByTagName(\'video\'),t=0;t<e.length;t++)e[t]!=this&&e[t].pause()"'
+    }
+    str += '>'
+    for (let i = 0; i < node.src.length; i++) {
+      str += '<source src="' + node.src[i] + '">'
+    }
+    str += '</video>'
+    node.html = str
+    /* #endif */
+  } else if ((node.name === 'ul' || node.name === 'ol') && node.c) {
     // 列表处理
-    else if ((node.name == 'ul' || node.name == 'ol') && node.c) {
-        const types = {
-            a: 'lower-alpha',
-            A: 'upper-alpha',
-            i: 'lower-roman',
-            I: 'upper-roman'
-        }
-
-        if (types[attrs.type]) {
-            attrs.style += `;list-style-type:${types[attrs.type]}`
-            attrs.type = void 0
-        }
-
-        for (let _i4 = children.length; _i4--;) {
-            if (children[_i4].name == 'li') children[_i4].c = 1
-        }
-    } // 表格处理
-    else if (node.name == 'table') {
-        // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现
-        let padding = parseFloat(attrs.cellpadding)
-        let spacing = parseFloat(attrs.cellspacing)
-        const border = parseFloat(attrs.border)
-
-        if (node.c) {
-            // padding 和 spacing 默认 2
-            if (isNaN(padding)) padding = 2
-            if (isNaN(spacing)) spacing = 2
+    const types = {
+      a: 'lower-alpha',
+      A: 'upper-alpha',
+      i: 'lower-roman',
+      I: 'upper-roman'
+    }
+    if (types[attrs.type]) {
+      attrs.style += ';list-style-type:' + types[attrs.type]
+      attrs.type = undefined
+    }
+    for (let i = children.length; i--;) {
+      if (children[i].name === 'li') {
+        children[i].c = 1
+      }
+    }
+  } else if (node.name === 'table') {
+    // 表格处理
+    // cellpadding、cellspacing、border 这几个常用表格属性需要通过转换实现
+    let padding = parseFloat(attrs.cellpadding)
+    let spacing = parseFloat(attrs.cellspacing)
+    const border = parseFloat(attrs.border)
+    const bordercolor = styleObj['border-color']
+    const borderstyle = styleObj['border-style']
+    if (node.c) {
+      // padding 和 spacing 默认 2
+      if (isNaN(padding)) {
+        padding = 2
+      }
+      if (isNaN(spacing)) {
+        spacing = 2
+      }
+    }
+    if (border) {
+      attrs.style += `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}`
+    }
+    if (node.flag && node.c) {
+      // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
+      styleObj.display = 'grid'
+      if (spacing) {
+        styleObj['grid-gap'] = spacing + 'px'
+        styleObj.padding = spacing + 'px'
+      } else if (border) {
+        // 无间隔的情况下避免边框重叠
+        attrs.style += ';border-left:0;border-top:0'
+      }
+
+      const width = [] // 表格的列宽
+      const trList = [] // tr 列表
+      const cells = [] // 保存新的单元格
+      const map = {}; // 被合并单元格占用的格子
+
+      (function traversal (nodes) {
+        for (let i = 0; i < nodes.length; i++) {
+          if (nodes[i].name === 'tr') {
+            trList.push(nodes[i])
+          } else {
+            traversal(nodes[i].children || [])
+          }
         }
-
-        if (border) attrs.style += `;border:${border}px solid gray`
-
-        if (node.flag && node.c) {
-            // 有 colspan 或 rowspan 且含有链接的表格通过 grid 布局实现
-            styleObj.display = 'grid'
-
-            if (spacing) {
-                styleObj['grid-gap'] = `${spacing}px`
-                styleObj.padding = `${spacing}px`
-            } // 无间隔的情况下避免边框重叠
-            else if (border) attrs.style += ';border-left:0;border-top:0'
-
-            const width = []
-            // 表格的列宽
-            const trList = []
-            // tr 列表
-            const cells = []
-            // 保存新的单元格
-            const map = {}; // 被合并单元格占用的格子
-
-            (function traversal(nodes) {
-                for (let _i5 = 0; _i5 < nodes.length; _i5++) {
-                    if (nodes[_i5].name == 'tr') trList.push(nodes[_i5]); else traversal(nodes[_i5].children || [])
-                }
-            }(children))
-
-            for (let row = 1; row <= trList.length; row++) {
-                let col = 1
-
-                for (let j = 0; j < trList[row - 1].children.length; j++, col++) {
-                    const td = trList[row - 1].children[j]
-
-                    if (td.name == 'td' || td.name == 'th') {
-                        // 这个格子被上面的单元格占用,则列号++
-                        while (map[`${row}.${col}`]) {
-                            col++
-                        }
-
-                        let _style2 = td.attrs.style || ''
-                        const start = _style2.indexOf('width') ? _style2.indexOf(';width') : 0 // 提取出 td 的宽度
-
-                        if (start != -1) {
-                            let end = _style2.indexOf(';', start + 6)
-
-                            if (end == -1) end = _style2.length
-                            if (!td.attrs.colspan) width[col] = _style2.substring(start ? start + 7 : 6, end)
-                            _style2 = _style2.substr(0, start) + _style2.substr(end)
-                        }
-
-                        _style2 += (border ? ';border:'.concat(border, 'px solid gray') + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? ';padding:'.concat(padding, 'px') : '') // 处理列合并
-
-                        if (td.attrs.colspan) {
-                            _style2 += ';grid-column-start:'.concat(col, ';grid-column-end:').concat(col + parseInt(td.attrs.colspan))
-                            if (!td.attrs.rowspan) _style2 += ';grid-row-start:'.concat(row, ';grid-row-end:').concat(row + 1)
-                            col += parseInt(td.attrs.colspan) - 1
-                        } // 处理行合并
-
-                        if (td.attrs.rowspan) {
-                            _style2 += ';grid-row-start:'.concat(row, ';grid-row-end:').concat(row + parseInt(td.attrs.rowspan))
-                            if (!td.attrs.colspan) _style2 += ';grid-column-start:'.concat(col, ';grid-column-end:').concat(col + 1) // 记录下方单元格被占用
-
-                            for (let k = 1; k < td.attrs.rowspan; k++) {
-                                map[`${row + k}.${col}`] = 1
-                            }
-                        }
-
-                        if (_style2) td.attrs.style = _style2
-                        cells.push(td)
-                    }
-                }
-
-                if (row == 1) {
-                    let temp = ''
-
-                    for (let _i6 = 1; _i6 < col; _i6++) {
-                        temp += `${width[_i6] ? width[_i6] : 'auto'} `
-                    }
-
-                    styleObj['grid-template-columns'] = temp
-                }
+      })(children)
+
+      for (let row = 1; row <= trList.length; row++) {
+        let col = 1
+        for (let j = 0; j < trList[row - 1].children.length; j++) {
+          const td = trList[row - 1].children[j]
+          if (td.name === 'td' || td.name === 'th') {
+            // 这个格子被上面的单元格占用,则列号++
+            while (map[row + '.' + col]) {
+              col++
             }
-
-            node.children = cells
-        } else {
-            // 没有使用合并单元格的表格通过 table 布局实现
-            if (node.c) styleObj.display = 'table'
-            if (!isNaN(spacing)) styleObj['border-spacing'] = `${spacing}px`
-
-            if (border || padding) {
-                // 遍历
-                (function traversal(nodes) {
-                    for (let _i7 = 0; _i7 < nodes.length; _i7++) {
-                        const _td = nodes[_i7]
-
-                        if (_td.name == 'th' || _td.name == 'td') {
-                            if (border) _td.attrs.style = 'border:'.concat(border, 'px solid gray;').concat(_td.attrs.style || '')
-                            if (padding) _td.attrs.style = 'padding:'.concat(padding, 'px;').concat(_td.attrs.style || '')
-                        } else if (_td.children) traversal(_td.children)
-                    }
-                }(children))
+            let style = td.attrs.style || ''
+            let start = style.indexOf('width') ? style.indexOf(';width') : 0
+            // 提取出 td 的宽度
+            if (start !== -1) {
+              let end = style.indexOf(';', start + 6)
+              if (end === -1) {
+                end = style.length
+              }
+              if (!td.attrs.colspan) {
+                width[col] = style.substring(start ? start + 7 : 6, end)
+              }
+              style = style.substr(0, start) + style.substr(end)
             }
-        } // 给表格添加一个单独的横向滚动层
-
-        if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {
-            const table = { ...node }
-            node.name = 'div'
-            node.attrs = {
-                style: 'overflow:auto'
+            // 设置竖直对齐
+            style += ';display:flex'
+            start = style.indexOf('vertical-align')
+            if (start !== -1) {
+              const val = style.substr(start + 15, 10)
+              if (val.includes('middle')) {
+                style += ';align-items:center'
+              } else if (val.includes('bottom')) {
+                style += ';align-items:flex-end'
+              }
+            } else {
+              style += ';align-items:center'
             }
-            node.children = [table]
-            attrs = table.attrs
-        }
-    } else if ((node.name == 'td' || node.name == 'th') && (attrs.colspan || attrs.rowspan)) {
-        for (let _i8 = this.stack.length; _i8--;) {
-            if (this.stack[_i8].name == 'table') {
-                this.stack[_i8].flag = 1 // 指示含有合并单元格
-
-                break
+            // 设置水平对齐
+            start = style.indexOf('text-align')
+            if (start !== -1) {
+              const val = style.substr(start + 11, 10)
+              if (val.includes('center')) {
+                style += ';justify-content: center'
+              } else if (val.includes('right')) {
+                style += ';justify-content: right'
+              }
             }
-        }
-    } // 转换 ruby
-    else if (node.name == 'ruby') {
-        node.name = 'span'
-
-        for (let _i9 = 0; _i9 < children.length - 1; _i9++) {
-            if (children[_i9].type == 'text' && children[_i9 + 1].name == 'rt') {
-                children[_i9] = {
-                    name: 'div',
-                    attrs: {
-                        style: 'display:inline-block'
-                    },
-                    children: [{
-                        name: 'div',
-                        attrs: {
-                            style: 'font-size:50%;text-align:start'
-                        },
-                        children: children[_i9 + 1].children
-                    }, children[_i9]]
+            style = (border ? `;border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'}` + (spacing ? '' : ';border-right:0;border-bottom:0') : '') + (padding ? `;padding:${padding}px` : '') + ';' + style
+            // 处理列合并
+            if (td.attrs.colspan) {
+              style += `;grid-column-start:${col};grid-column-end:${col + parseInt(td.attrs.colspan)}`
+              if (!td.attrs.rowspan) {
+                style += `;grid-row-start:${row};grid-row-end:${row + 1}`
+              }
+              col += parseInt(td.attrs.colspan) - 1
+            }
+            // 处理行合并
+            if (td.attrs.rowspan) {
+              style += `;grid-row-start:${row};grid-row-end:${row + parseInt(td.attrs.rowspan)}`
+              if (!td.attrs.colspan) {
+                style += `;grid-column-start:${col};grid-column-end:${col + 1}`
+              }
+              // 记录下方单元格被占用
+              for (let rowspan = 1; rowspan < td.attrs.rowspan; rowspan++) {
+                for (let colspan = 0; colspan < (td.attrs.colspan || 1); colspan++) {
+                  map[(row + rowspan) + '.' + (col - colspan)] = 1
                 }
-                children.splice(_i9 + 1, 1)
+              }
             }
+            if (style) {
+              td.attrs.style = style
+            }
+            cells.push(td)
+            col++
+          }
         }
-    } else if (node.c) {
-        node.c = 2
-
-        for (let _i10 = node.children.length; _i10--;) {
-            if (!node.children[_i10].c || node.children[_i10].name == 'table') node.c = 1
+        if (row === 1) {
+          let temp = ''
+          for (let i = 1; i < col; i++) {
+            temp += (width[i] ? width[i] : 'auto') + ' '
+          }
+          styleObj['grid-template-columns'] = temp
         }
-    }
-    if ((styleObj.display || '').includes('flex') && !node.c) {
-        for (let _i11 = children.length; _i11--;) {
-            const _item = children[_i11]
-
-            if (_item.f) {
-                _item.attrs.style = (_item.attrs.style || '') + _item.f
-                _item.f = void 0
+      }
+      node.children = cells
+    } else {
+      // 没有使用合并单元格的表格通过 table 布局实现
+      if (node.c) {
+        styleObj.display = 'table'
+      }
+      if (!isNaN(spacing)) {
+        styleObj['border-spacing'] = spacing + 'px'
+      }
+      if (border || padding) {
+        // 遍历
+        (function traversal (nodes) {
+          for (let i = 0; i < nodes.length; i++) {
+            const td = nodes[i]
+            if (td.name === 'th' || td.name === 'td') {
+              if (border) {
+                td.attrs.style = `border:${border}px ${borderstyle || 'solid'} ${bordercolor || 'gray'};${td.attrs.style || ''}`
+              }
+              if (padding) {
+                td.attrs.style = `padding:${padding}px;${td.attrs.style || ''}`
+              }
+            } else if (td.children) {
+              traversal(td.children)
             }
+          }
+        })(children)
+      }
+    }
+    // 给表格添加一个单独的横向滚动层
+    if (this.options.scrollTable && !(attrs.style || '').includes('inline')) {
+      const table = Object.assign({}, node)
+      node.name = 'div'
+      node.attrs = {
+        style: 'overflow:auto'
+      }
+      node.children = [table]
+      attrs = table.attrs
+    }
+  } else if ((node.name === 'td' || node.name === 'th') && (attrs.colspan || attrs.rowspan)) {
+    for (let i = this.stack.length; i--;) {
+      if (this.stack[i].name === 'table') {
+        this.stack[i].flag = 1 // 指示含有合并单元格
+        break
+      }
+    }
+  } else if (node.name === 'ruby') {
+    // 转换 ruby
+    node.name = 'span'
+    for (let i = 0; i < children.length - 1; i++) {
+      if (children[i].type === 'text' && children[i + 1].name === 'rt') {
+        children[i] = {
+          name: 'div',
+          attrs: {
+            style: 'display:inline-block;text-align:center'
+          },
+          children: [{
+            name: 'div',
+            attrs: {
+              style: 'font-size:50%;' + (children[i + 1].attrs.style || '')
+            },
+            children: children[i + 1].children
+          }, children[i]]
         }
-    } // flex 布局时部分样式需要提取到 rich-text 外层
-
-    const flex = parent && (parent.attrs.style || '').includes('flex') // #ifdef MP-WEIXIN
-  // 检查基础库版本 virtualHost 是否可用
-  && !(node.c && wx.getNFCAdapter) // #endif
-  // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
-  && !node.c // #endif
-
-    if (flex) node.f = ';max-width:100%' // #endif
-
-    for (const key in styleObj) {
-        if (styleObj[key]) {
-            const val = ';'.concat(key, ':').concat(styleObj[key].replace(' !important', '')) // #ifndef APP-PLUS-NVUE
-
-            if (flex && (key.includes('flex') && key != 'flex-direction' || key == 'align-self' || styleObj[key][0] == '-' || key == 'width' && val.includes('%'))) {
-                node.f += val
-                if (key == 'width') attrs.style += ';width:100%'
-            } else // #endif
-            { attrs.style += val }
+        children.splice(i + 1, 1)
+      }
+    }
+  } else if (node.c) {
+    (function traversal (node) {
+      node.c = 2
+      for (let i = node.children.length; i--;) {
+        const child = node.children[i]
+        // #ifdef (MP-WEIXIN || MP-QQ || APP-PLUS || MP-360) && VUE3
+        if (child.name && (config.inlineTags[child.name] || ((child.attrs.style || '').includes('inline') && child.children)) && !child.c) {
+          traversal(child)
         }
+        // #endif
+        if (!child.c || child.name === 'table') {
+          node.c = 1
+        }
+      }
+    })(node)
+  }
+
+  if ((styleObj.display || '').includes('flex') && !node.c) {
+    for (let i = children.length; i--;) {
+      const item = children[i]
+      if (item.f) {
+        item.attrs.style = (item.attrs.style || '') + item.f
+        item.f = undefined
+      }
     }
-
-    attrs.style = attrs.style.substr(1) || void 0
+  }
+  // flex 布局时部分样式需要提取到 rich-text 外层
+  const flex = parent && ((parent.attrs.style || '').includes('flex') || (parent.attrs.style || '').includes('grid'))
+    // #ifdef MP-WEIXIN
+    // 检查基础库版本 virtualHost 是否可用
+    && !(node.c && wx.getNFCAdapter) // eslint-disable-line
+    // #endif
+    // #ifndef MP-WEIXIN || MP-QQ || MP-BAIDU || MP-TOUTIAO
+    && !node.c // eslint-disable-line
+  // #endif
+  if (flex) {
+    node.f = ';max-width:100%'
+  }
+
+  if (children.length >= 50 && node.c && !(styleObj.display || '').includes('flex')) {
+    mergeNodes(children)
+  }
+  // #endif
+
+  for (const key in styleObj) {
+    if (styleObj[key]) {
+      const val = `;${key}:${styleObj[key].replace(' !important', '')}`
+      /* #ifndef APP-PLUS-NVUE */
+      if (flex && ((key.includes('flex') && key !== 'flex-direction') || key === 'align-self' || key.includes('grid') || styleObj[key][0] === '-' || (key.includes('width') && val.includes('%')))) {
+        node.f += val
+        if (key === 'width') {
+          attrs.style += ';width:100%'
+        }
+      } else /* #endif */ {
+        attrs.style += val
+      }
+    }
+  }
+  attrs.style = attrs.style.substr(1) || undefined
+  // #ifdef (MP-WEIXIN || MP-QQ) && VUE3
+  for (const key in attrs) {
+    if (!attrs[key]) {
+      delete attrs[key]
+    }
+  }
+  // #endif
 }
+
 /**
  * @description 解析到文本
  * @param {String} text 文本内容
  */
-
-parser.prototype.onText = function (text) {
-    if (!this.pre) {
+Parser.prototype.onText = function (text) {
+  if (!this.pre) {
     // 合并空白符
-        let trim = ''
-        let flag
-
-        for (let i = 0, len = text.length; i < len; i++) {
-            if (!blankChar[text[i]]) trim += text[i]; else {
-                if (trim[trim.length - 1] != ' ') trim += ' '
-                if (text[i] == '\n' && !flag) flag = true
-            }
-        } // 去除含有换行符的空串
-
-        if (trim == ' ' && flag) return
-        text = trim
+    let trim = ''
+    let flag
+    for (let i = 0, len = text.length; i < len; i++) {
+      if (!blankChar[text[i]]) {
+        trim += text[i]
+      } else {
+        if (trim[trim.length - 1] !== ' ') {
+          trim += ' '
+        }
+        if (text[i] === '\n' && !flag) {
+          flag = true
+        }
+      }
     }
-
-    const node = Object.create(null)
-    node.type = 'text'
-    node.text = decodeEntity(text)
-
-    if (this.hook(node)) {
-        const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
-        siblings.push(node)
+    // 去除含有换行符的空串
+    if (trim === ' ') {
+      if (flag) return
+      // #ifdef VUE3
+      else {
+        const parent = this.stack[this.stack.length - 1]
+        if (parent && parent.name[0] === 't') return
+      }
+      // #endif
     }
+    text = trim
+  }
+  const node = Object.create(null)
+  node.type = 'text'
+  // #ifdef (MP-BAIDU || MP-ALIPAY || MP-TOUTIAO) && VUE3
+  node.attrs = {}
+  // #endif
+  node.text = decodeEntity(text)
+  if (this.hook(node)) {
+    // #ifdef MP-WEIXIN
+    if (this.options.selectable === 'force' && system.includes('iOS') && !uni.canIUse('rich-text.user-select')) {
+      this.expose()
+    }
+    // #endif
+    const siblings = this.stack.length ? this.stack[this.stack.length - 1].children : this.nodes
+    siblings.push(node)
+  }
 }
+
 /**
  * @description html 词法分析器
  * @param {Object} handler 高层处理器
  */
-
-function lexer(handler) {
-    this.handler = handler
+function Lexer (handler) {
+  this.handler = handler
 }
+
 /**
  * @description 执行解析
  * @param {String} content 要解析的文本
  */
-
-lexer.prototype.parse = function (content) {
-    this.content = content || ''
-    this.i = 0 // 标记解析位置
-
-    this.start = 0 // 标记一个单词的开始位置
-
-    this.state = this.text // 当前状态
-
-    for (let len = this.content.length; this.i != -1 && this.i < len;) {
-        this.state()
-    }
+Lexer.prototype.parse = function (content) {
+  this.content = content || ''
+  this.i = 0 // 标记解析位置
+  this.start = 0 // 标记一个单词的开始位置
+  this.state = this.text // 当前状态
+  for (let len = this.content.length; this.i !== -1 && this.i < len;) {
+    this.state()
+  }
 }
+
 /**
  * @description 检查标签是否闭合
  * @param {String} method 如果闭合要进行的操作
  * @returns {Boolean} 是否闭合
  * @private
  */
-
-lexer.prototype.checkClose = function (method) {
-    const selfClose = this.content[this.i] == '/'
-
-    if (this.content[this.i] == '>' || selfClose && this.content[this.i + 1] == '>') {
-        if (method) this.handler[method](this.content.substring(this.start, this.i))
-        this.i += selfClose ? 2 : 1
+Lexer.prototype.checkClose = function (method) {
+  const selfClose = this.content[this.i] === '/'
+  if (this.content[this.i] === '>' || (selfClose && this.content[this.i + 1] === '>')) {
+    if (method) {
+      this.handler[method](this.content.substring(this.start, this.i))
+    }
+    this.i += selfClose ? 2 : 1
+    this.start = this.i
+    this.handler.onOpenTag(selfClose)
+    if (this.handler.tagName === 'script') {
+      this.i = this.content.indexOf('</', this.i)
+      if (this.i !== -1) {
+        this.i += 2
         this.start = this.i
-        this.handler.onOpenTag(selfClose)
-
-        if (this.handler.tagName == 'script') {
-            this.i = this.content.indexOf('</', this.i)
-
-            if (this.i != -1) {
-                this.i += 2
-                this.start = this.i
-            }
-
-            this.state = this.endTag
-        } else this.state = this.text
-
-        return true
+      }
+      this.state = this.endTag
+    } else {
+      this.state = this.text
     }
-
-    return false
+    return true
+  }
+  return false
 }
+
 /**
  * @description 文本状态
  * @private
  */
-
-lexer.prototype.text = function () {
-    this.i = this.content.indexOf('<', this.i) // 查找最近的标签
-
-    if (this.i == -1) {
+Lexer.prototype.text = function () {
+  this.i = this.content.indexOf('<', this.i) // 查找最近的标签
+  if (this.i === -1) {
     // 没有标签了
-        if (this.start < this.content.length) this.handler.onText(this.content.substring(this.start, this.content.length))
-        return
+    if (this.start < this.content.length) {
+      this.handler.onText(this.content.substring(this.start, this.content.length))
     }
-
-    const c = this.content[this.i + 1]
-
-    if (c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z') {
+    return
+  }
+  const c = this.content[this.i + 1]
+  if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) {
     // 标签开头
-        if (this.start != this.i) this.handler.onText(this.content.substring(this.start, this.i))
-        this.start = ++this.i
-        this.state = this.tagName
-    } else if (c == '/' || c == '!' || c == '?') {
-        if (this.start != this.i) this.handler.onText(this.content.substring(this.start, this.i))
-        const next = this.content[this.i + 2]
-
-        if (c == '/' && (next >= 'a' && next <= 'z' || next >= 'A' && next <= 'Z')) {
-            // 标签结尾
-            this.i += 2
-            this.start = this.i
-            return this.state = this.endTag
-        } // 处理注释
-
-        let end = '-->'
-        if (c != '!' || this.content[this.i + 2] != '-' || this.content[this.i + 3] != '-') end = '>'
-        this.i = this.content.indexOf(end, this.i)
-
-        if (this.i != -1) {
-            this.i += end.length
-            this.start = this.i
-        }
-    } else this.i++
+    if (this.start !== this.i) {
+      this.handler.onText(this.content.substring(this.start, this.i))
+    }
+    this.start = ++this.i
+    this.state = this.tagName
+  } else if (c === '/' || c === '!' || c === '?') {
+    if (this.start !== this.i) {
+      this.handler.onText(this.content.substring(this.start, this.i))
+    }
+    const next = this.content[this.i + 2]
+    if (c === '/' && ((next >= 'a' && next <= 'z') || (next >= 'A' && next <= 'Z'))) {
+      // 标签结尾
+      this.i += 2
+      this.start = this.i
+      this.state = this.endTag
+      return
+    }
+    // 处理注释
+    let end = '-->'
+    if (c !== '!' || this.content[this.i + 2] !== '-' || this.content[this.i + 3] !== '-') {
+      end = '>'
+    }
+    this.i = this.content.indexOf(end, this.i)
+    if (this.i !== -1) {
+      this.i += end.length
+      this.start = this.i
+    }
+  } else {
+    this.i++
+  }
 }
+
 /**
  * @description 标签名状态
  * @private
  */
-
-lexer.prototype.tagName = function () {
-    if (blankChar[this.content[this.i]]) {
+Lexer.prototype.tagName = function () {
+  if (blankChar[this.content[this.i]]) {
     // 解析到标签名
-        this.handler.onTagName(this.content.substring(this.start, this.i))
-
-        while (blankChar[this.content[++this.i]]) {
-
-        }
-
-        if (this.i < this.content.length && !this.checkClose()) {
-            this.start = this.i
-            this.state = this.attrName
-        }
-    } else if (!this.checkClose('onTagName')) this.i++
+    this.handler.onTagName(this.content.substring(this.start, this.i))
+    while (blankChar[this.content[++this.i]]);
+    if (this.i < this.content.length && !this.checkClose()) {
+      this.start = this.i
+      this.state = this.attrName
+    }
+  } else if (!this.checkClose('onTagName')) {
+    this.i++
+  }
 }
+
 /**
  * @description 属性名状态
  * @private
  */
-
-lexer.prototype.attrName = function () {
-    let c = this.content[this.i]
-
-    if (blankChar[c] || c == '=') {
+Lexer.prototype.attrName = function () {
+  let c = this.content[this.i]
+  if (blankChar[c] || c === '=') {
     // 解析到属性名
-        this.handler.onAttrName(this.content.substring(this.start, this.i))
-        let needVal = c == '='
-        const len = this.content.length
-
-        while (++this.i < len) {
-            c = this.content[this.i]
-
-            if (!blankChar[c]) {
-                if (this.checkClose()) return
-
-                if (needVal) {
-                    // 等号后遇到第一个非空字符
-                    this.start = this.i
-                    return this.state = this.attrVal
-                }
-
-                if (this.content[this.i] == '=') needVal = true; else {
-                    this.start = this.i
-                    return this.state = this.attrName
-                }
-            }
+    this.handler.onAttrName(this.content.substring(this.start, this.i))
+    let needVal = c === '='
+    const len = this.content.length
+    while (++this.i < len) {
+      c = this.content[this.i]
+      if (!blankChar[c]) {
+        if (this.checkClose()) return
+        if (needVal) {
+          // 等号后遇到第一个非空字符
+          this.start = this.i
+          this.state = this.attrVal
+          return
+        }
+        if (this.content[this.i] === '=') {
+          needVal = true
+        } else {
+          this.start = this.i
+          this.state = this.attrName
+          return
         }
-    } else if (!this.checkClose('onAttrName')) this.i++
+      }
+    }
+  } else if (!this.checkClose('onAttrName')) {
+    this.i++
+  }
 }
+
 /**
  * @description 属性值状态
  * @private
  */
-
-lexer.prototype.attrVal = function () {
-    const c = this.content[this.i]
-    const len = this.content.length // 有冒号的属性
-
-    if (c == '"' || c == "'") {
-        this.start = ++this.i
-        this.i = this.content.indexOf(c, this.i)
-        if (this.i == -1) return
+Lexer.prototype.attrVal = function () {
+  const c = this.content[this.i]
+  const len = this.content.length
+  if (c === '"' || c === "'") {
+    // 有冒号的属性
+    this.start = ++this.i
+    this.i = this.content.indexOf(c, this.i)
+    if (this.i === -1) return
+    this.handler.onAttrVal(this.content.substring(this.start, this.i))
+  } else {
+    // 没有冒号的属性
+    for (; this.i < len; this.i++) {
+      if (blankChar[this.content[this.i]]) {
         this.handler.onAttrVal(this.content.substring(this.start, this.i))
-    } // 没有冒号的属性
-    else {
-        for (; this.i < len; this.i++) {
-            if (blankChar[this.content[this.i]]) {
-                this.handler.onAttrVal(this.content.substring(this.start, this.i))
-                break
-            } else if (this.checkClose('onAttrVal')) return
-        }
-    }
-
-    while (blankChar[this.content[++this.i]]) {
-
-    }
-
-    if (this.i < len && !this.checkClose()) {
-        this.start = this.i
-        this.state = this.attrName
+        break
+      } else if (this.checkClose('onAttrVal')) return
     }
+  }
+  while (blankChar[this.content[++this.i]]);
+  if (this.i < len && !this.checkClose()) {
+    this.start = this.i
+    this.state = this.attrName
+  }
 }
+
 /**
  * @description 结束标签状态
  * @returns {String} 结束的标签名
  * @private
  */
-
-lexer.prototype.endTag = function () {
-    const c = this.content[this.i]
-
-    if (blankChar[c] || c == '>' || c == '/') {
-        this.handler.onCloseTag(this.content.substring(this.start, this.i))
-
-        if (c != '>') {
-            this.i = this.content.indexOf('>', this.i)
-            if (this.i == -1) return
-        }
-
-        this.start = ++this.i
-        this.state = this.text
-    } else this.i++
+Lexer.prototype.endTag = function () {
+  const c = this.content[this.i]
+  if (blankChar[c] || c === '>' || c === '/') {
+    this.handler.onCloseTag(this.content.substring(this.start, this.i))
+    if (c !== '>') {
+      this.i = this.content.indexOf('>', this.i)
+      if (this.i === -1) return
+    }
+    this.start = ++this.i
+    this.state = this.text
+  } else {
+    this.i++
+  }
 }
 
-module.exports = parser
+export default Parser

+ 4 - 3
src/uni_modules/uview-plus/components/u-parse/props.js

@@ -1,9 +1,10 @@
 import defprops from '../../libs/config/props';
 export default {
     props: {
-        // #ifdef APP-PLUS-NVUE
-        bgColor: String,
-        // #endif
+		containerStyle: {
+          type: String,
+          default: null
+		},
         content: String,
         copyLink: {
 		  type: Boolean,

+ 461 - 328
src/uni_modules/uview-plus/components/u-parse/u-parse.vue

@@ -1,352 +1,482 @@
 <template>
-  <view id="_root" :class="(selectable?'_select ':'')+'_root'">
-    <slot v-if="!nodes[0]" />
-    <!-- #ifndef APP-PLUS-NVUE -->
-    <node v-else :childs="nodes" :opts="[lazyLoad,loadingImg,errorImg,showImgMenu]" />
-    <!-- #endif -->
-    <!-- #ifdef APP-PLUS-NVUE -->
-    <web-view ref="web" src="/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
-    <!-- #endif -->
-  </view>
+	<view id="_root" :class="(selectable ? '_select ' : '') + '_root'" :style="containerStyle">
+		<slot v-if="!nodes[0]" />
+		<!-- #ifndef APP-PLUS-NVUE -->
+		<node v-else :childs="nodes" :opts="[lazyLoad, loadingImg, errorImg, showImgMenu, selectable]" name="span" />
+		<!-- #endif -->
+		<!-- #ifdef APP-PLUS-NVUE -->
+		<web-view ref="web" src="/static/app-plus/mp-html/local.html" :style="'margin-top:-2px;height:' + height + 'px'" @onPostMessage="_onMessage" />
+		<!-- #endif -->
+	</view>
 </template>
 
 <script>
-	import props from './props.js';
 /**
- * mp-html v2.0.4
+ * mp-html v2.4.1
  * @description 富文本组件
  * @tutorial https://github.com/jin-yufeng/mp-html
- * @property {String}			bgColor		背景颜色,只适用与APP-PLUS-NVUE
- * @property {String}			content		用于渲染的富文本字符串(默认 true )
- * @property {Boolean}			copyLink	是否允许外部链接被点击时自动复制
- * @property {String}			domain		主域名,用于拼接链接
- * @property {String}			errorImg	图片出错时的占位图链接
- * @property {Boolean}			lazyLoad	是否开启图片懒加载(默认 true )
- * @property {string}			loadingImg	图片加载过程中的占位图链接
- * @property {Boolean}			pauseVideo	是否在播放一个视频时自动暂停其它视频(默认 true )
- * @property {Boolean}			previewImg	是否允许图片被点击时自动预览(默认 true )
- * @property {Boolean}			scrollTable	是否给每个表格添加一个滚动层使其能单独横向滚动
- * @property {Boolean}			selectable	是否开启长按复制
- * @property {Boolean}			setTitle	是否将 title 标签的内容设置到页面标题(默认 true )
- * @property {Boolean}			showImgMenu	是否允许图片被长按时显示菜单(默认 true )
- * @property {Object}			tagStyle	标签的默认样式
- * @property {Boolean | Number}	useAnchor	是否使用锚点链接
- * 
- * @event {Function}	load	dom 结构加载完毕时触发
- * @event {Function}	ready	所有图片加载完毕时触发
- * @event {Function}	imgTap	图片被点击时触发
- * @event {Function}	linkTap	链接被点击时触发
- * @event {Function}	error	媒体加载出错时触发
+ * @property {String} container-style 容器的样式
+ * @property {String} content 用于渲染的 html 字符串
+ * @property {Boolean} copy-link 是否允许外部链接被点击时自动复制
+ * @property {String} domain 主域名,用于拼接链接
+ * @property {String} error-img 图片出错时的占位图链接
+ * @property {Boolean} lazy-load 是否开启图片懒加载
+ * @property {string} loading-img 图片加载过程中的占位图链接
+ * @property {Boolean} pause-video 是否在播放一个视频时自动暂停其他视频
+ * @property {Boolean} preview-img 是否允许图片被点击时自动预览
+ * @property {Boolean} scroll-table 是否给每个表格添加一个滚动层使其能单独横向滚动
+ * @property {Boolean | String} selectable 是否开启长按复制
+ * @property {Boolean} set-title 是否将 title 标签的内容设置到页面标题
+ * @property {Boolean} show-img-menu 是否允许图片被长按时显示菜单
+ * @property {Object} tag-style 标签的默认样式
+ * @property {Boolean | Number} use-anchor 是否使用锚点链接
+ * @event {Function} load dom 结构加载完毕时触发
+ * @event {Function} ready 所有图片加载完毕时触发
+ * @event {Function} imgtap 图片被点击时触发
+ * @event {Function} linktap 链接被点击时触发
+ * @event {Function} play 音视频播放时触发
+ * @event {Function} error 媒体加载出错时触发
  */
-const plugins=[]
-const parser = import('./parser')
 // #ifndef APP-PLUS-NVUE
 import node from './node/node'
 // #endif
+import Parser from './parser'
+const plugins = []
 // #ifdef APP-PLUS-NVUE
 const dom = weex.requireModule('dom')
 // #endif
 export default {
-  name: 'mp-html',
-  data() {
-    return {
-      nodes: [],
-      // #ifdef APP-PLUS-NVUE
-      height: 0
-      // #endif
-    }
-  },
-  mixins:[props],
-  // #ifndef APP-PLUS-NVUE
-  components: {
-    node
-  },
-  // #endif
-  watch: {
-    content(content) {
-      this.setContent(content)
-    }
-  },
-  created() {
-    this.plugins = []
-    for (let i = plugins.length; i--;)
-      this.plugins.push(new plugins[i](this))
-  },
-  mounted() {
-    if (this.content && !this.nodes.length)
-      this.setContent(this.content)
-  },
-  beforeDestroy() {
-    this._hook('onDetached')
-    clearInterval(this._timer)
-  },
-  methods: {
-    /**
-     * @description 将锚点跳转的范围限定在一个 scroll-view 内
-     * @param {Object} page scroll-view 所在页面的示例
-     * @param {String} selector scroll-view 的选择器
-     * @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
-     */
-    in(page, selector, scrollTop) {
-      // #ifndef APP-PLUS-NVUE
-      if (page && selector && scrollTop)
-        this._in = {
-          page,
-          selector,
-          scrollTop
-        }
-      // #endif
-    },
+	name: 'u-parse',
+	data() {
+		return {
+			nodes: [],
+			// #ifdef APP-PLUS-NVUE
+			height: 3
+			// #endif
+		}
+	},
+	props: {
+		containerStyle: {
+			type: String,
+			default: ''
+		},
+		content: {
+			type: String,
+			default: ''
+		},
+		copyLink: {
+			type: [Boolean, String],
+			default: true
+		},
+		domain: String,
+		errorImg: {
+			type: String,
+			default: ''
+		},
+		lazyLoad: {
+			type: [Boolean, String],
+			default: false
+		},
+		loadingImg: {
+			type: String,
+			default: ''
+		},
+		pauseVideo: {
+			type: [Boolean, String],
+			default: true
+		},
+		previewImg: {
+			type: [Boolean, String],
+			default: true
+		},
+		scrollTable: [Boolean, String],
+		selectable: [Boolean, String],
+		setTitle: {
+			type: [Boolean, String],
+			default: true
+		},
+		showImgMenu: {
+			type: [Boolean, String],
+			default: true
+		},
+		tagStyle: Object,
+		useAnchor: [Boolean, Number]
+	},
+	// #ifdef VUE3
+	emits: ['load', 'ready', 'imgtap', 'linktap', 'play', 'error'],
+	// #endif
+	// #ifndef APP-PLUS-NVUE
+	components: {
+		node
+	},
+	// #endif
+	watch: {
+		content(content) {
+			this.setContent(content)
+		}
+	},
+	created() {
+		this.plugins = []
+		for (let i = plugins.length; i--;) {
+			this.plugins.push(new plugins[i](this))
+		}
+	},
+	mounted() {
+		if (this.content && !this.nodes.length) {
+			this.setContent(this.content)
+		}
+	},
+	beforeDestroy() {
+		this._hook('onDetached')
+	},
+	methods: {
+		/**
+		 * @description 将锚点跳转的范围限定在一个 scroll-view 内
+		 * @param {Object} page scroll-view 所在页面的示例
+		 * @param {String} selector scroll-view 的选择器
+		 * @param {String} scrollTop scroll-view scroll-top 属性绑定的变量名
+		 */
+		in(page, selector, scrollTop) {
+			// #ifndef APP-PLUS-NVUE
+			if (page && selector && scrollTop) {
+				this._in = {
+					page,
+					selector,
+					scrollTop
+				}
+			}
+			// #endif
+		},
 
-    /**
-     * @description 锚点跳转
-     * @param {String} id 要跳转的锚点 id
-     * @param {Number} offset 跳转位置的偏移量
-     * @returns {Promise}
-     */
-    navigateTo(id, offset) {
-      return new Promise((resolve, reject) => {
-        if (!this.useAnchor)
-          return reject('Anchor is disabled')
-        offset = offset || parseInt(this.useAnchor) || 0
-        // #ifdef APP-PLUS-NVUE
-        if (!id) {
-          dom.scrollToElement(this.$refs.web, {
-            offset
-          })
-          resolve()
-        } else {
-          this._navigateTo = {
-            resolve,
-            reject,
-            offset
-          }
-          this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
-        }
-        // #endif
-        // #ifndef APP-PLUS-NVUE
-        let deep = ' '
-        // #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
-        deep = '>>>'
-        // #endif
-        const selector = uni.createSelectorQuery()
-          // #ifndef MP-ALIPAY
-          .in(this._in ? this._in.page : this)
-          // #endif
-          .select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
-        if (this._in)
-          selector.select(this._in.selector).scrollOffset()
-            .select(this._in.selector).boundingClientRect() // 获取 scroll-view 的位置和滚动距离
-        else
-          selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
-        selector.exec(res => {
-          if (!res[0])
-            return reject('Label not found')
-          const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
-          if (this._in)
-            // scroll-view 跳转
-            this._in.page[this._in.scrollTop] = scrollTop
-          else
-            // 页面跳转
-            uni.pageScrollTo({
-              scrollTop,
-              duration: 300
-            })
-          resolve()
-        })
-        // #endif
-      })
-    },
+		/**
+		 * @description 锚点跳转
+		 * @param {String} id 要跳转的锚点 id
+		 * @param {Number} offset 跳转位置的偏移量
+		 * @returns {Promise}
+		 */
+		navigateTo(id, offset) {
+			return new Promise((resolve, reject) => {
+				if (!this.useAnchor) {
+					reject(Error('Anchor is disabled'))
+					return
+				}
+				offset = offset || parseInt(this.useAnchor) || 0
+				// #ifdef APP-PLUS-NVUE
+				if (!id) {
+					dom.scrollToElement(this.$refs.web, {
+						offset
+					})
+					resolve()
+				} else {
+					this._navigateTo = {
+						resolve,
+						reject,
+						offset
+					}
+					this.$refs.web.evalJs('uni.postMessage({data:{action:"getOffset",offset:(document.getElementById(' + id + ')||{}).offsetTop}})')
+				}
+				// #endif
+				// #ifndef APP-PLUS-NVUE
+				let deep = ' '
+				// #ifdef MP-WEIXIN || MP-QQ || MP-TOUTIAO
+				deep = '>>>'
+				// #endif
+				const selector = uni.createSelectorQuery()
+					// #ifndef MP-ALIPAY
+					.in(this._in ? this._in.page : this)
+					// #endif
+					.select((this._in ? this._in.selector : '._root') + (id ? `${deep}#${id}` : '')).boundingClientRect()
+				if (this._in) {
+					selector.select(this._in.selector).scrollOffset()
+						.select(this._in.selector).boundingClientRect()
+				} else {
+					// 获取 scroll-view 的位置和滚动距离
+					selector.selectViewport().scrollOffset() // 获取窗口的滚动距离
+				}
+				selector.exec(res => {
+					if (!res[0]) {
+						reject(Error('Label not found'))
+						return
+					}
+					const scrollTop = res[1].scrollTop + res[0].top - (res[2] ? res[2].top : 0) + offset
+					if (this._in) {
+						// scroll-view 跳转
+						this._in.page[this._in.scrollTop] = scrollTop
+					} else {
+						// 页面跳转
+						uni.pageScrollTo({
+							scrollTop,
+							duration: 300
+						})
+					}
+					resolve()
+				})
+				// #endif
+			})
+		},
 
-    /**
-     * @description 获取文本内容
-     * @return {String}
-     */
-    getText() {
-      let text = '';
-      (function traversal(nodes) {
-        for (let i = 0; i < nodes.length; i++) {
-          const node = nodes[i]
-          if (node.type == 'text')
-            text += node.text.replace(/&amp;/g, '&')
-          else if (node.name == 'br')
-            text += '\n'
-          else {
-            // 块级标签前后加换行
-            const isBlock = node.name == 'p' || node.name == 'div' || node.name == 'tr' || node.name == 'li' || (node.name[0] == 'h' && node.name[1] > '0' && node.name[1] < '7')
-            if (isBlock && text && text[text.length - 1] != '\n')
-              text += '\n'
-            // 递归获取子节点的文本
-            if (node.children)
-              traversal(node.children)
-            if (isBlock && text[text.length - 1] != '\n')
-              text += '\n'
-            else if (node.name == 'td' || node.name == 'th')
-              text += '\t'
-          }
-        }
-      })(this.nodes)
-      return text
-    },
+		/**
+		 * @description 获取文本内容
+		 * @return {String}
+		 */
+		getText(nodes) {
+			let text = '';
+			(function traversal(nodes) {
+				for (let i = 0; i < nodes.length; i++) {
+					const node = nodes[i]
+					if (node.type === 'text') {
+						text += node.text.replace(/&amp;/g, '&')
+					} else if (node.name === 'br') {
+						text += '\n'
+					} else {
+						// 块级标签前后加换行
+						const isBlock = node.name === 'p' || node.name === 'div' || node.name === 'tr' || node.name === 'li' || (node.name[0] === 'h' && node.name[1] > '0' && node.name[1] < '7')
+						if (isBlock && text && text[text.length - 1] !== '\n') {
+							text += '\n'
+						}
+						// 递归获取子节点的文本
+						if (node.children) {
+							traversal(node.children)
+						}
+						if (isBlock && text[text.length - 1] !== '\n') {
+							text += '\n'
+						} else if (node.name === 'td' || node.name === 'th') {
+							text += '\t'
+						}
+					}
+				}
+			})(nodes || this.nodes)
+			return text
+		},
 
-    /**
-     * @description 获取内容大小和位置
-     * @return {Promise}
-     */
-    getRect() {
-      return new Promise((resolve, reject) => {
-        uni.createSelectorQuery()
-          // #ifndef MP-ALIPAY
-          .in(this)
-          // #endif
-          .select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject('Root label not found'))
-      })
-    },
+		/**
+		 * @description 获取内容大小和位置
+		 * @return {Promise}
+		 */
+		getRect() {
+			return new Promise((resolve, reject) => {
+				uni.createSelectorQuery()
+					// #ifndef MP-ALIPAY
+					.in(this)
+					// #endif
+					.select('#_root').boundingClientRect().exec(res => res[0] ? resolve(res[0]) : reject(Error('Root label not found')))
+			})
+		},
 
-    /**
-     * @description 设置内容
-     * @param {String} content html 内容
-     * @param {Boolean} append 是否在尾部追加
-     */
-    setContent(content, append) {
-      if (!append || !this.imgList)
-        this.imgList = []
-      const nodes = new parser(this).parse(content)
-      // #ifdef APP-PLUS-NVUE
-      if (this._ready)
-        this._set(nodes, append)
-      // #endif
-      this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
+		/**
+		 * @description 暂停播放媒体
+		 */
+		pauseMedia() {
+			for (let i = (this._videos || []).length; i--;) {
+				this._videos[i].pause()
+			}
+			// #ifdef APP-PLUS
+			const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].pause()'
+			// #ifndef APP-PLUS-NVUE
+			let page = this.$parent
+			while (!page.$scope) page = page.$parent
+			page.$scope.$getAppWebview().evalJS(command)
+			// #endif
+			// #ifdef APP-PLUS-NVUE
+			this.$refs.web.evalJs(command)
+			// #endif
+			// #endif
+		},
 
-      // #ifndef APP-PLUS-NVUE
-      this._videos = []
-      this.$nextTick(() => {
-        this._hook('onLoad')
-        this.$emit('load')
-      })
+		/**
+		 * @description 设置媒体播放速率
+		 * @param {Number} rate 播放速率
+		 */
+		setPlaybackRate(rate) {
+			this.playbackRate = rate
+			for (let i = (this._videos || []).length; i--;) {
+				this._videos[i].playbackRate(rate)
+			}
+			// #ifdef APP-PLUS
+			const command = 'for(var e=document.getElementsByTagName("video"),i=e.length;i--;)e[i].playbackRate=' + rate
+			// #ifndef APP-PLUS-NVUE
+			let page = this.$parent
+			while (!page.$scope) page = page.$parent
+			page.$scope.$getAppWebview().evalJS(command)
+			// #endif
+			// #ifdef APP-PLUS-NVUE
+			this.$refs.web.evalJs(command)
+			// #endif
+			// #endif
+		},
 
-      // 等待图片加载完毕
-      let height
-      clearInterval(this._timer)
-      this._timer = setInterval(() => {
-        this.getRect().then(rect => {
-          // 350ms 总高度无变化就触发 ready 事件
-          if (rect.height == height) {
-            this.$emit('ready', rect)
-            clearInterval(this._timer)
-          }
-          height = rect.height
-        }).catch(() => { })
-      }, 350)
-      // #endif
-    },
+		/**
+		 * @description 设置内容
+		 * @param {String} content html 内容
+		 * @param {Boolean} append 是否在尾部追加
+		 */
+		setContent(content, append) {
+			if (!append || !this.imgList) {
+				this.imgList = []
+			}
+			const nodes = new Parser(this).parse(content)
+			// #ifdef APP-PLUS-NVUE
+			if (this._ready) {
+				this._set(nodes, append)
+			}
+			// #endif
+			this.$set(this, 'nodes', append ? (this.nodes || []).concat(nodes) : nodes)
 
-    /**
-     * @description 调用插件钩子函数
-     */
-    _hook(name) {
-      for (let i = plugins.length; i--;)
-        if (this.plugins[i][name])
-          this.plugins[i][name]()
-    },
+			// #ifndef APP-PLUS-NVUE
+			this._videos = []
+			this.$nextTick(() => {
+				this._hook('onLoad')
+				this.$emit('load')
+			})
 
-    // #ifdef APP-PLUS-NVUE
-    /**
-     * @description 设置内容
-     */
-    _set(nodes, append) {
-      this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes) + ',' + JSON.stringify([this.bgColor, this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
-    },
+			if (this.lazyLoad || this.imgList._unloadimgs < this.imgList.length / 2) {
+				// 设置懒加载,每 350ms 获取高度,不变则认为加载完毕
+				let height = 0
+				const callback = rect => {
+					if (!rect || !rect.height) rect = {}
+					// 350ms 总高度无变化就触发 ready 事件
+					if (rect.height === height) {
+						this.$emit('ready', rect)
+					} else {
+						height = rect.height
+						setTimeout(() => {
+							this.getRect().then(callback).catch(callback)
+						}, 350)
+					}
+				}
+				this.getRect().then(callback).catch(callback)
+			} else {
+				// 未设置懒加载,等待所有图片加载完毕
+				if (!this.imgList._unloadimgs) {
+					this.getRect().then(rect => {
+						this.$emit('ready', rect)
+					}).catch(() => {
+						this.$emit('ready', {})
+					})
+				}
+			}
+			// #endif
+		},
 
-    /**
-     * @description 接收到 web-view 消息
-     */
-    _onMessage(e) {
-      const message = e.detail.data[0]
-      switch (message.action) {
-        // web-view 初始化完毕
-        case 'onJSBridgeReady':
-          this._ready = true
-          if (this.nodes)
-            this._set(this.nodes)
-          break
-        // 内容 dom 加载完毕
-        case 'onLoad':
-          this.height = message.height
-          this._hook('onLoad')
-          this.$emit('load')
-          break
-        // 所有图片加载完毕
-        case 'onReady':
-          this.getRect().then(res => {
-            this.$emit('ready', res)
-          }).catch(() => { })
-          break
-        // 总高度发生变化
-        case 'onHeightChange':
-          this.height = message.height
-          break
-        // 图片点击
-        case 'onImgTap':
-          this.$emit('imgTap', message.attrs)
-          if (this.previewImg)
-            uni.previewImage({
-              current: parseInt(message.attrs.i),
-              urls: this.imgList
-            })
-          break
-        // 链接点击
-        case 'onLinkTap':
-          const href = message.attrs.href
-          this.$emit('linkTap', message.attrs)
-          if (href) {
-            // 锚点跳转
-            if (href[0] == '#') {
-              if (this.useAnchor)
-                dom.scrollToElement(this.$refs.web, {
-                  offset: message.offset
-                })
-            }
-            // 打开外链
-            else if (href.includes('://')) {
-              if (this.copyLink)
-                plus.runtime.openWeb(href)
-            }
-            else
-              uni.navigateTo({
-                url: href,
-                fail() {
-                  wx.switchTab({
-                    url: href
-                  })
-                }
-              })
-          }
-          break
-        // 获取到锚点的偏移量
-        case 'getOffset':
-          if (typeof message.offset == 'number') {
-            dom.scrollToElement(this.$refs.web, {
-              offset: message.offset + this._navigateTo.offset
-            })
-            this._navigateTo.resolve()
-          } else
-            this._navigateTo.reject('Label not found')
-          break
-        // 点击
-        case 'onClick':
-          this.$emit('tap')
-          break
-        // 出错
-        case 'onError':
-          this.$emit('error', {
-            source: message.source,
-            attrs: message.attrs
-          })
-      }
-    }
-    // #endif
-  }
+		/**
+		 * @description 调用插件钩子函数
+		 */
+		_hook(name) {
+			for (let i = plugins.length; i--;) {
+				if (this.plugins[i][name]) {
+					this.plugins[i][name]()
+				}
+			}
+		},
+
+		// #ifdef APP-PLUS-NVUE
+		/**
+		 * @description 设置内容
+		 */
+		_set(nodes, append) {
+			this.$refs.web.evalJs('setContent(' + JSON.stringify(nodes).replace(/%22/g, '') + ',' + JSON.stringify([this.containerStyle.replace(/(?:margin|padding)[^;]+/g, ''), this.errorImg, this.loadingImg, this.pauseVideo, this.scrollTable, this.selectable]) + ',' + append + ')')
+		},
+
+		/**
+		 * @description 接收到 web-view 消息
+		 */
+		_onMessage(e) {
+			const message = e.detail.data[0]
+			switch (message.action) {
+				// web-view 初始化完毕
+				case 'onJSBridgeReady':
+					this._ready = true
+					if (this.nodes) {
+						this._set(this.nodes)
+					}
+					break
+				// 内容 dom 加载完毕
+				case 'onLoad':
+					this.height = message.height
+					this._hook('onLoad')
+					this.$emit('load')
+					break
+				// 所有图片加载完毕
+				case 'onReady':
+					this.getRect().then(res => {
+						this.$emit('ready', res)
+					}).catch(() => {
+						this.$emit('ready', {})
+					})
+					break
+				// 总高度发生变化
+				case 'onHeightChange':
+					this.height = message.height
+					break
+				// 图片点击
+				case 'onImgTap':
+					this.$emit('imgtap', message.attrs)
+					if (this.previewImg) {
+						uni.previewImage({
+							current: parseInt(message.attrs.i),
+							urls: this.imgList
+						})
+					}
+					break
+				// 链接点击
+				case 'onLinkTap': {
+					const href = message.attrs.href
+					this.$emit('linktap', message.attrs)
+					if (href) {
+						// 锚点跳转
+						if (href[0] === '#') {
+							if (this.useAnchor) {
+								dom.scrollToElement(this.$refs.web, {
+									offset: message.offset
+								})
+							}
+						} else if (href.includes('://')) {
+							// 打开外链
+							if (this.copyLink) {
+								plus.runtime.openWeb(href)
+							}
+						} else {
+							uni.navigateTo({
+								url: href,
+								fail() {
+									uni.switchTab({
+										url: href
+									})
+								}
+							})
+						}
+					}
+					break
+				}
+				case 'onPlay':
+					this.$emit('play')
+					break
+				// 获取到锚点的偏移量
+				case 'getOffset':
+					if (typeof message.offset === 'number') {
+						dom.scrollToElement(this.$refs.web, {
+							offset: message.offset + this._navigateTo.offset
+						})
+						this._navigateTo.resolve()
+					} else {
+						this._navigateTo.reject(Error('Label not found'))
+					}
+					break
+				// 点击
+				case 'onClick':
+					this.$emit('tap')
+					this.$emit('click')
+					break
+				// 出错
+				case 'onError':
+					this.$emit('error', {
+						source: message.source,
+						attrs: message.attrs
+					})
+			}
+		}
+		// #endif
+	}
 }
 </script>
 
@@ -354,13 +484,16 @@ export default {
 /* #ifndef APP-PLUS-NVUE */
 /* 根节点样式 */
 ._root {
-  overflow: auto;
-  -webkit-overflow-scrolling: touch;
+	padding: 1px 0;
+	overflow-x: auto;
+	overflow-y: hidden;
+	-webkit-overflow-scrolling: touch;
 }
 
 /* 长按复制 */
 ._select {
-  user-select: text;
+	user-select: text;
 }
+
 /* #endif */
 </style>

+ 3 - 0
src/uni_modules/uview-plus/components/u-picker/u-picker.vue

@@ -105,11 +105,13 @@ export default {
 		// 监听columns参数的变化
 		columns: {
 			immediate: true,
+			deep:true,
 			handler(n) {
 				this.setColumns(n)
 			}
 		},
 	},
+	emits: ['close', 'cancel', 'confirm', 'change'],
 	methods: {
 		// 获取item需要显示的文字,判别为对象还是文本
 		getItemText(item) {
@@ -212,6 +214,7 @@ export default {
 		},
 		// 设置整体各列的columns的值
 		setColumns(columns) {
+			console.log(columns)
 			this.innerColumns = uni.$u.deepClone(columns)
 			// 如果在设置各列数据时,没有被设置默认的各列索引defaultIndex,那么用0去填充它,数组长度为列的数量
 			if (this.innerIndex.length === 0) {

+ 1 - 1
src/uni_modules/uview-plus/components/u-popup/u-popup.vue

@@ -52,7 +52,7 @@
 	/**
 	 * popup 弹窗
 	 * @description 弹出层容器,用于展示弹窗、信息提示等内容,支持上、下、左、右和中部弹出。组件只提供容器,内部内容由用户自定义
-	 * @tutorial https://www.uviewui.com/components/popup.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/popup.html
 	 * @property {Boolean}			show				是否展示弹窗 (默认 false )
 	 * @property {Boolean}			overlay				是否显示遮罩 (默认 true )
 	 * @property {String}			mode				弹出方向(默认 'bottom' )

+ 5 - 2
src/uni_modules/uview-plus/components/u-radio-group/u-radio-group.vue

@@ -15,7 +15,7 @@
 	/**
 	 * radioRroup 单选框父组件
 	 * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配u-radio使用
-	 * @tutorial https://www.uviewui.com/components/radio.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/radio.html
 	 * @property {String | Number | Boolean}	value 			绑定的值
 	 * @property {Boolean}						disabled		是否禁用所有radio(默认 false )
 	 * @property {String}						shape			外观形状,shape-方形,circle-圆形(默认 circle )
@@ -115,7 +115,10 @@
 		flex: 1;
 
 		&--row {
-			@include flex;
+			/* #ifndef APP-NVUE */
+			display: flex;
+			/* #endif */
+			flex-flow: row wrap;
 		}
 
 		&--column {

+ 5 - 0
src/uni_modules/uview-plus/components/u-radio/props.js

@@ -60,6 +60,11 @@ export default {
         labelColor: {
             type: String,
             default: defprops.radio.labelColor
+        },
+        // 图标颜色
+        iconColor: {
+            type: String,
+            default: defprops.radio.iconColor
         }
     }
 }

+ 4 - 2
src/uni_modules/uview-plus/components/u-radio/u-radio.vue

@@ -39,7 +39,7 @@
 	/**
 	 * radio 单选框
 	 * @description 单选框用于有一个选择,用户只能选择其中一个的场景。搭配u-radio-group使用
-	 * @tutorial https://www.uviewui.com/components/radio.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/radio.html
 	 * @property {String | Number}	name			radio的名称
 	 * @property {String}			shape			形状,square为方形,circle为圆型
 	 * @property {Boolean}			disabled		是否禁用
@@ -269,7 +269,9 @@
 		overflow: hidden;
 		flex-direction: row;
 		align-items: center;
-
+		margin-bottom: 5px;
+		margin-top: 5px;
+		
 		&-label--left {
 			flex-direction: row
 		}

+ 6 - 1
src/uni_modules/uview-plus/components/u-rate/u-rate.vue

@@ -84,7 +84,7 @@
 	/**
 	 * rate 评分
 	 * @description 该组件一般用于满意度调查,星型评分的场景
-	 * @tutorial https://www.uviewui.com/components/rate.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/rate.html
 	 * @property {String | Number}	value			用于v-model双向绑定选中的星星数量 (默认 1 )
 	 * @property {String | Number}	count			最多可选的星星数量 (默认 5 )
 	 * @property {Boolean}			disabled		是否禁止用户操作 (默认 false )
@@ -111,7 +111,12 @@
 				elId: uni.$u.guid(),
 				elClass: uni.$u.guid(),
 				rateBoxLeft: 0, // 评分盒子左边到屏幕左边的距离,用于滑动选择时计算距离
+				// #ifdef VUE3
+				activeIndex: this.modelValue,
+				// #endif
+				// #ifdef VUE2
 				activeIndex: this.value,
+				// #endif
 				rateWidth: 0, // 每个星星的宽度
 				// 标识是否正在滑动,由于iOS事件上touch比click先触发,导致快速滑动结束后,接着触发click,导致事件混乱而出错
 				moving: false,

+ 1 - 1
src/uni_modules/uview-plus/components/u-read-more/u-read-more.vue

@@ -55,7 +55,7 @@
 	/**
 	 * readMore 阅读更多
 	 * @description 该组件一般用于内容较长,预先收起一部分,点击展开全部内容的场景。
-	 * @tutorial https://www.uviewui.com/components/readMore.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/readMore.html
 	 * @property {String | Number}	showHeight	内容超出此高度才会显示展开全文按钮,单位px(默认 400 )
 	 * @property {Boolean}			toggle		展开后是否显示收起按钮(默认 false )
 	 * @property {String}			closeText	关闭时的提示文字(默认 '展开阅读全文' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-row-notice/u-row-notice.vue

@@ -62,7 +62,7 @@
 	/**
 	 * RowNotice 滚动通知中的水平滚动模式
 	 * @description 水平滚动
-	 * @tutorial https://www.uviewui.com/components/noticeBar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/noticeBar.html
 	 * @property {String | Number}	text			显示的内容,字符串
 	 * @property {String}			icon			是否显示左侧的音量图标 (默认 'volume' )
 	 * @property {String}			mode			通告模式,link-显示右箭头,closable-显示右侧关闭图标

+ 1 - 1
src/uni_modules/uview-plus/components/u-row/u-row.vue

@@ -19,7 +19,7 @@
 	/**
 	 * Row 栅格系统中的行
 	 * @description 通过基础的 12 分栏,迅速简便地创建布局 
-	 * @tutorial https://www.uviewui.com/components/layout.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/layout.html
 	 * @property {String | Number}	gutter		栅格间隔,左右各为此值的一半,单位px  (默认 0 )
 	 * @property {String}			justify		水平排列方式(微信小程序暂不支持) 可选值为`start`(或`flex-start`)、`end`(或`flex-end`)、`center`、`around`(或`space-around`)、`between`(或`space-between`)  (默认 'start' )
 	 * @property {String}			align		垂直排列方式 (默认 'center' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-safe-bottom/u-safe-bottom.vue

@@ -14,7 +14,7 @@
 	/**
 	 * SafeBottom 底部安全区
 	 * @description 这个适配,主要是针对IPhone X等一些底部带指示条的机型,指示条的操作区域与页面底部存在重合,容易导致用户误操作,因此我们需要针对这些机型进行底部安全区适配。
-	 * @tutorial https://www.uviewui.com/components/safeAreaInset.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/safeAreaInset.html
 	 * @property {type}		prop_name
 	 * @property {Object}	customStyle	定义需要用到的外部样式
 	 *

+ 1 - 1
src/uni_modules/uview-plus/components/u-scroll-list/u-scroll-list.vue

@@ -82,7 +82,7 @@
 /**
  * scrollList 横向滚动列表
  * @description 该组件一般用于同时展示多个商品、分类的场景,也可以完成左右滑动的列表。
- * @tutorial https://www.uviewui.com/components/scrollList.html
+ * @tutorial https://ijry.github.io/uview-plus/components/scrollList.html
  * @property {String | Number}	indicatorWidth			指示器的整体宽度 (默认 50 )
  * @property {String | Number}	indicatorBarWidth		滑块的宽度 (默认 20 )
  * @property {Boolean}			indicator				是否显示面板指示器 (默认 true )

+ 1 - 1
src/uni_modules/uview-plus/components/u-search/u-search.vue

@@ -79,7 +79,7 @@
 	/**
 	 * search 搜索框
 	 * @description 搜索组件,集成了常见搜索框所需功能,用户可以一键引入,开箱即用。
-	 * @tutorial https://www.uviewui.com/components/search.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/search.html
 	 * @property {String}			shape				搜索框形状,round-圆形,square-方形(默认 'round' )
 	 * @property {String}			bgColor				搜索框背景颜色(默认 '#f2f2f2' )
 	 * @property {String}			placeholder			占位文字内容(默认 '请输入关键字' )

+ 2 - 2
src/uni_modules/uview-plus/components/u-skeleton/u-skeleton.vue

@@ -60,7 +60,7 @@
 	/**
 	 * Skeleton 骨架屏
 	 * @description 骨架屏一般用于页面在请求远程数据尚未完成时,页面用灰色块预显示本来的页面结构,给用户更好的体验。
-	 * @tutorial https://www.uviewui.com/components/skeleton.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/skeleton.html
 	 * @property {Boolean}					loading		是否显示骨架占位图,设置为false将会展示子组件内容 (默认 true )
 	 * @property {Boolean}					animate		是否开启动画效果 (默认 true )
 	 * @property {String | Number}			rows		段落占位图行数 (默认 0 )
@@ -96,7 +96,7 @@
 				for (let i = 0; i < this.rows; i++) {
 					let item = {},
 						// 需要预防超出数组边界的情况
-						rowWidth = uni.$u.test.array(this.rowsWidth) ? (this.rowsWidth[i] || (i === this.row - 1 ? '70%' : '100%')) : i ===
+						rowWidth = uni.$u.test.array(this.rowsWidth) ? (this.rowsWidth[i] || (i === this.rows - 1 ? '70%' : '100%')) : i ===
 						this.rows - 1 ? '70%' : this.rowsWidth,
 						rowHeight = uni.$u.test.array(this.rowsHeight) ? (this.rowsHeight[i] || '18px') : this.rowsHeight
 					// 如果有title占位图,第一个段落占位图的外边距需要大一些,如果没有title占位图,第一个段落占位图则无需外边距

+ 1 - 1
src/uni_modules/uview-plus/components/u-sticky/u-sticky.vue

@@ -20,7 +20,7 @@
 	/**
 	 * sticky 吸顶
 	 * @description 该组件与CSS中position: sticky属性实现的效果一致,当组件达到预设的到顶部距离时, 就会固定在指定位置,组件位置大于预设的顶部距离时,会重新按照正常的布局排列。
-	 * @tutorial https://www.uviewui.com/components/sticky.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/sticky.html
 	 * @property {String | Number}	offsetTop		吸顶时与顶部的距离,单位px(默认 0 )
 	 * @property {String | Number}	customNavHeight	自定义导航栏的高度 (h5 默认44  其他默认 0 )
 	 * @property {Boolean}			disabled		是否开启吸顶功能 (默认 false )

+ 1 - 1
src/uni_modules/uview-plus/components/u-subsection/u-subsection.vue

@@ -58,7 +58,7 @@ import mixin from '../../libs/mixin/mixin.js';
 /**
  * Subsection 分段器
  * @description 该分段器一般用于用户从几个选项中选择某一个的场景
- * @tutorial https://www.uviewui.com/components/subsection.html
+ * @tutorial https://ijry.github.io/uview-plus/components/subsection.html
  * @property {Array}			list			tab的数据
  * @property {String | Number}	current			当前活动的tab的index(默认 0 )
  * @property {String}			activeColor		激活时的颜色(默认 '#3c9cff' )

+ 2 - 1
src/uni_modules/uview-plus/components/u-swipe-action-item/u-swipe-action-item.vue

@@ -59,7 +59,7 @@
 	/**
 	 * SwipeActionItem 滑动单元格子组件
 	 * @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
-	 * @tutorial https://www.uviewui.com/components/swipeAction.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/swipeAction.html
 	 * @property {Boolean}			show			控制打开或者关闭(默认 false )
 	 * @property {String | Number}	index			标识符,如果是v-for,可用index索引
 	 * @property {Boolean}			disabled		是否禁用(默认 false )
@@ -73,6 +73,7 @@
 	 */
 	export default {
 		name: 'u-swipe-action-item',
+		emits: ['click'],
 		// #ifndef APP-NVUE
 		mixins: [mpMixin, mixin, props, touch],
 		// #endif

+ 1 - 1
src/uni_modules/uview-plus/components/u-swipe-action/u-swipe-action.vue

@@ -11,7 +11,7 @@
 	/**
 	 * SwipeAction 滑动单元格 
 	 * @description 该组件一般用于左滑唤出操作菜单的场景,用的最多的是左滑删除操作
-	 * @tutorial https://www.uviewui.com/components/swipeAction.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/swipeAction.html
 	 * @property {Boolean}	autoClose	是否自动关闭其他swipe按钮组
 	 * @event {Function(index)}	click	点击组件时触发
 	 * @example	<u-swipe-action><u-swipe-action-item :rightOptions="options1" ></u-swipe-action-item></u-swipe-action>

+ 1 - 1
src/uni_modules/uview-plus/components/u-swiper-indicator/u-swiper-indicator.vue

@@ -38,7 +38,7 @@
 	/**
 	 * SwiperIndicator 轮播图指示器
 	 * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用,
-	 * @tutorial https://www.uviewui.com/components/swiper.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/swiper.html
 	 * @property {String | Number}	length					轮播的长度(默认 0 )
 	 * @property {String | Number}	current					当前处于活动状态的轮播的索引(默认 0 )
 	 * @property {String}			indicatorActiveColor	指示器非激活颜色

+ 1 - 1
src/uni_modules/uview-plus/components/u-swiper/u-swiper.vue

@@ -96,7 +96,7 @@
 	/**
 	 * Swiper 轮播图
 	 * @description 该组件一般用于导航轮播,广告展示等场景,可开箱即用,
-	 * @tutorial https://www.uviewui.com/components/swiper.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/swiper.html
 	 * @property {Array}			list					轮播图数据
 	 * @property {Boolean}			indicator				是否显示面板指示器(默认 false )
 	 * @property {String}			indicatorActiveColor	指示器非激活颜色(默认 '#FFFFFF' )

+ 13 - 3
src/uni_modules/uview-plus/components/u-switch/u-switch.vue

@@ -12,7 +12,12 @@
 		</view>
 		<view
 		    class="u-switch__node"
-		    :class="[value && 'u-switch__node--on']"
+		    <!-- #ifdef VUE3 -->
+			:class="[modelValue && 'u-switch__node--on']"
+			<!-- #endif -->
+			<!-- #ifdef VUE2 -->
+			:class="[value && 'u-switch__node--on']"
+			<!-- #endif -->
 		    :style="[nodeStyle]"
 		    ref="u-switch__node"
 		>
@@ -20,7 +25,12 @@
 			    :show="loading"
 			    mode="circle"
 			    timingFunction='linear'
-			    :color="value ? activeColor : '#AAABAD'"
+			    <!-- #ifdef VUE3 -->
+				:color="modelValue ? activeColor : '#AAABAD'"
+				<!-- #endif -->
+				<!-- #ifdef VUE2 -->
+				:color="value ? activeColor : '#AAABAD'"
+				<!-- #endif -->
 			    :size="size * 0.6"
 			/>
 		</view>
@@ -34,7 +44,7 @@
 	/**
 	 * switch 开关选择器
 	 * @description 选择开关一般用于只有两个选择,且只能选其一的场景。
-	 * @tutorial https://www.uviewui.com/components/switch.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/switch.html
 	 * @property {Boolean}						loading			是否处于加载中(默认 false )
 	 * @property {Boolean}						disabled		是否禁用(默认 false )
 	 * @property {String | Number}				size			开关尺寸,单位px (默认 25 )

+ 5 - 1
src/uni_modules/uview-plus/components/u-tabbar-item/u-tabbar-item.vue

@@ -49,7 +49,7 @@
 	/**
 	 * TabbarItem 底部导航栏子组件
 	 * @description 此组件提供了自定义tabbar的能力。
-	 * @tutorial https://www.uviewui.com/components/tabbar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/tabbar.html
 	 * @property {String | Number}	name		item标签的名称,作为与u-tabbar的value参数匹配的标识符
 	 * @property {String}			icon		uView内置图标或者绝对路径的图片
 	 * @property {String | Number}	badge		右上角的角标提示信息
@@ -73,6 +73,10 @@
 				}
 			}
 		},
+		//  微信小程序中 options 选项
+		options: {
+		    virtualHost: true //将自定义节点设置成虚拟的,更加接近Vue组件的表现。我们不希望自定义组件的这个节点本身可以设置样式、响应 flex 布局等
+		},
 		created() {
 			this.init()
 		},

+ 1 - 1
src/uni_modules/uview-plus/components/u-tabbar/u-tabbar.vue

@@ -32,7 +32,7 @@
 	/**
 	 * Tabbar 底部导航栏
 	 * @description 此组件提供了自定义tabbar的能力。
-	 * @tutorial https://www.uviewui.com/components/tabbar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/tabbar.html
 	 * @property {String | Number}	value				当前匹配项的name
 	 * @property {Boolean}			safeAreaInsetBottom	是否为iPhoneX留出底部安全距离(默认 true )
 	 * @property {Boolean}			border				是否显示上方边框(默认 true )

+ 1 - 1
src/uni_modules/uview-plus/components/u-table/u-table.vue

@@ -11,7 +11,7 @@
 	/**
 	 * Table 表格 
 	 * @description 表格组件一般用于展示大量结构化数据的场景 本组件标签类似HTML的table表格,由table、tr、th、td四个组件组成
-	 * @tutorial https://www.uviewui.com/components/table.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/table.html
 	 * @example <u-table><u-tr><u-th>学校</u-th </u-tr> <u-tr><u-td>浙江大学</u-td> </u-tr> <u-tr><u-td>清华大学</u-td> </u-tr></u-table>
 	 */
 	export default {

+ 1 - 1
src/uni_modules/uview-plus/components/u-tabs-item/u-tabs-item.vue

@@ -11,7 +11,7 @@
 	/**
 	 * TabsItem  tabs标签组件的自组件
 	 * @description tabs标签组件,在标签多的时候,可以配置为左右滑动,标签少的时候,可以禁止滑动。 该组件的一个特点是配置为滚动模式时,激活的tab会自动移动到组件的中间位置。
-	 * @tutorial https://www.uviewui.com/components/tabs.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/tabs.html
 	 * @property {type}	prop_name
 	 * @event {Function()} 
 	 * @example 

+ 2 - 3
src/uni_modules/uview-plus/components/u-tabs/u-tabs.vue

@@ -90,7 +90,7 @@
 	/**
 	 * Tabs 标签
 	 * @description tabs标签组件,在标签多的时候,可以配置为左右滑动,标签少的时候,可以禁止滑动。 该组件的一个特点是配置为滚动模式时,激活的tab会自动移动到组件的中间位置。
-	 * @tutorial https://www.uviewui.com/components/tabs.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/tabs.html
 	 * @property {String | Number}	duration			滑块移动一次所需的时间,单位秒(默认 200 )
 	 * @property {String | Number}	swierWidth			swiper的宽度(默认 '750rpx' )
 	 * @property {String}	keyName	 从`list`元素对象中读取的键名(默认 'name' )
@@ -271,7 +271,7 @@
 			// 获取各个标签的尺寸
 			queryRect(el, item) {
 				// #ifndef APP-NVUE
-				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://ijry.github.io/uview-plus/js/getRect.html
 				// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
 				return new Promise(resolve => {
 					this.$uGetRect(`.${el}`).then(size => {
@@ -324,7 +324,6 @@
 					@include flex;
 					align-items: center;
 					justify-content: center;
-					margin: auto;
 
 					&--disabled {
 						/* #ifndef APP-NVUE */

+ 1 - 1
src/uni_modules/uview-plus/components/u-tag/u-tag.vue

@@ -62,7 +62,7 @@
 	/**
 	 * Tag 标签
 	 * @description tag组件一般用于标记和选择,我们提供了更加丰富的表现形式,能够较全面的涵盖您的使用场景
-	 * @tutorial https://www.uviewui.com/components/tag.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/tag.html
 	 * @property {String}			type		标签类型info、primary、success、warning、error (默认 'primary' )
 	 * @property {Boolean | String}	disabled	不可用(默认 false )
 	 * @property {String}			size		标签的大小,large,medium,mini (默认 'medium' )

+ 1 - 1
src/uni_modules/uview-plus/components/u-text/u-text.vue

@@ -79,7 +79,7 @@ import props from './props.js'
 /**
  * Text 文本
  * @description 此组件集成了文本类在项目中的常用功能,包括状态,拨打电话,格式化日期,*替换,超链接...等功能。 您大可不必在使用特殊文本时自己定义,text组件几乎涵盖您能使用的大部分场景。
- * @tutorial https://www.uviewui.com/components/loading.html
+ * @tutorial https://ijry.github.io/uview-plus/components/loading.html
  * @property {String} 					type		主题颜色
  * @property {Boolean} 					show		是否显示(默认 true )
  * @property {String | Number}			text		显示的值

+ 5 - 3
src/uni_modules/uview-plus/components/u-textarea/u-textarea.vue

@@ -20,7 +20,7 @@
             :disableDefaultPadding="disableDefaultPadding"
             :holdKeyboard="holdKeyboard"
             :maxlength="maxlength"
-            :confirmType="confirmType"
+            :confirm-type="confirmType"
             :ignoreCompositionEvent="ignoreCompositionEvent"
             @focus="onFocus"
             @blur="onBlur"
@@ -47,7 +47,7 @@ import mixin from '../../libs/mixin/mixin.js';
 /**
  * Textarea 文本域
  * @description 文本域此组件满足了可能出现的表单信息补充,编辑等实际逻辑的功能,内置了字数校验等
- * @tutorial https://www.uviewui.com/components/textarea.html
+ * @tutorial https://ijry.github.io/uview-plus/components/textarea.html
  *
  * @property {String | Number} 		value					输入框的内容
  * @property {String | Number}		placeholder				输入框为空时占位符
@@ -98,6 +98,8 @@ export default {
 			innerFormatter: value => value
 		}
 	},
+	created() {
+	},
 	watch: {
         // #ifdef VUE2
 	    value: {
@@ -144,7 +146,7 @@ export default {
         // 组件的类名
         textareaClass() {
             let classes = [],
-                { border, disabled, shape } = this;
+                { border, disabled } = this;
             border === "surround" &&
                 (classes = classes.concat(["u-border", "u-textarea--radius"]));
             border === "bottom" &&

+ 1 - 1
src/uni_modules/uview-plus/components/u-toast/u-toast.vue

@@ -44,7 +44,7 @@
 	/**
 	 * toast 消息提示
 	 * @description 此组件表现形式类似uni的uni.showToastAPI,但也有不同的地方。
-	 * @tutorial https://www.uviewui.com/components/toast.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/toast.html
 	 * @property {String | Number}	zIndex		toast展示时的zIndex值 (默认 10090 )
 	 * @property {Boolean}			loading		是否加载中 (默认 false )
 	 * @property {String | Number}	message		显示的文字内容

+ 1 - 1
src/uni_modules/uview-plus/components/u-toolbar/u-toolbar.vue

@@ -42,7 +42,7 @@
 	/**
 	 * Toolbar 工具条
 	 * @description 
-	 * @tutorial https://www.uviewui.com/components/toolbar.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/toolbar.html
 	 * @property {Boolean}	show			是否展示工具条(默认 true )
 	 * @property {String}	cancelText		取消按钮的文字(默认 '取消' )
 	 * @property {String}	confirmText		确认按钮的文字(默认 '确认' )

+ 2 - 2
src/uni_modules/uview-plus/components/u-tooltip/u-tooltip.vue

@@ -103,7 +103,7 @@
 	/**
 	 * Tooltip 
 	 * @description 
-	 * @tutorial https://www.uviewui.com/components/tooltip.html
+	 * @tutorial https://ijry.github.io/uview-plus/components/tooltip.html
 	 * @property {String | Number}	text		需要显示的提示文字
 	 * @property {String | Number}	copyText	点击复制按钮时,复制的文本,为空则使用text值
 	 * @property {String | Number}	size		文本大小(默认 14 )
@@ -219,7 +219,7 @@
 			// 查询内容高度
 			queryRect(ref) {
 				// #ifndef APP-NVUE
-				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://www.uviewui.com/js/getRect.html
+				// $uGetRect为uView自带的节点查询简化方法,详见文档介绍:https://ijry.github.io/uview-plus/js/getRect.html
 				// 组件内部一般用this.$uGetRect,对外的为uni.$u.getRect,二者功能一致,名称不同
 				return new Promise(resolve => {
 					this.$uGetRect(`#${ref}`).then(size => {

+ 3 - 1
src/uni_modules/uview-plus/components/u-upload/u-upload.vue

@@ -184,7 +184,9 @@
 				immediate: true,
 				handler() {
 					this.formatFileList()
-				}
+				},
+				immediate: true,
+				deep: true,
 			},
 		},
 		// #ifdef VUE3

+ 1 - 1
src/uni_modules/uview-plus/index.js

@@ -1,5 +1,5 @@
 // 看到此报错,是因为没有配置vue.config.js的【transpileDependencies】,详见:https://www.uviewui.com/components/npmSetting.html#_5-cli模式额外配置
-//const pleaseSetTranspileDependencies = {}, babelTest = pleaseSetTranspileDependencies?.test
+const pleaseSetTranspileDependencies = {}, babelTest = pleaseSetTranspileDependencies?.test
 
 // 引入全局mixin
 import mixin from './libs/mixin/mixin.js'

+ 176 - 176
src/uni_modules/uview-plus/libs/config/props.js

@@ -5,186 +5,186 @@
  */
 import config from './config'
 
-import actionSheet from './props/actionSheet.js'
-import album from './props/album.js'
-import alert from './props/alert.js'
-import avatar from './props/avatar'
-import avatarGroup from './props/avatarGroup'
-import backtop from './props/backtop'
-import badge from './props/badge'
-import button from './props/button'
-import calendar from './props/calendar'
-import carKeyboard from './props/carKeyboard'
-import cell from './props/cell'
-import cellGroup from './props/cellGroup'
-import checkbox from './props/checkbox'
-import checkboxGroup from './props/checkboxGroup'
-import circleProgress from './props/circleProgress'
-import code from './props/code'
-import codeInput from './props/codeInput'
-import col from './props/col'
-import collapse from './props/collapse'
-import collapseItem from './props/collapseItem'
-import columnNotice from './props/columnNotice'
-import countDown from './props/countDown'
-import countTo from './props/countTo'
-import datetimePicker from './props/datetimePicker'
-import divider from './props/divider'
-import empty from './props/empty'
-import form from './props/form'
-import formItem from './props/formItem'
-import gap from './props/gap'
-import grid from './props/grid'
-import gridItem from './props/gridItem'
-import icon from './props/icon'
-import image from './props/image'
-import indexAnchor from './props/indexAnchor'
-import indexList from './props/indexList'
-import input from './props/input'
-import keyboard from './props/keyboard'
-import line from './props/line'
-import lineProgress from './props/lineProgress'
-import link from './props/link'
-import list from './props/list'
-import listItem from './props/listItem'
-import loadingIcon from './props/loadingIcon'
-import loadingPage from './props/loadingPage'
-import loadmore from './props/loadmore'
-import modal from './props/modal'
-import navbar from './props/navbar'
-import noNetwork from './props/noNetwork'
-import noticeBar from './props/noticeBar'
-import notify from './props/notify'
-import numberBox from './props/numberBox'
-import numberKeyboard from './props/numberKeyboard'
-import overlay from './props/overlay'
-import parse from './props/parse'
-import picker from './props/picker'
-import popup from './props/popup'
-import radio from './props/radio'
-import radioGroup from './props/radioGroup'
-import rate from './props/rate'
-import readMore from './props/readMore'
-import row from './props/row'
-import rowNotice from './props/rowNotice'
-import scrollList from './props/scrollList'
-import search from './props/search'
-import section from './props/section'
-import skeleton from './props/skeleton'
-import slider from './props/slider'
-import statusBar from './props/statusBar'
-import steps from './props/steps'
-import stepsItem from './props/stepsItem'
-import sticky from './props/sticky'
-import subsection from './props/subsection'
-import swipeAction from './props/swipeAction'
-import swipeActionItem from './props/swipeActionItem'
-import swiper from './props/swiper'
-import swipterIndicator from './props/swipterIndicator'
-import _switch from './props/switch'
-import tabbar from './props/tabbar'
-import tabbarItem from './props/tabbarItem'
-import tabs from './props/tabs'
-import tag from './props/tag'
-import text from './props/text'
-import textarea from './props/textarea'
-import toast from './props/toast'
-import toolbar from './props/toolbar'
-import tooltip from './props/tooltip'
-import transition from './props/transition'
-import upload from './props/upload'
+import ActionSheet from './props/actionSheet'
+import Album from './props/album'
+import Alert from './props/alert'
+import Avatar from './props/avatar'
+import AvatarGroup from './props/avatarGroup'
+import Backtop from './props/backtop'
+import Badge from './props/badge'
+import Button from './props/button'
+import Calendar from './props/calendar'
+import CarKeyboard from './props/carKeyboard'
+import Cell from './props/cell'
+import CellGroup from './props/cellGroup'
+import Checkbox from './props/checkbox'
+import CheckboxGroup from './props/checkboxGroup'
+import CircleProgress from './props/circleProgress'
+import Code from './props/code'
+import CodeInput from './props/codeInput'
+import Col from './props/col'
+import Collapse from './props/collapse'
+import CollapseItem from './props/collapseItem'
+import ColumnNotice from './props/columnNotice'
+import CountDown from './props/countDown'
+import CountTo from './props/countTo'
+import DatetimePicker from './props/datetimePicker'
+import Divider from './props/divider'
+import Empty from './props/empty'
+import Form from './props/form'
+import GormItem from './props/formItem'
+import Gap from './props/gap'
+import Grid from './props/grid'
+import GridItem from './props/gridItem'
+import Icon from './props/icon'
+import Image from './props/image'
+import IndexAnchor from './props/indexAnchor'
+import IndexList from './props/indexList'
+import Input from './props/input'
+import Keyboard from './props/keyboard'
+import Line from './props/line'
+import LineProgress from './props/lineProgress'
+import Link from './props/link'
+import List from './props/list'
+import ListItem from './props/listItem'
+import LoadingIcon from './props/loadingIcon'
+import LoadingPage from './props/loadingPage'
+import Loadmore from './props/loadmore'
+import Modal from './props/modal'
+import Navbar from './props/navbar'
+import NoNetwork from './props/noNetwork'
+import NoticeBar from './props/noticeBar'
+import Notify from './props/notify'
+import NumberBox from './props/numberBox'
+import NumberKeyboard from './props/numberKeyboard'
+import Overlay from './props/overlay'
+import Parse from './props/parse'
+import Picker from './props/picker'
+import Popup from './props/popup'
+import Radio from './props/radio'
+import RadioGroup from './props/radioGroup'
+import Rate from './props/rate'
+import ReadMore from './props/readMore'
+import Row from './props/row'
+import RowNotice from './props/rowNotice'
+import ScrollList from './props/scrollList'
+import Search from './props/search'
+import Section from './props/section'
+import Skeleton from './props/skeleton'
+import Slider from './props/slider'
+import StatusBar from './props/statusBar'
+import Steps from './props/steps'
+import StepsItem from './props/stepsItem'
+import Sticky from './props/sticky'
+import Subsection from './props/subsection'
+import SwipeAction from './props/swipeAction'
+import SwipeActionItem from './props/swipeActionItem'
+import Swiper from './props/swiper'
+import SwipterIndicator from './props/swipterIndicator'
+import Switch from './props/switch'
+import Tabbar from './props/tabbar'
+import TabbarItem from './props/tabbarItem'
+import Tabs from './props/tabs'
+import Tag from './props/tag'
+import Text from './props/text'
+import Textarea from './props/textarea'
+import Toast from './props/toast'
+import Toolbar from './props/toolbar'
+import Tooltip from './props/tooltip'
+import Transition from './props/transition'
+import Upload from './props/upload'
 
 const {
     color
 } = config
 
 export default {
-    ...actionSheet,
-    ...album,
-    ...alert,
-    ...avatar,
-    ...avatarGroup,
-    ...backtop,
-    ...badge,
-    ...button,
-    ...calendar,
-    ...carKeyboard,
-    ...cell,
-    ...cellGroup,
-    ...checkbox,
-    ...checkboxGroup,
-    ...circleProgress,
-    ...code,
-    ...codeInput,
-    ...col,
-    ...collapse,
-    ...collapseItem,
-    ...columnNotice,
-    ...countDown,
-    ...countTo,
-    ...datetimePicker,
-    ...divider,
-    ...empty,
-    ...form,
-    ...formItem,
-    ...gap,
-    ...grid,
-    ...gridItem,
-    ...icon,
-    ...image,
-    ...indexAnchor,
-    ...indexList,
-    ...input,
-    ...keyboard,
-    ...line,
-    ...lineProgress,
-    ...link,
-    ...list,
-    ...listItem,
-    ...loadingIcon,
-    ...loadingPage,
-    ...loadmore,
-    ...modal,
-    ...navbar,
-    ...noNetwork,
-    ...noticeBar,
-    ...notify,
-    ...numberBox,
-    ...numberKeyboard,
-    ...overlay,
-    ...parse,
-    ...picker,
-    ...popup,
-    ...radio,
-    ...radioGroup,
-    ...rate,
-    ...readMore,
-    ...row,
-    ...rowNotice,
-    ...scrollList,
-    ...search,
-    ...section,
-    ...skeleton,
-    ...slider,
-    ...statusBar,
-    ...steps,
-    ...stepsItem,
-    ...sticky,
-    ...subsection,
-    ...swipeAction,
-    ...swipeActionItem,
-    ...swiper,
-    ...swipterIndicator,
-    ..._switch,
-    ...tabbar,
-    ...tabbarItem,
-    ...tabs,
-    ...tag,
-    ...text,
-    ...textarea,
-    ...toast,
-    ...toolbar,
-    ...tooltip,
-    ...transition,
-    ...upload
+    ...ActionSheet,
+    ...Album,
+    ...Alert,
+    ...Avatar,
+    ...AvatarGroup,
+    ...Backtop,
+    ...Badge,
+    ...Button,
+    ...Calendar,
+    ...CarKeyboard,
+    ...Cell,
+    ...CellGroup,
+    ...Checkbox,
+    ...CheckboxGroup,
+    ...CircleProgress,
+    ...Code,
+    ...CodeInput,
+    ...Col,
+    ...Collapse,
+    ...CollapseItem,
+    ...ColumnNotice,
+    ...CountDown,
+    ...CountTo,
+    ...DatetimePicker,
+    ...Divider,
+    ...Empty,
+    ...Form,
+    ...GormItem,
+    ...Gap,
+    ...Grid,
+    ...GridItem,
+    ...Icon,
+    ...Image,
+    ...IndexAnchor,
+    ...IndexList,
+    ...Input,
+    ...Keyboard,
+    ...Line,
+    ...LineProgress,
+    ...Link,
+    ...List,
+    ...ListItem,
+    ...LoadingIcon,
+    ...LoadingPage,
+    ...Loadmore,
+    ...Modal,
+    ...Navbar,
+    ...NoNetwork,
+    ...NoticeBar,
+    ...Notify,
+    ...NumberBox,
+    ...NumberKeyboard,
+    ...Overlay,
+    ...Parse,
+    ...Picker,
+    ...Popup,
+    ...Radio,
+    ...RadioGroup,
+    ...Rate,
+    ...ReadMore,
+    ...Row,
+    ...RowNotice,
+    ...ScrollList,
+    ...Search,
+    ...Section,
+    ...Skeleton,
+    ...Slider,
+    ...StatusBar,
+    ...Steps,
+    ...StepsItem,
+    ...Sticky,
+    ...Subsection,
+    ...SwipeAction,
+    ...SwipeActionItem,
+    ...Swiper,
+    ...SwipterIndicator,
+    ...Switch,
+    ...Tabbar,
+    ...TabbarItem,
+    ...Tabs,
+    ...Tag,
+    ...Text,
+    ...Textarea,
+    ...Toast,
+    ...Toolbar,
+    ...Tooltip,
+    ...Transition,
+    ...Upload
 }

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

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

+ 6 - 3
src/uni_modules/uview-plus/libs/mixin/mixin.js

@@ -88,10 +88,13 @@ export default {
         openPage(urlKey = 'url') {
             const url = this[urlKey]
             if (url) {
+                // h5官方回应:发行h5会自动摇树优化,所有使用uni的地方,都会被直接转换成具体的API调用 https://ask.dcloud.net.cn/question/161523?notification_id-1201922__rf-false__item_id-226372
+                // 使用封装的 route 进行跳转(直接调用方法),不使用 uni 对象
+                this.$u.route({ type: this.linkType, url })
                 // 执行类似uni.navigateTo的方法
-                uni[this.linkType]({
-                    url
-                })
+                // uni[this.linkType]({
+                //     url
+                // })
             }
         },
         // 查询节点信息

File diff ditekan karena terlalu besar
+ 0 - 0
src/uni_modules/uview-plus/libs/util/dayjs.min.js


+ 1 - 1
src/uni_modules/uview-plus/libs/util/route.js

@@ -53,7 +53,7 @@ class Router {
             mergeConfig.url = this.mixinParam(options, params)
             mergeConfig.type = 'navigateTo'
         } else {
-            mergeConfig = uni.$u.deepMerge(options, this.config)
+            mergeConfig = uni.$u.deepMerge(this.config, options)
             // 否则正常使用mergeConfig中的url和params进行拼接
             mergeConfig.url = this.mixinParam(options.url, options.params)
         }

+ 2 - 2
src/uni_modules/uview-plus/package.json

@@ -1,8 +1,8 @@
 {
 	"id": "uview-plus",
 	"name": "uview-plus",
-	"displayName": "uview-plus3.0重磅发布,利剑出鞘,一统江湖",
-	"version": "3.1.7",
+	"displayName": "uview-plus3.0重磅发布,全面的Vue3移动组件库。",
+	"version": "3.1.26",
 	"description": "uview-plus已兼容vue3,全面的组件和便捷的工具会让您信手拈来,如鱼得水",
 	"keywords": [
         "uview",

Beberapa file tidak ditampilkan karena terlalu banyak file yang berubah dalam diff ini