;
+}
+
+declare class HttpRequest extends HttpRequestAbstract {
+}
+
+export default HttpRequest;
diff --git a/uni_modules/uv-ui-tools/libs/luch-request/index.js b/uni_modules/uv-ui-tools/libs/luch-request/index.js
new file mode 100644
index 0000000..d8fe348
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/luch-request/index.js
@@ -0,0 +1,2 @@
+import Request from './core/Request'
+export default Request
diff --git a/uni_modules/uv-ui-tools/libs/luch-request/utils.js b/uni_modules/uv-ui-tools/libs/luch-request/utils.js
new file mode 100644
index 0000000..0b5bf21
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/luch-request/utils.js
@@ -0,0 +1,135 @@
+'use strict'
+
+// utils is a library of generic helper functions non-specific to axios
+
+var toString = Object.prototype.toString
+
+/**
+ * Determine if a value is an Array
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Array, otherwise false
+ */
+export function isArray (val) {
+ return toString.call(val) === '[object Array]'
+}
+
+
+/**
+ * Determine if a value is an Object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is an Object, otherwise false
+ */
+export function isObject (val) {
+ return val !== null && typeof val === 'object'
+}
+
+/**
+ * Determine if a value is a Date
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a Date, otherwise false
+ */
+export function isDate (val) {
+ return toString.call(val) === '[object Date]'
+}
+
+/**
+ * Determine if a value is a URLSearchParams object
+ *
+ * @param {Object} val The value to test
+ * @returns {boolean} True if value is a URLSearchParams object, otherwise false
+ */
+export function isURLSearchParams (val) {
+ return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams
+}
+
+
+/**
+ * Iterate over an Array or an Object invoking a function for each item.
+ *
+ * If `obj` is an Array callback will be called passing
+ * the value, index, and complete array for each item.
+ *
+ * If 'obj' is an Object callback will be called passing
+ * the value, key, and complete object for each property.
+ *
+ * @param {Object|Array} obj The object to iterate
+ * @param {Function} fn The callback to invoke for each item
+ */
+export function forEach (obj, fn) {
+ // Don't bother if no value provided
+ if (obj === null || typeof obj === 'undefined') {
+ return
+ }
+
+ // Force an array if not already something iterable
+ if (typeof obj !== 'object') {
+ /*eslint no-param-reassign:0*/
+ obj = [obj]
+ }
+
+ if (isArray(obj)) {
+ // Iterate over array values
+ for (var i = 0, l = obj.length; i < l; i++) {
+ fn.call(null, obj[i], i, obj)
+ }
+ } else {
+ // Iterate over object keys
+ for (var key in obj) {
+ if (Object.prototype.hasOwnProperty.call(obj, key)) {
+ fn.call(null, obj[key], key, obj)
+ }
+ }
+ }
+}
+
+/**
+ * 是否为boolean 值
+ * @param val
+ * @returns {boolean}
+ */
+export function isBoolean(val) {
+ return typeof val === 'boolean'
+}
+
+/**
+ * 是否为真正的对象{} new Object
+ * @param {any} obj - 检测的对象
+ * @returns {boolean}
+ */
+export function isPlainObject(obj) {
+ return Object.prototype.toString.call(obj) === '[object Object]'
+}
+
+
+
+/**
+ * Function equal to merge with the difference being that no reference
+ * to original objects is kept.
+ *
+ * @see merge
+ * @param {Object} obj1 Object to merge
+ * @returns {Object} Result of all merge properties
+ */
+export function deepMerge(/* obj1, obj2, obj3, ... */) {
+ let result = {}
+ function assignValue(val, key) {
+ if (typeof result[key] === 'object' && typeof val === 'object') {
+ result[key] = deepMerge(result[key], val)
+ } else if (typeof val === 'object') {
+ result[key] = deepMerge({}, val)
+ } else {
+ result[key] = val
+ }
+ }
+ for (let i = 0, l = arguments.length; i < l; i++) {
+ forEach(arguments[i], assignValue)
+ }
+ return result
+}
+
+export function isUndefined (val) {
+ return typeof val === 'undefined'
+}
diff --git a/uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js b/uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js
new file mode 100644
index 0000000..2fee704
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/luch-request/utils/clone.js
@@ -0,0 +1,264 @@
+/* eslint-disable */
+var clone = (function() {
+ 'use strict';
+
+ function _instanceof(obj, type) {
+ return type != null && obj instanceof type;
+ }
+
+ var nativeMap;
+ try {
+ nativeMap = Map;
+ } catch(_) {
+ // maybe a reference error because no `Map`. Give it a dummy value that no
+ // value will ever be an instanceof.
+ nativeMap = function() {};
+ }
+
+ var nativeSet;
+ try {
+ nativeSet = Set;
+ } catch(_) {
+ nativeSet = function() {};
+ }
+
+ var nativePromise;
+ try {
+ nativePromise = Promise;
+ } catch(_) {
+ nativePromise = function() {};
+ }
+
+ /**
+ * Clones (copies) an Object using deep copying.
+ *
+ * This function supports circular references by default, but if you are certain
+ * there are no circular references in your object, you can save some CPU time
+ * by calling clone(obj, false).
+ *
+ * Caution: if `circular` is false and `parent` contains circular references,
+ * your program may enter an infinite loop and crash.
+ *
+ * @param `parent` - the object to be cloned
+ * @param `circular` - set to true if the object to be cloned may contain
+ * circular references. (optional - true by default)
+ * @param `depth` - set to a number if the object is only to be cloned to
+ * a particular depth. (optional - defaults to Infinity)
+ * @param `prototype` - sets the prototype to be used when cloning an object.
+ * (optional - defaults to parent prototype).
+ * @param `includeNonEnumerable` - set to true if the non-enumerable properties
+ * should be cloned as well. Non-enumerable properties on the prototype
+ * chain will be ignored. (optional - false by default)
+ */
+ function clone(parent, circular, depth, prototype, includeNonEnumerable) {
+ if (typeof circular === 'object') {
+ depth = circular.depth;
+ prototype = circular.prototype;
+ includeNonEnumerable = circular.includeNonEnumerable;
+ circular = circular.circular;
+ }
+ // maintain two arrays for circular references, where corresponding parents
+ // and children have the same index
+ var allParents = [];
+ var allChildren = [];
+
+ var useBuffer = typeof Buffer != 'undefined';
+
+ if (typeof circular == 'undefined')
+ circular = true;
+
+ if (typeof depth == 'undefined')
+ depth = Infinity;
+
+ // recurse this function so we don't reset allParents and allChildren
+ function _clone(parent, depth) {
+ // cloning null always returns null
+ if (parent === null)
+ return null;
+
+ if (depth === 0)
+ return parent;
+
+ var child;
+ var proto;
+ if (typeof parent != 'object') {
+ return parent;
+ }
+
+ if (_instanceof(parent, nativeMap)) {
+ child = new nativeMap();
+ } else if (_instanceof(parent, nativeSet)) {
+ child = new nativeSet();
+ } else if (_instanceof(parent, nativePromise)) {
+ child = new nativePromise(function (resolve, reject) {
+ parent.then(function(value) {
+ resolve(_clone(value, depth - 1));
+ }, function(err) {
+ reject(_clone(err, depth - 1));
+ });
+ });
+ } else if (clone.__isArray(parent)) {
+ child = [];
+ } else if (clone.__isRegExp(parent)) {
+ child = new RegExp(parent.source, __getRegExpFlags(parent));
+ if (parent.lastIndex) child.lastIndex = parent.lastIndex;
+ } else if (clone.__isDate(parent)) {
+ child = new Date(parent.getTime());
+ } else if (useBuffer && Buffer.isBuffer(parent)) {
+ if (Buffer.from) {
+ // Node.js >= 5.10.0
+ child = Buffer.from(parent);
+ } else {
+ // Older Node.js versions
+ child = new Buffer(parent.length);
+ parent.copy(child);
+ }
+ return child;
+ } else if (_instanceof(parent, Error)) {
+ child = Object.create(parent);
+ } else {
+ if (typeof prototype == 'undefined') {
+ proto = Object.getPrototypeOf(parent);
+ child = Object.create(proto);
+ }
+ else {
+ child = Object.create(prototype);
+ proto = prototype;
+ }
+ }
+
+ if (circular) {
+ var index = allParents.indexOf(parent);
+
+ if (index != -1) {
+ return allChildren[index];
+ }
+ allParents.push(parent);
+ allChildren.push(child);
+ }
+
+ if (_instanceof(parent, nativeMap)) {
+ parent.forEach(function(value, key) {
+ var keyChild = _clone(key, depth - 1);
+ var valueChild = _clone(value, depth - 1);
+ child.set(keyChild, valueChild);
+ });
+ }
+ if (_instanceof(parent, nativeSet)) {
+ parent.forEach(function(value) {
+ var entryChild = _clone(value, depth - 1);
+ child.add(entryChild);
+ });
+ }
+
+ for (var i in parent) {
+ var attrs = Object.getOwnPropertyDescriptor(parent, i);
+ if (attrs) {
+ child[i] = _clone(parent[i], depth - 1);
+ }
+
+ try {
+ var objProperty = Object.getOwnPropertyDescriptor(parent, i);
+ if (objProperty.set === 'undefined') {
+ // no setter defined. Skip cloning this property
+ continue;
+ }
+ child[i] = _clone(parent[i], depth - 1);
+ } catch(e){
+ if (e instanceof TypeError) {
+ // when in strict mode, TypeError will be thrown if child[i] property only has a getter
+ // we can't do anything about this, other than inform the user that this property cannot be set.
+ continue
+ } else if (e instanceof ReferenceError) {
+ //this may happen in non strict mode
+ continue
+ }
+ }
+
+ }
+
+ if (Object.getOwnPropertySymbols) {
+ var symbols = Object.getOwnPropertySymbols(parent);
+ for (var i = 0; i < symbols.length; i++) {
+ // Don't need to worry about cloning a symbol because it is a primitive,
+ // like a number or string.
+ var symbol = symbols[i];
+ var descriptor = Object.getOwnPropertyDescriptor(parent, symbol);
+ if (descriptor && !descriptor.enumerable && !includeNonEnumerable) {
+ continue;
+ }
+ child[symbol] = _clone(parent[symbol], depth - 1);
+ Object.defineProperty(child, symbol, descriptor);
+ }
+ }
+
+ if (includeNonEnumerable) {
+ var allPropertyNames = Object.getOwnPropertyNames(parent);
+ for (var i = 0; i < allPropertyNames.length; i++) {
+ var propertyName = allPropertyNames[i];
+ var descriptor = Object.getOwnPropertyDescriptor(parent, propertyName);
+ if (descriptor && descriptor.enumerable) {
+ continue;
+ }
+ child[propertyName] = _clone(parent[propertyName], depth - 1);
+ Object.defineProperty(child, propertyName, descriptor);
+ }
+ }
+
+ return child;
+ }
+
+ return _clone(parent, depth);
+ }
+
+ /**
+ * Simple flat clone using prototype, accepts only objects, usefull for property
+ * override on FLAT configuration object (no nested props).
+ *
+ * USE WITH CAUTION! This may not behave as you wish if you do not know how this
+ * works.
+ */
+ clone.clonePrototype = function clonePrototype(parent) {
+ if (parent === null)
+ return null;
+
+ var c = function () {};
+ c.prototype = parent;
+ return new c();
+ };
+
+// private utility functions
+
+ function __objToStr(o) {
+ return Object.prototype.toString.call(o);
+ }
+ clone.__objToStr = __objToStr;
+
+ function __isDate(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object Date]';
+ }
+ clone.__isDate = __isDate;
+
+ function __isArray(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object Array]';
+ }
+ clone.__isArray = __isArray;
+
+ function __isRegExp(o) {
+ return typeof o === 'object' && __objToStr(o) === '[object RegExp]';
+ }
+ clone.__isRegExp = __isRegExp;
+
+ function __getRegExpFlags(re) {
+ var flags = '';
+ if (re.global) flags += 'g';
+ if (re.ignoreCase) flags += 'i';
+ if (re.multiline) flags += 'm';
+ return flags;
+ }
+ clone.__getRegExpFlags = __getRegExpFlags;
+
+ return clone;
+})();
+
+export default clone
diff --git a/uni_modules/uv-ui-tools/libs/mixin/button.js b/uni_modules/uv-ui-tools/libs/mixin/button.js
new file mode 100644
index 0000000..0c019c2
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/mixin/button.js
@@ -0,0 +1,13 @@
+export default {
+ props: {
+ lang: String,
+ sessionFrom: String,
+ sendMessageTitle: String,
+ sendMessagePath: String,
+ sendMessageImg: String,
+ showMessageCard: Boolean,
+ appParameter: String,
+ formType: String,
+ openType: String
+ }
+}
diff --git a/uni_modules/uv-ui-tools/libs/mixin/mixin.js b/uni_modules/uv-ui-tools/libs/mixin/mixin.js
new file mode 100644
index 0000000..0dd3b03
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/mixin/mixin.js
@@ -0,0 +1,172 @@
+import * as index from '../function/index.js';
+import * as test from '../function/test.js';
+import route from '../util/route.js';
+import debounce from '../function/debounce.js';
+import throttle from '../function/throttle.js';
+export default {
+ // 定义每个组件都可能需要用到的外部样式以及类名
+ props: {
+ // 每个组件都有的父组件传递的样式,可以为字符串或者对象形式
+ customStyle: {
+ type: [Object, String],
+ default: () => ({})
+ },
+ customClass: {
+ type: String,
+ default: ''
+ },
+ // 跳转的页面路径
+ url: {
+ type: String,
+ default: ''
+ },
+ // 页面跳转的类型
+ linkType: {
+ type: String,
+ default: 'navigateTo'
+ }
+ },
+ data() {
+ return {}
+ },
+ onLoad() {
+ // getRect挂载到$uv上,因为这方法需要使用in(this),所以无法把它独立成一个单独的文件导出
+ this.$uv.getRect = this.$uvGetRect
+ },
+ created() {
+ // 组件当中,只有created声明周期,为了能在组件使用,故也在created中将方法挂载到$uv
+ this.$uv.getRect = this.$uvGetRect
+ },
+ computed: {
+ $uv() {
+ return {
+ ...index,
+ test,
+ route,
+ debounce,
+ throttle,
+ unit: uni?.$uv?.config?.unit
+ }
+ },
+ /**
+ * 生成bem规则类名
+ * 由于微信小程序,H5,nvue之间绑定class的差异,无法通过:class="[bem()]"的形式进行同用
+ * 故采用如下折中做法,最后返回的是数组(一般平台)或字符串(支付宝和字节跳动平台),类似['a', 'b', 'c']或'a b c'的形式
+ * @param {String} name 组件名称
+ * @param {Array} fixed 一直会存在的类名
+ * @param {Array} change 会根据变量值为true或者false而出现或者隐藏的类名
+ * @returns {Array|string}
+ */
+ bem() {
+ return function(name, fixed, change) {
+ // 类名前缀
+ const prefix = `uv-${name}--`
+ const classes = {}
+ if (fixed) {
+ fixed.map((item) => {
+ // 这里的类名,会一直存在
+ classes[prefix + this[item]] = true
+ })
+ }
+ if (change) {
+ change.map((item) => {
+ // 这里的类名,会根据this[item]的值为true或者false,而进行添加或者移除某一个类
+ this[item] ? (classes[prefix + item] = this[item]) : (delete classes[prefix + item])
+ })
+ }
+ return Object.keys(classes)
+ // 支付宝,头条小程序无法动态绑定一个数组类名,否则解析出来的结果会带有",",而导致失效
+ // #ifdef MP-ALIPAY || MP-TOUTIAO || MP-LARK || MP-BAIDU
+ .join(' ')
+ // #endif
+ }
+ }
+ },
+ methods: {
+ // 跳转某一个页面
+ openPage(urlKey = 'url') {
+ const url = this[urlKey]
+ if (url) {
+ // 执行类似uni.navigateTo的方法
+ uni[this.linkType]({
+ url
+ })
+ }
+ },
+ // 查询节点信息
+ // 目前此方法在支付宝小程序中无法获取组件跟接点的尺寸,为支付宝的bug(2020-07-21)
+ // 解决办法为在组件根部再套一个没有任何作用的view元素
+ $uvGetRect(selector, all) {
+ return new Promise((resolve) => {
+ uni.createSelectorQuery()
+ .in(this)[all ? 'selectAll' : 'select'](selector)
+ .boundingClientRect((rect) => {
+ if (all && Array.isArray(rect) && rect.length) {
+ resolve(rect)
+ }
+ if (!all && rect) {
+ resolve(rect)
+ }
+ })
+ .exec()
+ })
+ },
+ getParentData(parentName = '') {
+ // 避免在created中去定义parent变量
+ if (!this.parent) this.parent = {}
+ // 这里的本质原理是,通过获取父组件实例(也即类似uv-radio的父组件uv-radio-group的this)
+ // 将父组件this中对应的参数,赋值给本组件(uv-radio的this)的parentData对象中对应的属性
+ // 之所以需要这么做,是因为所有端中,头条小程序不支持通过this.parent.xxx去监听父组件参数的变化
+ // 此处并不会自动更新子组件的数据,而是依赖父组件uv-radio-group去监听data的变化,手动调用更新子组件的方法去重新获取
+ this.parent = this.$uv.$parent.call(this, parentName)
+ if (this.parent.children) {
+ // 如果父组件的children不存在本组件的实例,才将本实例添加到父组件的children中
+ this.parent.children.indexOf(this) === -1 && this.parent.children.push(this)
+ }
+ if (this.parent && this.parentData) {
+ // 历遍parentData中的属性,将parent中的同名属性赋值给parentData
+ Object.keys(this.parentData).map((key) => {
+ this.parentData[key] = this.parent[key]
+ })
+ }
+ },
+ // 阻止事件冒泡
+ preventEvent(e) {
+ e && typeof(e.stopPropagation) === 'function' && e.stopPropagation()
+ },
+ // 空操作
+ noop(e) {
+ this.preventEvent(e)
+ }
+ },
+ onReachBottom() {
+ uni.$emit('uvOnReachBottom')
+ },
+ beforeDestroy() {
+ // 判断当前页面是否存在parent和chldren,一般在checkbox和checkbox-group父子联动的场景会有此情况
+ // 组件销毁时,移除子组件在父组件children数组中的实例,释放资源,避免数据混乱
+ if (this.parent && test.array(this.parent.children)) {
+ // 组件销毁时,移除父组件中的children数组中对应的实例
+ const childrenList = this.parent.children
+ childrenList.map((child, index) => {
+ // 如果相等,则移除
+ if (child === this) {
+ childrenList.splice(index, 1)
+ }
+ })
+ }
+ },
+ // 兼容vue3
+ unmounted() {
+ if (this.parent && test.array(this.parent.children)) {
+ // 组件销毁时,移除父组件中的children数组中对应的实例
+ const childrenList = this.parent.children
+ childrenList.map((child, index) => {
+ // 如果相等,则移除
+ if (child === this) {
+ childrenList.splice(index, 1)
+ }
+ })
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js b/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js
new file mode 100644
index 0000000..90b6903
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/mixin/mpMixin.js
@@ -0,0 +1,8 @@
+export default {
+ // #ifdef MP-WEIXIN
+ // 将自定义节点设置成虚拟的(去掉自定义组件包裹层),更加接近Vue组件的表现,能更好的使用flex属性
+ options: {
+ virtualHost: true
+ }
+ // #endif
+}
\ No newline at end of file
diff --git a/uni_modules/uv-ui-tools/libs/mixin/mpShare.js b/uni_modules/uv-ui-tools/libs/mixin/mpShare.js
new file mode 100644
index 0000000..c9695a0
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/mixin/mpShare.js
@@ -0,0 +1,13 @@
+export default {
+ onLoad() {
+ // 设置默认的转发参数
+ uni.$uv.mpShare = {
+ title: '', // 默认为小程序名称
+ path: '', // 默认为当前页面路径
+ imageUrl: '' // 默认为当前页面的截图
+ }
+ },
+ onShareAppMessage() {
+ return uni.$uv.mpShare
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/uv-ui-tools/libs/mixin/openType.js b/uni_modules/uv-ui-tools/libs/mixin/openType.js
new file mode 100644
index 0000000..1b94b7e
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/mixin/openType.js
@@ -0,0 +1,47 @@
+export default {
+ props: {
+ openType: String
+ },
+ emits: ['getphonenumber','getuserinfo','error','opensetting','launchapp','contact','chooseavatar','addgroupapp','chooseaddress','subscribe','login','im'],
+ methods: {
+ onGetPhoneNumber(event) {
+ this.$emit('getphonenumber', event.detail)
+ },
+ onGetUserInfo(event) {
+ this.$emit('getuserinfo', event.detail)
+ },
+ onError(event) {
+ this.$emit('error', event.detail)
+ },
+ onOpenSetting(event) {
+ this.$emit('opensetting', event.detail)
+ },
+ onLaunchApp(event) {
+ this.$emit('launchapp', event.detail)
+ },
+ onContact(event) {
+ this.$emit('contact', event.detail)
+ },
+ onChooseavatar(event) {
+ this.$emit('chooseavatar', event.detail)
+ },
+ onAgreeprivacyauthorization(event) {
+ this.$emit('agreeprivacyauthorization', event.detail)
+ },
+ onAddgroupapp(event) {
+ this.$emit('addgroupapp', event.detail)
+ },
+ onChooseaddress(event) {
+ this.$emit('chooseaddress', event.detail)
+ },
+ onSubscribe(event) {
+ this.$emit('subscribe', event.detail)
+ },
+ onLogin(event) {
+ this.$emit('login', event.detail)
+ },
+ onIm(event) {
+ this.$emit('im', event.detail)
+ }
+ }
+}
diff --git a/uni_modules/uv-ui-tools/libs/mixin/touch.js b/uni_modules/uv-ui-tools/libs/mixin/touch.js
new file mode 100644
index 0000000..0ecbd88
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/mixin/touch.js
@@ -0,0 +1,59 @@
+const MIN_DISTANCE = 10
+
+function getDirection(x, y) {
+ if (x > y && x > MIN_DISTANCE) {
+ return 'horizontal'
+ }
+ if (y > x && y > MIN_DISTANCE) {
+ return 'vertical'
+ }
+ return ''
+}
+
+export default {
+ methods: {
+ getTouchPoint(e) {
+ if (!e) {
+ return {
+ x: 0,
+ y: 0
+ }
+ } if (e.touches && e.touches[0]) {
+ return {
+ x: e.touches[0].pageX,
+ y: e.touches[0].pageY
+ }
+ } if (e.changedTouches && e.changedTouches[0]) {
+ return {
+ x: e.changedTouches[0].pageX,
+ y: e.changedTouches[0].pageY
+ }
+ }
+ return {
+ x: e.clientX || 0,
+ y: e.clientY || 0
+ }
+ },
+ resetTouchStatus() {
+ this.direction = ''
+ this.deltaX = 0
+ this.deltaY = 0
+ this.offsetX = 0
+ this.offsetY = 0
+ },
+ touchStart(event) {
+ this.resetTouchStatus()
+ const touch = this.getTouchPoint(event)
+ this.startX = touch.x
+ this.startY = touch.y
+ },
+ touchMove(event) {
+ const touch = this.getTouchPoint(event)
+ this.deltaX = touch.x - this.startX
+ this.deltaY = touch.y - this.startY
+ this.offsetX = Math.abs(this.deltaX)
+ this.offsetY = Math.abs(this.deltaY)
+ this.direction = this.direction || getDirection(this.offsetX, this.offsetY)
+ }
+ }
+}
diff --git a/uni_modules/uv-ui-tools/libs/util/dayjs.js b/uni_modules/uv-ui-tools/libs/util/dayjs.js
new file mode 100644
index 0000000..c84ab68
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/util/dayjs.js
@@ -0,0 +1,216 @@
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __commonJS = (cb, mod) => function __require() {
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
+};
+
+var require_dayjs_min = __commonJS({
+ "uvuidayjs"(exports, module) {
+ !function(t, e) {
+ "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).dayjs = e();
+ }(exports, function() {
+ "use strict";
+ var t = 1e3, e = 6e4, n = 36e5, r = "millisecond", i = "second", s = "minute", u = "hour", a = "day", o = "week", f = "month", h = "quarter", c = "year", d = "date", l = "Invalid Date", $ = /^(\d{4})[-/]?(\d{1,2})?[-/]?(\d{0,2})[Tt\s]*(\d{1,2})?:?(\d{1,2})?:?(\d{1,2})?[.:]?(\d+)?$/, y = /\[([^\]]+)]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a|A|m{1,2}|s{1,2}|Z{1,2}|SSS/g, M = { name: "en", weekdays: "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"), months: "January_February_March_April_May_June_July_August_September_October_November_December".split("_"), ordinal: function(t2) {
+ var e2 = ["th", "st", "nd", "rd"], n2 = t2 % 100;
+ return "[" + t2 + (e2[(n2 - 20) % 10] || e2[n2] || e2[0]) + "]";
+ } }, m = function(t2, e2, n2) {
+ var r2 = String(t2);
+ return !r2 || r2.length >= e2 ? t2 : "" + Array(e2 + 1 - r2.length).join(n2) + t2;
+ }, v = { s: m, z: function(t2) {
+ var e2 = -t2.utcOffset(), n2 = Math.abs(e2), r2 = Math.floor(n2 / 60), i2 = n2 % 60;
+ return (e2 <= 0 ? "+" : "-") + m(r2, 2, "0") + ":" + m(i2, 2, "0");
+ }, m: function t2(e2, n2) {
+ if (e2.date() < n2.date())
+ return -t2(n2, e2);
+ var r2 = 12 * (n2.year() - e2.year()) + (n2.month() - e2.month()), i2 = e2.clone().add(r2, f), s2 = n2 - i2 < 0, u2 = e2.clone().add(r2 + (s2 ? -1 : 1), f);
+ return +(-(r2 + (n2 - i2) / (s2 ? i2 - u2 : u2 - i2)) || 0);
+ }, a: function(t2) {
+ return t2 < 0 ? Math.ceil(t2) || 0 : Math.floor(t2);
+ }, p: function(t2) {
+ return { M: f, y: c, w: o, d: a, D: d, h: u, m: s, s: i, ms: r, Q: h }[t2] || String(t2 || "").toLowerCase().replace(/s$/, "");
+ }, u: function(t2) {
+ return void 0 === t2;
+ } }, g = "en", D = {};
+ D[g] = M;
+ var p = function(t2) {
+ return t2 instanceof _;
+ }, S = function t2(e2, n2, r2) {
+ var i2;
+ if (!e2)
+ return g;
+ if ("string" == typeof e2) {
+ var s2 = e2.toLowerCase();
+ D[s2] && (i2 = s2), n2 && (D[s2] = n2, i2 = s2);
+ var u2 = e2.split("-");
+ if (!i2 && u2.length > 1)
+ return t2(u2[0]);
+ } else {
+ var a2 = e2.name;
+ D[a2] = e2, i2 = a2;
+ }
+ return !r2 && i2 && (g = i2), i2 || !r2 && g;
+ }, w = function(t2, e2) {
+ if (p(t2))
+ return t2.clone();
+ var n2 = "object" == typeof e2 ? e2 : {};
+ return n2.date = t2, n2.args = arguments, new _(n2);
+ }, O = v;
+ O.l = S, O.i = p, O.w = function(t2, e2) {
+ return w(t2, { locale: e2.$L, utc: e2.$u, x: e2.$x, $offset: e2.$offset });
+ };
+ var _ = function() {
+ function M2(t2) {
+ this.$L = S(t2.locale, null, true), this.parse(t2);
+ }
+ var m2 = M2.prototype;
+ return m2.parse = function(t2) {
+ this.$d = function(t3) {
+ var e2 = t3.date, n2 = t3.utc;
+ if (null === e2)
+ return new Date(NaN);
+ if (O.u(e2))
+ return new Date();
+ if (e2 instanceof Date)
+ return new Date(e2);
+ if ("string" == typeof e2 && !/Z$/i.test(e2)) {
+ var r2 = e2.match($);
+ if (r2) {
+ var i2 = r2[2] - 1 || 0, s2 = (r2[7] || "0").substring(0, 3);
+ return n2 ? new Date(Date.UTC(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2)) : new Date(r2[1], i2, r2[3] || 1, r2[4] || 0, r2[5] || 0, r2[6] || 0, s2);
+ }
+ }
+ return new Date(e2);
+ }(t2), this.$x = t2.x || {}, this.init();
+ }, m2.init = function() {
+ var t2 = this.$d;
+ this.$y = t2.getFullYear(), this.$M = t2.getMonth(), this.$D = t2.getDate(), this.$W = t2.getDay(), this.$H = t2.getHours(), this.$m = t2.getMinutes(), this.$s = t2.getSeconds(), this.$ms = t2.getMilliseconds();
+ }, m2.$utils = function() {
+ return O;
+ }, m2.isValid = function() {
+ return !(this.$d.toString() === l);
+ }, m2.isSame = function(t2, e2) {
+ var n2 = w(t2);
+ return this.startOf(e2) <= n2 && n2 <= this.endOf(e2);
+ }, m2.isAfter = function(t2, e2) {
+ return w(t2) < this.startOf(e2);
+ }, m2.isBefore = function(t2, e2) {
+ return this.endOf(e2) < w(t2);
+ }, m2.$g = function(t2, e2, n2) {
+ return O.u(t2) ? this[e2] : this.set(n2, t2);
+ }, m2.unix = function() {
+ return Math.floor(this.valueOf() / 1e3);
+ }, m2.valueOf = function() {
+ return this.$d.getTime();
+ }, m2.startOf = function(t2, e2) {
+ var n2 = this, r2 = !!O.u(e2) || e2, h2 = O.p(t2), l2 = function(t3, e3) {
+ var i2 = O.w(n2.$u ? Date.UTC(n2.$y, e3, t3) : new Date(n2.$y, e3, t3), n2);
+ return r2 ? i2 : i2.endOf(a);
+ }, $2 = function(t3, e3) {
+ return O.w(n2.toDate()[t3].apply(n2.toDate("s"), (r2 ? [0, 0, 0, 0] : [23, 59, 59, 999]).slice(e3)), n2);
+ }, y2 = this.$W, M3 = this.$M, m3 = this.$D, v2 = "set" + (this.$u ? "UTC" : "");
+ switch (h2) {
+ case c:
+ return r2 ? l2(1, 0) : l2(31, 11);
+ case f:
+ return r2 ? l2(1, M3) : l2(0, M3 + 1);
+ case o:
+ var g2 = this.$locale().weekStart || 0, D2 = (y2 < g2 ? y2 + 7 : y2) - g2;
+ return l2(r2 ? m3 - D2 : m3 + (6 - D2), M3);
+ case a:
+ case d:
+ return $2(v2 + "Hours", 0);
+ case u:
+ return $2(v2 + "Minutes", 1);
+ case s:
+ return $2(v2 + "Seconds", 2);
+ case i:
+ return $2(v2 + "Milliseconds", 3);
+ default:
+ return this.clone();
+ }
+ }, m2.endOf = function(t2) {
+ return this.startOf(t2, false);
+ }, m2.$set = function(t2, e2) {
+ var n2, o2 = O.p(t2), h2 = "set" + (this.$u ? "UTC" : ""), l2 = (n2 = {}, n2[a] = h2 + "Date", n2[d] = h2 + "Date", n2[f] = h2 + "Month", n2[c] = h2 + "FullYear", n2[u] = h2 + "Hours", n2[s] = h2 + "Minutes", n2[i] = h2 + "Seconds", n2[r] = h2 + "Milliseconds", n2)[o2], $2 = o2 === a ? this.$D + (e2 - this.$W) : e2;
+ if (o2 === f || o2 === c) {
+ var y2 = this.clone().set(d, 1);
+ y2.$d[l2]($2), y2.init(), this.$d = y2.set(d, Math.min(this.$D, y2.daysInMonth())).$d;
+ } else
+ l2 && this.$d[l2]($2);
+ return this.init(), this;
+ }, m2.set = function(t2, e2) {
+ return this.clone().$set(t2, e2);
+ }, m2.get = function(t2) {
+ return this[O.p(t2)]();
+ }, m2.add = function(r2, h2) {
+ var d2, l2 = this;
+ r2 = Number(r2);
+ var $2 = O.p(h2), y2 = function(t2) {
+ var e2 = w(l2);
+ return O.w(e2.date(e2.date() + Math.round(t2 * r2)), l2);
+ };
+ if ($2 === f)
+ return this.set(f, this.$M + r2);
+ if ($2 === c)
+ return this.set(c, this.$y + r2);
+ if ($2 === a)
+ return y2(1);
+ if ($2 === o)
+ return y2(7);
+ var M3 = (d2 = {}, d2[s] = e, d2[u] = n, d2[i] = t, d2)[$2] || 1, m3 = this.$d.getTime() + r2 * M3;
+ return O.w(m3, this);
+ }, m2.subtract = function(t2, e2) {
+ return this.add(-1 * t2, e2);
+ }, m2.format = function(t2) {
+ var e2 = this, n2 = this.$locale();
+ if (!this.isValid())
+ return n2.invalidDate || l;
+ var r2 = t2 || "YYYY-MM-DDTHH:mm:ssZ", i2 = O.z(this), s2 = this.$H, u2 = this.$m, a2 = this.$M, o2 = n2.weekdays, f2 = n2.months, h2 = function(t3, n3, i3, s3) {
+ return t3 && (t3[n3] || t3(e2, r2)) || i3[n3].slice(0, s3);
+ }, c2 = function(t3) {
+ return O.s(s2 % 12 || 12, t3, "0");
+ }, d2 = n2.meridiem || function(t3, e3, n3) {
+ var r3 = t3 < 12 ? "AM" : "PM";
+ return n3 ? r3.toLowerCase() : r3;
+ }, $2 = { YY: String(this.$y).slice(-2), YYYY: this.$y, M: a2 + 1, MM: O.s(a2 + 1, 2, "0"), MMM: h2(n2.monthsShort, a2, f2, 3), MMMM: h2(f2, a2), D: this.$D, DD: O.s(this.$D, 2, "0"), d: String(this.$W), dd: h2(n2.weekdaysMin, this.$W, o2, 2), ddd: h2(n2.weekdaysShort, this.$W, o2, 3), dddd: o2[this.$W], H: String(s2), HH: O.s(s2, 2, "0"), h: c2(1), hh: c2(2), a: d2(s2, u2, true), A: d2(s2, u2, false), m: String(u2), mm: O.s(u2, 2, "0"), s: String(this.$s), ss: O.s(this.$s, 2, "0"), SSS: O.s(this.$ms, 3, "0"), Z: i2 };
+ return r2.replace(y, function(t3, e3) {
+ return e3 || $2[t3] || i2.replace(":", "");
+ });
+ }, m2.utcOffset = function() {
+ return 15 * -Math.round(this.$d.getTimezoneOffset() / 15);
+ }, m2.diff = function(r2, d2, l2) {
+ var $2, y2 = O.p(d2), M3 = w(r2), m3 = (M3.utcOffset() - this.utcOffset()) * e, v2 = this - M3, g2 = O.m(this, M3);
+ return g2 = ($2 = {}, $2[c] = g2 / 12, $2[f] = g2, $2[h] = g2 / 3, $2[o] = (v2 - m3) / 6048e5, $2[a] = (v2 - m3) / 864e5, $2[u] = v2 / n, $2[s] = v2 / e, $2[i] = v2 / t, $2)[y2] || v2, l2 ? g2 : O.a(g2);
+ }, m2.daysInMonth = function() {
+ return this.endOf(f).$D;
+ }, m2.$locale = function() {
+ return D[this.$L];
+ }, m2.locale = function(t2, e2) {
+ if (!t2)
+ return this.$L;
+ var n2 = this.clone(), r2 = S(t2, e2, true);
+ return r2 && (n2.$L = r2), n2;
+ }, m2.clone = function() {
+ return O.w(this.$d, this);
+ }, m2.toDate = function() {
+ return new Date(this.valueOf());
+ }, m2.toJSON = function() {
+ return this.isValid() ? this.toISOString() : null;
+ }, m2.toISOString = function() {
+ return this.$d.toISOString();
+ }, m2.toString = function() {
+ return this.$d.toUTCString();
+ }, M2;
+ }(), T = _.prototype;
+ return w.prototype = T, [["$ms", r], ["$s", i], ["$m", s], ["$H", u], ["$W", a], ["$M", f], ["$y", c], ["$D", d]].forEach(function(t2) {
+ T[t2[1]] = function(e2) {
+ return this.$g(e2, t2[0], t2[1]);
+ };
+ }), w.extend = function(t2, e2) {
+ return t2.$i || (t2(e2, _, w), t2.$i = true), w;
+ }, w.locale = S, w.isDayjs = p, w.unix = function(t2) {
+ return w(1e3 * t2);
+ }, w.en = D[g], w.Ls = D, w.p = {}, w;
+ });
+ }
+});
+export default require_dayjs_min();
diff --git a/uni_modules/uv-ui-tools/libs/util/route.js b/uni_modules/uv-ui-tools/libs/util/route.js
new file mode 100644
index 0000000..80c0afd
--- /dev/null
+++ b/uni_modules/uv-ui-tools/libs/util/route.js
@@ -0,0 +1,126 @@
+/**
+ * 路由跳转方法,该方法相对于直接使用uni.xxx的好处是使用更加简单快捷
+ * 并且带有路由拦截功能
+ */
+import { queryParams, deepMerge, page } from '@/uni_modules/uv-ui-tools/libs/function/index.js'
+class Router {
+ constructor() {
+ // 原始属性定义
+ this.config = {
+ type: 'navigateTo',
+ url: '',
+ delta: 1, // navigateBack页面后退时,回退的层数
+ params: {}, // 传递的参数
+ animationType: 'pop-in', // 窗口动画,只在APP有效
+ animationDuration: 300, // 窗口动画持续时间,单位毫秒,只在APP有效
+ intercept: false ,// 是否需要拦截
+ events: {} // 页面间通信接口,用于监听被打开页面发送到当前页面的数据。hbuilderx 2.8.9+ 开始支持。
+ }
+ // 因为route方法是需要对外赋值给另外的对象使用,同时route内部有使用this,会导致route失去上下文
+ // 这里在构造函数中进行this绑定
+ this.route = this.route.bind(this)
+ }
+
+ // 判断url前面是否有"/",如果没有则加上,否则无法跳转
+ addRootPath(url) {
+ return url[0] === '/' ? url : `/${url}`
+ }
+
+ // 整合路由参数
+ mixinParam(url, params) {
+ url = url && this.addRootPath(url)
+
+ // 使用正则匹配,主要依据是判断是否有"/","?","="等,如“/page/index/index?name=mary"
+ // 如果有url中有get参数,转换后无需带上"?"
+ let query = ''
+ if (/.*\/.*\?.*=.*/.test(url)) {
+ // object对象转为get类型的参数
+ query = queryParams(params, false)
+ // 因为已有get参数,所以后面拼接的参数需要带上"&"隔开
+ return url += `&${query}`
+ }
+ // 直接拼接参数,因为此处url中没有后面的query参数,也就没有"?/&"之类的符号
+ query = queryParams(params)
+ return url += query
+ }
+
+ // 对外的方法名称
+ async route(options = {}, params = {}) {
+ // 合并用户的配置和内部的默认配置
+ let mergeConfig = {}
+
+ if (typeof options === 'string') {
+ // 如果options为字符串,则为route(url, params)的形式
+ mergeConfig.url = this.mixinParam(options, params)
+ mergeConfig.type = 'navigateTo'
+ } else {
+ mergeConfig = deepMerge(this.config, options)
+ // 否则正常使用mergeConfig中的url和params进行拼接
+ mergeConfig.url = this.mixinParam(options.url, options.params)
+ }
+ // 如果本次跳转的路径和本页面路径一致,不执行跳转,防止用户快速点击跳转按钮,造成多次跳转同一个页面的问题
+ if (mergeConfig.url === page()) return
+
+ if (params.intercept) {
+ mergeConfig.intercept = params.intercept
+ }
+ // params参数也带给拦截器
+ mergeConfig.params = params
+ // 合并内外部参数
+ mergeConfig = deepMerge(this.config, mergeConfig)
+ // 判断用户是否定义了拦截器
+ if (typeof mergeConfig.intercept === 'function') {
+ // 定一个promise,根据用户执行resolve(true)或者resolve(false)来决定是否进行路由跳转
+ const isNext = await new Promise((resolve, reject) => {
+ mergeConfig.intercept(mergeConfig, resolve)
+ })
+ // 如果isNext为true,则执行路由跳转
+ isNext && this.openPage(mergeConfig)
+ } else {
+ this.openPage(mergeConfig)
+ }
+ }
+
+ // 执行路由跳转
+ openPage(config) {
+ // 解构参数
+ const {
+ url,
+ type,
+ delta,
+ animationType,
+ animationDuration,
+ events
+ } = config
+ if (config.type == 'navigateTo' || config.type == 'to') {
+ uni.navigateTo({
+ url,
+ animationType,
+ animationDuration,
+ events
+ })
+ }
+ if (config.type == 'redirectTo' || config.type == 'redirect') {
+ uni.redirectTo({
+ url
+ })
+ }
+ if (config.type == 'switchTab' || config.type == 'tab') {
+ uni.switchTab({
+ url
+ })
+ }
+ if (config.type == 'reLaunch' || config.type == 'launch') {
+ uni.reLaunch({
+ url
+ })
+ }
+ if (config.type == 'navigateBack' || config.type == 'back') {
+ uni.navigateBack({
+ delta
+ })
+ }
+ }
+}
+
+export default (new Router()).route
\ No newline at end of file
diff --git a/uni_modules/uv-ui-tools/package.json b/uni_modules/uv-ui-tools/package.json
new file mode 100644
index 0000000..2d940f6
--- /dev/null
+++ b/uni_modules/uv-ui-tools/package.json
@@ -0,0 +1,81 @@
+{
+ "id": "uv-ui-tools",
+ "displayName": "uv-ui-tools 工具集 全面兼容vue3+2、app、h5、小程序等多端",
+ "version": "1.1.25",
+ "description": "uv-ui-tools,集成工具库,强大的Http请求封装,清晰的文档说明,开箱即用。方便使用,可以全局使用",
+ "keywords": [
+ "uv-ui-tools,uv-ui组件库,工具集,uvui,uView2.x"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "y",
+ "快手": "y",
+ "飞书": "y",
+ "京东": "y"
+ },
+ "快应用": {
+ "华为": "y",
+ "联盟": "y"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/uv-ui-tools/readme.md b/uni_modules/uv-ui-tools/readme.md
new file mode 100644
index 0000000..79a7df5
--- /dev/null
+++ b/uni_modules/uv-ui-tools/readme.md
@@ -0,0 +1,23 @@
+## uv-ui-tools 工具集
+
+> **组件名:uv-ui-tools**
+
+uv-ui工具集成,包括网络Http请求、便捷工具、节流防抖、对象操作、时间格式化、路由跳转、全局唯一标识符、规则校验等等。
+
+该组件推荐配合[uv-ui组件库](https://www.uvui.cn/components/intro.html)使用,单独下载也可以在自己项目中使用,需要做相应的配置,可查看文档。强烈推荐使用[uv-ui组件库](https://www.uvui.cn/components/intro.html),导入组件都会自动导入`uv-ui-tools`。需要在自己的项目中使用请参考[扩展配置](https://www.uvui.cn/components/setting.html)。
+
+uv-ui破釜沉舟之兼容vue3+2、app、h5、多端小程序的uni-app生态框架,大部分组件基于uView2.x,在经过改进后全面支持vue3,部分组件做了进一步的优化,修复大量BUG,支持单独导入,方便开发者选择导入需要的组件。开箱即用,灵活配置。
+
+# 查看文档
+
+## [下载完整示例项目](https://ext.dcloud.net.cn/plugin?name=uv-ui) (请不要 下载插件ZIP)
+
+### [更多插件,请关注uv-ui组件库](https://ext.dcloud.net.cn/plugin?name=uv-ui)
+
+
+
+
+
+
+
+#### 如使用过程中有任何问题反馈,或者您对uv-ui有一些好的建议,欢迎加入uv-ui官方交流群:官方QQ群
\ No newline at end of file
diff --git a/uni_modules/uv-ui-tools/theme.scss b/uni_modules/uv-ui-tools/theme.scss
new file mode 100644
index 0000000..cfaae92
--- /dev/null
+++ b/uni_modules/uv-ui-tools/theme.scss
@@ -0,0 +1,43 @@
+// 此文件为uvUI的主题变量,这些变量目前只能通过uni.scss引入才有效,另外由于
+// uni.scss中引入的样式会同时混入到全局样式文件和单独每一个页面的样式中,造成微信程序包太大,
+// 故uni.scss只建议放scss变量名相关样式,其他的样式可以通过main.js或者App.vue引入
+
+$uv-main-color: #303133;
+$uv-content-color: #606266;
+$uv-tips-color: #909193;
+$uv-light-color: #c0c4cc;
+$uv-border-color: #dadbde;
+$uv-bg-color: #f3f4f6;
+$uv-disabled-color: #c8c9cc;
+
+$uv-primary: #3c9cff;
+$uv-primary-dark: #398ade;
+$uv-primary-disabled: #9acafc;
+$uv-primary-light: #ecf5ff;
+
+$uv-warning: #f9ae3d;
+$uv-warning-dark: #f1a532;
+$uv-warning-disabled: #f9d39b;
+$uv-warning-light: #fdf6ec;
+
+$uv-success: #5ac725;
+$uv-success-dark: #53c21d;
+$uv-success-disabled: #a9e08f;
+$uv-success-light: #f5fff0;
+
+$uv-error: #f56c6c;
+$uv-error-dark: #e45656;
+$uv-error-disabled: #f7b2b2;
+$uv-error-light: #fef0f0;
+
+$uv-info: #909399;
+$uv-info-dark: #767a82;
+$uv-info-disabled: #c4c6c9;
+$uv-info-light: #f4f4f5;
+
+@mixin flex($direction: row) {
+ /* #ifndef APP-NVUE */
+ display: flex;
+ /* #endif */
+ flex-direction: $direction;
+}
\ No newline at end of file
diff --git a/uni_modules/uv-ui/changelog.md b/uni_modules/uv-ui/changelog.md
new file mode 100644
index 0000000..fce7860
--- /dev/null
+++ b/uni_modules/uv-ui/changelog.md
@@ -0,0 +1,353 @@
+## 1.1.20(2024-01-20)
+1. 优化 uv-loading-page重构,避免初始加载的时候先显示页面的问题,小程序平台由于性能问题,可能还是会短暂出现,请注意优化处理
+2. 优化 uv-textarea confirmType默认值改成return,支持换行
+3. 优化 uv-ui-tools luch-request更新
+4. 修复 uv-drop-down parentData不变的BUG
+5. 修复 uv-picker 上个版本引出的BUG
+6. 修复 uv-image 设置show-menu-by-longpress不生效的BUG
+7. 修复 uv-upload 动态设置deletable为false不生效的BUG
+## 1.1.19(2023-12-12)
+1. 优化 uv-toast部分重构,支持取消遮罩不影响点击其他元素 a. 增加z-index参数;b. 增加overlay参数
+2. 优化 关于组件如何阻止事件冒泡,增加相关说明:[组件怎么阻止事件冒泡](https://www.uvui.cn/components/feature.html#组件怎么阻止事件冒泡)
+3. 优化 持续优化文档
+4. 修复 uv-tabs current为字符串activeStyle不生效的BUG
+5. 修复 uv-vtabs 在change回调中设置current,快速点击菜单,导致死循环的BUG
+6. 修复 uv-cell right-icon插槽编译到APP端不显示的BUG,问题来源:[https://gitee.com/climblee/uv-ui/issues/I8LXZI](https://gitee.com/climblee/uv-ui/issues/I8LXZI)
+## 1.1.18(2023-11-28)
+0. 优化 uv-datetime-picker 增加round属性,设置弹窗圆角
+1. 优化 uv-subsection 增加customItemStyle属性,方便修改bar样式,比如设置圆角
+2. 修复 uv-picker issues反馈的问题uv-picker在组合式API的自定义组件中,columns动态赋值无法显示选项:[https://gitee.com/climblee/uv-ui/issues/I8H0GQ](https://gitee.com/climblee/uv-ui/issues/I8H0GQ)
+3. 修复 uv-popup issues问题:[https://gitee.com/climblee/uv-ui/issues/I8HDLO](https://gitee.com/climblee/uv-ui/issues/I8HDLO)
+4. 修复 uv-skeletons 支付宝小程序报错的BUG
+5. 修复 uv-image webp之前未使用的BUG & 微信报错的BUG
+6. 修复 uv-modal 上版本引出的确认和取消按钮均不显示,还有高度的BUG
+## 1.1.17(2023-11-10)
+0. 交流反馈 欢迎加入uv-ui官方群1交流反馈: 549833913(1000+)
+1. 交流反馈 欢迎加入uv-ui官方群2交流反馈: 206060892
+2. 新增 新增其他小组件下载入口:其他小组件
+3. 优化 uv-calendars 增加readonly属性,是否为只读状态,只读状态下禁止选择日期
+4. 修复 uv-search 禁用时,不能触发click的BUG
+5. 修复 uv-checkbox and uv-radio label文字过长,不换行的相关说明及处理
+6. 修复 uv-input and uv-search 点击clear按钮在微信小程序不好触发的BUG
+7. 修复 uv-list 修复设置ellipsis不生效的BUG
+8. 修复 uv-index-list 修复sticky属性不生效的BUG
+## 1.1.16(2023-10-30)
+1. 交流反馈 欢迎加入uv-ui官方群1交流反馈: 549833913(940+/1000)
+2. 交流反馈 欢迎加入uv-ui官方群2交流反馈: 206060892
+3. 新增 uv-skeletons 骨架屏,全新升级骨架屏,更加灵活,体验更加,强烈推荐使用新版骨架屏。一般用于页面在请求远程数据尚未完成时,在内容加载出来前展示与内容布局结构一致的灰白块,提升用户视觉体验。
+4. 优化 uv-button 增加后置插槽suffix,方便在按钮文字后面增加图标等
+5. 优化 uv-popup vue模式内容有背景色,设置圆角被遮挡的情况
+6. 优化 uv-tabs 点击一个选项,change事件重复派发的问题
+7. 优化 持续优化文档及其他
+8. 修复 uv-album 设置singleSize、multipleSize、space等值带单位,存在不显示的BUG
+9. 修复 uv-picker 省市级示例设置defaultValue,再次选择第二列错乱的BUG
+10. 修复 uv-icon imgMode默认值改成aspectFit,否则会导致支付宝平台name设置为本地图片显示不全的BUG
+11. 修复 uv-transition 在APP-IOS上不能正常显示的BUG
+12. 修复 uv-toast、uv-swipe-action、uv-sticky、uv-notify、uv-notice-bar、uv-grid、uv-count-down、uv-code等组件,unmounted兼容vue3
+## 1.1.15(2023-10-12)
+1. 优化 uv-keyboard a. 增加disKeys参数,mode = "car"下,被禁用的键,如:['I','O']; b. 增加customabc参数,mode = "car"下,是否启用自定义中英文切换内容模式,为了兼容支付宝等小程序不兼容嵌套插槽,导致同时显示自定义内容和原始内容; c. 增加ref方法changeCarMode,mode = "car"下, 调用此方法可以切换中英文模式; d. 增加@changeCarInputMode,mode = "car"下,调用此方法可以进行切换中英文; e. 增加插槽abc,mode = "car"下,自定义中英文切换内容
+2. 优化 uv-checkbox uv-radio 优化:https://gitee.com/climblee/uv-ui/issues/I872VD
+3. 优化 uv-picker 将immediate-change默认值改为true,该值在于change回调的及时性,微信小程序生效
+4. 优化 uv-tags 兼容customStyle参数等优化
+5. 修复 uv-transition 部分情况,修改某属性自动关闭的BUG
+6. 修复 uv-calendars 懒加载报错:https://gitee.com/climblee/uv-ui/issues/I869JS
+7. 修复 uv-datetime-picker 设置minDate出现选择错乱的BUG
+8. 修复 uv-input 搜狗输入法下存在不可清空的情况
+9. 修复 uv-calendars selected没有设置了info或者info设置为空字符串后,文本则无法恢复BUG
+## 1.1.14(2023-09-27)
+1. 优化 uv-list-item 可使用customStyle变量进行样式控制
+2. 优化 uv-cell 增加cellStyle参数,方便自定义单元格的样式
+3. 优化 uv-switch 优化细节
+4. 优化 不断优化[文档](https://www.uvui.cn/)
+5. 修复 uv-button 通过customStyle修改按钮宽度,组件中最外层节点不改变的问题
+6. 修复 uv-calendars a. 修复range模式下,selected设置了info后选中后,导致文本不恢复的问题;b. 修复multiple模式下,selected自定义信息的颜色没变,依然是白色
+7. 修复 uv-checkbox uv-checkbox-group之change回调中v-model值不更新的BUG
+## 1.1.13(2023-09-15)
+1. 优化 uv-button a. 增加参数iconSize,用于控制图标的大小;b. 增加open-type="agreePrivacyAuthorization"类型,用户同意隐私协议事件回调
+2. 优化 uv-picker 三级联动的案例:[https://www.uvui.cn/components/picker.html#省市区三级联动](https://www.uvui.cn/components/picker.html#省市区三级联动)
+3. 修复 uv-read-more 全局设置rpx时,导致展开高度不对的BUG
+4. 修复 uv-tabs a. 设置lineWidth未带单位产生的误差BUG;b. 首次加载时,处理下划线会有左到右的过渡效果
+5. 修复 uv-textaera 设置autoHeight后出现高度异常的BUG
+6. 修复 uv-input H5等情况设置禁用或可读情况下,点击事件无效的问题,nvue需要特殊处理
+7. 修复 uv-calendars a. 在vue2+小程序渲染时闪烁的问题;b. 增加allowSameDay参数,是否允许日期范围的起止时间为同一天,mode=range时有效
+8. 修复 uv-safe-bottom 兼容飞书小程序
+9. 修复 uv-album 添加依赖,避免导入运行有误
+10. 修复 uv-ui-tools 优化组件用到的相关
+## 1.1.12(2023-09-10)
+1. 修复 uv-popup a. h5初始zIndex错误的问题;b. 修复全局设置prop无效的问题
+2. 修复 uv-button 修复多个按钮由view包裹,显示在一行宽度不正常的BUG
+3. 修复 uv-modal a. 修复两个按钮之间竖线不显示的问题;b. uv-ui项目自定义按钮示例修复
+4. 修复 uv-calendars 修复国际化失效的BUG
+5. 修复 uv-keyboard 修复键盘change回调事件产生冲突的BUG
+## 1.1.11(2023-09-02)
+1. 优化 uv-calendars a. 去除range参数,由mode="range"替换;b. 新增mode参数,不传 / multiple / range,分别为单日期, 多个日期,选择日期范围;c. 与uv-calendar选择日期的功能保持一致
+2. 优化 uv-modal a. 增加align参数,设置文本对齐方式;b. 增加textStyle参数,扩展文本样式
+3. 优化 uv-datetime-picker a. 增加mode="year"模式,方便只选择年;b. 增加clearDate参数,是否清除上次选择
+4. 修复 uv-ui-tools 设置customstyle同名计算属性报错:The computed property "customStyle" is already defined as a prop
+5. 修复 uv-image a. 设置widthFix时出现显示不全的BUG;b. 修复抖音等平台在width和height属性改变时出现不显示的BUG
+6. 修复 uv-checkbox 点击空隙处或label插槽内容不会选中的问题
+7. 修复 uv-radio 点击空隙处或label插槽内容不会选中的问题
+8. 修复 uv-calendars 在pages.json中设置easycom会报错的BUG
+9. 修复 uv-index-list 设置customNavHeight导致定位不准确的BUG
+## 1.1.10(2023-08-30)
+1. 交流反馈 欢迎加入uv-ui官方群1交流反馈: 549833913
+2. 交流反馈 欢迎加入uv-ui官方群2交流反馈: 206060892
+3. 优化 uv-calendars 1. 去除range参数,由mode="range"替换;2. 新增mode参数,不传 / multiple / range,分别为单日期, 多个日期,选择日期范围;3. 与uv-calendar选择日期的功能保持一致
+4. 新增 uv-album 新增相册组件及相关文档
+5. 优化 其他优化
+6. 修复 uv-text app-nvue设置align不生效的BUG
+7. 修复 uv-drop-down 自定义内容,点击自定义内容时会自动关闭弹窗的问题
+8. 修复 uv-image 异步修改宽高不生效的问题,问题来源:https://gitee.com/climblee/uv-ui/issues/I7WUQ3
+9. 修复 uv-calendars 通过setConfig修改属性不生效的问题,出自评论区:https://ext.dcloud.net.cn/plugin?id=12287
+10. 修复 uv-list 设置边框不生效的BUG
+## 1.1.9(2023-08-27)
+1. 优化 uv-calendars 1. 去除range参数,由mode="range"替换;2. 新增mode参数,不传 / multiple / range,分别为单日期, 多个日期,选择日期范围;3. 与uv-calendar选择日期的功能保持一致
+2. 优化 uv-picker 增加round属性,设置圆角
+3. 修复 uv-calendars 点击返回今天按钮时,monthSwitch方法回调参数返回月份不是当天对应月份
+4. 修复 uv-radio 1. 设置 labelSize 属性设置无效的问题:https://gitee.com/climblee/uv-ui/issues/I7W6UN;2. v-model 绑定布尔值控制台报警:https://gitee.com/climblee/uv-ui/issues/I7W714
+5. 修复 uv-checkbox 1. 设置 label 属性为布尔值不生效的BUG
+## 1.1.8(2023-08-24)
+1. 优化 uv-popup 弹出不丝滑优化思路:https://www.uvui.cn/components/popup.html#yh
+2. 修复 uv-switch 取消value传值,只能使用v-model传值,避免异步操作不生效的BUG
+3. 修复 uv-index-list ios端滚动过程中+快速点击右侧导航页面出现空白的BUG
+4. 修复 uv-rate 1. 支付宝报错的BUG; 2. 不能选半星的BUG
+5. 修复 uv-model 异步loading时,确认回调还会一直触发的BUG
+6. 修复 uv-swiper 标题文字过多未隐藏掉的BUG
+7. 修复 uv-text app-nvue编译不能自动换行的BUG
+## 1.1.7(2023-08-22)
+1. 优化 uv-drop-down a. 增加@change回调,返回弹窗关闭状态; b. 增加init方法,方便位置改变进行调整
+2. 优化 部分文档优化
+3. 修复 uv-input a. app-nvue-ios端不能输入的BUG;b. 键盘高度等值不返回BUG
+4. 修复 uv-scroll-list 报错导致不能移动指示器的BUG
+5. 修复 uv-search 边距值在上次更新中误改导致不对的BUG
+6. 修复 uv-image 设置width和height为100%不生效的BUG
+## 1.1.6(2023-08-18)
+1. 优化 优化文档
+2. 修复 uv-list 使用列表右侧显示 switch,switchChange回调中返回数据为undefined的BUG
+3. 修复 uv-checkbox 数据多不换行的BUG
+4. 修复 uv-upload 1. 图片预览位置错误的BUG;2. 视频预览不生效的BUG;3. 改变上传视频宽高不生效的BUG
+5. 修复 uv-navbar 在部分ios高版本机型,返回按钮不好操作的问题
+6. 修复 uv-waterfall 只有一条数据的时候,切换的时候数据会左右显示错误的BUG
+## 1.1.5(2023-08-14)
+1. 优化 uv-pick-color 删除scrollTop参数,内部修改后就不需要了
+2. 优化 uv-loading-icon 增加textStyle参数,可自定义文本样式,比如给上边距
+3. 修复 uv-safe-bottom 百度小程序报错的BUG
+4. 修复 uv-form 设置labelWidth属性时,节点渲染有闪动的BUG
+5. 修复 uv-grid 设置col属性时,节点渲染有闪动的BUG
+6. 修复 uv-parse 阻止a标签跳转文档说明
+## 1.1.4(2023-08-13)
+1. 优化 nvue自定义图标 [详细文档-nvue中自定义图标库](https://www.uvui.cn/guide/customIcon.html#nvue%E4%B8%AD%E8%87%AA%E5%AE%9A%E4%B9%89%E5%9B%BE%E6%A0%87%E5%BA%93)
+2. 优化 uv.$uv.http 在APP.vue页面使用报错的BUG: [Api集中管理](https://www.uvui.cn/js/http.html#_3-api%E9%9B%86%E4%B8%AD%E7%AE%A1%E7%90%86)
+3. 修复 uv-navbar app-nvue运行ios存在背景图片错乱的问题
+4. 修复 uv-list app-nvue运行ios存在,分包页面不滚动
+5. 修复 uv-textarea 值为null或undefined时显示错误的bug
+6. 修复 uv-search 值为null或undefined时显示错误的bug
+7. 修复 uv-scroll-list vue2编译报错的BUG
+8. 修复 uv-calendars 选择月份弹窗层级的问题
+9. 修复 uv-form 动画在vue3 setup语法糖中错乱,以及表单其他相关问题解决: [Issues](https://gitee.com/my_dear_li_pan/uv-ui/issues/I7SNTT)
+10. 修复 uv-picker-color 滚动页面无法点击的BUG:增加scrollTop参数,设置滚动条的位置。不设置如果页面出现滚动就需要传该值,会出现颜色面板无法进行选颜色的情况。
+11. 交流反馈 欢迎加入uv-ui官方群1交流反馈: [549833913](https://www.uvui.cn/components/addQQGroup.html)
+12. 交流反馈 欢迎加入uv-ui官方群2交流反馈: [206060892](https://www.uvui.cn/components/addQQGroup.html)
+## 1.1.3(2023-08-06)
+1. 优化 uv-calendars 1. 增加startText参数; 2. 增加endText参数; 3. 增加selected中的参数; 4. 优化日历范围选择
+2. 优化 uv-empty icon属性支持base64图片
+3. 优化 uv-navbar 增加背景图片的裁剪模式参数imgMode
+4. 优化 uv-picker-color 颜色值不对的BUG
+5. 优化 [API文档优化](https://www.uvui.cn/components/changelog.html)
+6. 优化 常见问题增加:[怎么隐藏uv-tabs等组件的滚动条](https://www.uvui.cn/components/problem.html#%E4%B9%9D%E3%80%81%E6%80%8E%E4%B9%88%E9%9A%90%E8%97%8Fuv-tabs%E7%AD%89%E7%BB%84%E4%BB%B6%E7%9A%84%E6%BB%9A%E5%8A%A8%E6%9D%A1)
+7. 修复 uv-radio name为数字0时不能选中的BUG
+8. 修复 uv-textarea 1. v-model设置为数据时的BUG;2. 复制过多内容,计数显示错误的BUG;3. maxlength为-1改成不显示计数
+9. 修复 uv-code-input 在vue2模式下,v-model设置为0时不生效的BUG
+10. 修复 uv-input 在vue2模式下,v-model设置为0时不生效的BUG
+11. 修复 uv-search 在vue2模式下,v-model设置为0时不生效的BUG
+12. 修复 uv-ui-tools 1. 路由拦截修复;2. 增加events参数
+## 1.1.2(2023-08-03)
+1. 新增 uv-calendars 新版日历发布
+2. 新增 uv-toolbar 组件独立发布,老用户更新uv-picker,需要手动删除uv-picker目录下的uv-toolbar目录,否则会有冲突提示
+3. 优化 uv-tags 增加cellChild参数
+4. 优化 uv-navbar 兼容背景图片
+5. 优化 uv-notice-bar 竖向滚动时候增加change回调
+## 1.1.1(2023-07-30)
+1. 新增 uv-drop-down 下拉筛选组件,兼容app-nvue及多端
+2. 优化 uv-textarea 增加confirm-hold参数,方便设置进行换行处理
+3. 优化 其他关于文档的优化等
+## 1.1.0(2023-07-26)
+1. 重构 uv-list 全面重构,提高性能,放弃使用scroll-view,具体文档参考:uv-list列表
+2. 优化 uv-search 1. 增加prefix和suffix 前置和后置插槽;2. 增加boxStyle参数,方便控制输入框部分的样式
+3. 优化 文档优化:获取节点布局信息,文档新增nvue获取方式的说明
+## 1.0.22(2023-07-26)
+1. 优化 uv-textarea 组件 增加textStyle和countStyle属性,方便控制文本样式
+2. 优化 uv-swiper 增加竖向播放属性:vertical
+3. 优化 uv-icon 支持base64图片格式
+4. 优化 uv-transition 和 uv-image 增加参数cellChild属性,避免nvue中出现回收后不显示的BUG
+5. 优化 uv-button 增加customTextStyle属性,方便自定义按钮文字样式
+6. 优化 优化部分文档说明
+7. 修复 uv-slider 修改背景颜色属性为backgroundColor,避免设置不生效
+8. 修复 uv-index-list 1. 修复全局设置成rpx存在的高度BUG;2. 修复其他BUG
+## 1.0.21(2023-07-22)
+1. 新增 uv-scroll-list 横向滚动列表组件
+2. 优化 增加测试占位图,方便开发者使用线上图片进行测试:[https://www.uvui.cn/components/testPic.html](https://www.uvui.cn/components/testPic.html)
+3. 优化 uv-calendar 组件文档示例等优化,增加setFormatter说明
+4. 优化 uv-notice-bar 优化文档,说明不显示左边图标的使用方法
+5. 修复 uv-input 在微信小程序端清除内容存在不能清除的BUG
+6. 修复 uv-button 1. 解决微信小程序动态设置hover-class点击态不消失的BUG; 2. 文档优化
+7. 修复 uv-waterfall 在tab切换等场景快速切换时,会出现报错的BUG
+8. 优化 优化其他
+## 1.0.20(2023-07-18)
+1. 修复 uv-textarea 设置-1不生效
+2. 修复 uv-icon 恢复uv-empty相关的图标
+3. 修复 uv-empty 恢复设置mode属性的内置图标
+4. 优化 [优化文档](https://www.uvui.cn)
+## 1.0.19(2023-07-14)
+1. 优化 uv-waterfall 当changeList未处理数据时,正确返回对应列的数据,避免误导
+2. 修复 uv-rate VUE3模式下设置value属性不生效的BUG
+3. 修复 uv-input VUE3模式下设置value属性不生效的BUG
+4. 修复 uv-search VUE3模式下设置value属性不生效的BUG
+5. 修复 uv-code-input VUE3模式下设置value属性不生效的BUG
+6. 修复 uv-number-box VUE3模式下设置value属性不生效的BUG
+7. 修复 uv-radio VUE3模式下设置value属性不生效的BUG
+8. 修复 uv-checkbox VUE3模式下设置value属性不生效的BUG
+9. 修复 uv-textarea VUE3模式下设置value属性不生效的BUG
+10. 修复 uv-switch VUE3模式下设置value属性不生效的BUG
+11. 修复 uv-slider VUE3模式下设置value属性不生效的BUG
+12. 修复 uv-datetime-picker VUE3模式下设置value属性不生效的BUG
+13. 修复 uv-icon 部分图标错误的BUG
+## 1.0.18(2023-07-06)
+1. 优化 uv-icon 1. 更新图标,删除一些不常用的图标;2. 删除base64,修改成ttf文件引入读取图标。uv-icon 图标
+2. 优化 uv-icon nvue自定义图标用法,文档说明:[点击跳转](https://www.uvui.cn/guide/customIcon.html)
+3. 优化 uv-upload 文档示例代码,增加fileList参数说明:[点击跳转](https://www.uvui.cn/components/upload.html#filelist-options)
+4. 修复 uv-checkbox vue3模式下,动态修改v-model绑定的值无效的BUG
+5. 修复 uv-radio vue3模式下,动态修改v-model绑定的值无效的BUG
+6. 修复 uv-datetime-picker vue3模式下,动态修改v-model绑定的值无效的BUG
+## 1.0.17(2023-07-04)
+1. 优化 uv-icon 修复,NVUE平台主题颜色在APP不生效的BUG
+2. 优化 uv-notice-bar 优化,增加disableScroll属性
+3. 优化 uv-input uv-back-top uv-cell uv-form uv-search uv-modal uv-navbar uv-index-list uv-empty uv-upload 去除插槽判断,避免某些平台不显示的BUG
+4. 优化 uv-form 优化文档
+5. 优化 优化其他相关文档
+## 1.0.16(2023-07-03)
+1. 优化 uv-transition 动画组件,代码重构优化,性能更加友好,增加自定义动画功能。详情[参考文档](https://www.uvui.cn/components/transition.html)
+2. 优化 uv-popup 弹出层,代码重构优化,性能翻倍,小程序体验性能更加,避免卡顿。打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/popup.html)
+3. 优化 uv-calendar 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/actionSheet.html)
+4. 优化 uv-action-sheet 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/calendar.html)
+5. 优化 uv-datetime-picker 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/datetimePicker.html)
+6. 优化 uv-form 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+7. 优化 uv-keyboard 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/keyboard.html)
+8. 优化 uv-modal 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/modal.html)
+9. 优化 uv-notify 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/notify.html)
+10. 优化 uv-overlay 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+11. 优化 uv-pick-color 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/pickColor.html)
+12. 优化 uv-picker 由于弹出层uv-popup的修改,打开和关闭方法更改,详情[参考文档](https://www.uvui.cn/components/picker.html)
+13. 优化 uv-tooltip 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+14. 优化 uv-loading-page 由于弹出层uv-transition的修改,组件内部做了相应的修改,参数不变。
+15. 优化 相关文档的优化更改。
+16. 修复 uv-safe-bottom 修复,在百度程序,抖音小程序不生效的BUG
+## 1.0.15(2023-06-29)
+1. 欢迎加QQ群交流:[549833913](https://www.uvui.cn/components/addQQGroup.html)
+2. 优化 uv-swiper 优化:1. 增加titleStyle属性,方便修改标题样式;2. 标题上去掉是否是图片的判断,避免无后缀的图片不显示
+3. 优化 uv-steps 优化:1. 增加插槽title; 3. 文档关于插槽相关的参数说明完善;增加customStyle属性
+4. 优化 uv-checkbox 优化:增加label文字插槽,与radio保持一致,优化文档相关说明
+5. 优化 uv-modal 优化:增加closeLoading方法,方便异步加载手动取消加载状态,更新文档
+6. 优化 uv-image 增加文档说明:uv-list、 uv-waterfall等组件在 Android平台使用了list封装,所以在该组件中仍然不能使用uv-image等组件
+7. 优化 优化更多文档
+8. 修复 uv-vtabs 修复非联动情况下,内容过多的情况,滚动一段距离,再切换未滚动到顶部的BUG
+9. 修复 uv-image 修复:duration属性不生效的BUG
+10. 修复 uv-code-input 修复:使用:disabledKeyboard="true"属性,事件全部失效的BUG
+11. 修复 uv-button 修复:设置open-type="chooseAvatar"等值不生效的BUG
+## 1.0.14(2023-06-25)
+1. 欢迎加QQ群交流:[549833913](https://www.uvui.cn/components/addQQGroup.html)
+2. 优化 uv-count-down 增加外部样式customStyle参数
+3. 优化 文档的全面优化
+4. 修复 uv-count-to 1. 修复继续滚动的函数 2. 修改文档错误 4. 适配px和rpx的单位 4. 适配customStyle参数
+5. 修复 uv-load-more 修复customStyle参数设置背景等不生效的BUG
+6. 修复 uv-code-input 优化下边框
+7. 修复 uv-tabs 添加uv-icon依赖
+8. 修复 uv-grid 优化修改
+9. 修复 uv-cell 优化修改
+## 1.0.13(2023-06-20)
+1. 优化 uv-calendar formatter格式化中增加topInfo参数
+2. 优化 uv-tabs 增加customStyle参数
+3. 优化 文档优化,便于开发者直接开干
+4. 优化 uv-switch 优化size属性,适配单位传递
+5. 修复 uv-ui-tools、uv-form、uv-picker 修复vue3编译支付宝异常
+6. 修复 uv-ui-tools、uv-form、uv-picker 修复vue3编译支付宝异常
+7. 修复 uv-parse 修复在nvue不显示的BUG
+8. 修复 uv-form 修复某些条件下报错的BUG
+## 1.0.12(2023-06-14)
+1. 优化部分组件,优化文档部分细节
+2. uv-popup、uv-modal 修复遮罩层zIndex问题
+3. uv-form 在vue3的setup语法中ref使用uvForm会导致报错
+4. uv-tabs activeStyle设置字体大小,可能会导致下划线位置不对BUG
+5. uv-pick-color 百度小程序点击报错
+6. uv-transition 恢复this.$nextTick
+7. uv-picker 抖音小程序选择的时候报错,导致不能关闭的BUG
+8. uv-checkbox 多余的属性labelDisabled,导致APP中报错提示
+9. uv-tabbar 底部安全距离组件无效的BUG
+10. uv-vtabs 头部存在的时候,联动不准确的BUG
+## 1.0.11(2023-06-12)
+1. uv-radio-group、uv-checkbox-group 兼容自定义样式customStyle,方便通过样式调整整体位置等,数据较多时允许换行
+2. uv-ui-tools 优化内置样式等,解决微信小程序使用uvui提示 Some selectors are not allowed in component wxss, including tag name selectors, ID selectors, and attribute selectors,[详情](https://www.uvui.cn/components/problem.html)
+3. uv-datetime-picker 取消defaultIndex参数,目前传该值也没实际意义
+4. uv-tabbar 增加iconSize参数
+5. uv-calendar 增加change回调
+6. uv-calendar 修复BUG
+7. uv-rate 修复只读或禁止状态下设置value无效的问题
+8. uv-popup 修复zIndex问题
+9. uv-modal 修复zIndex问题
+10. 文档-扩展配置更新:[扩展配置](https://www.uvui.cn/components/setting.html)
+11. 文档-优化更新:[uv-ui文档](https://www.uvui.cn/components/changelog.html)
+12. 文档-新增常见问题:[常见问题](https://www.uvui.cn/components/problem.html)
+13. 优化其他
+## 1.0.10(2023-06-05)
+1. uv-navbar 渐变背景色兼容
+2. uv-calendar 日历选择BUG修复
+## 1.0.9(2023-06-05)
+1. 新增uv-vtabs垂直选项卡组件,主要用于分类展示,分类切换功能,支持联动和不联动两种模式
+2. uv-qrcode,uv-datetime-picker,uv-subsection等文档说明优化,避免开发困难;优化API相关说明
+3. uv-notice-bar 1. 修复在触发error函数报错的BUG;2. 修复在text值为undefined的时候,解决报错BUG
+4. uv-button 等组件修复触发两次事件的BUG
+5. uv-datetime-picker 1. 修复重置值存在不更新的BUG;2. 优化文档,增加filter使用方法说明
+6. uv-badge 修复type等属性为null或undefined的时候不显示徽标的BUG
+7. uv-ui-tools 优化工具组件,兼容更多功能,小程序分享功能优化等
+...
+## 1.0.8(2023-05-27)
+1. uv-waterfall修复在百度小程序中可能存在的BUG;去掉原有的slot方式
+2. uv-image修复可能报错的问题
+3. uv-pick-color 在文档预览模式中无法点击的问题
+4. uv-index-list 修复select事件不触发的问题
+5. 优化其他组件及示例项目等
+## 1.0.7(2023-05-25)
+1. uv-icon 将线上ttf字体包替换成base64,避免加载时或者网络差时候显示白色方块
+2. uv-text 去掉多余的data-index属性,避免警告
+3. uv-upload 在fileList的watch中增加deep属性
+4. uv-pick-color 去掉template中存在的this.导致头条小程序编译警告
+5. uv-image 去掉template中存在的this.导致头条小程序编译警告
+## 1.0.6(2023-05-23)
+1. 新增uv-pick-color颜色选择器组件
+2. uv-toolbar组件增加showBorder属性,是否显示下边框
+3. uv-transition组件在百度小程序等平台不支持this.$nextick导致下面的逻辑不执行,使用延时替换方案
+4. uv-ui-tools组件中bem()函数兼容百度/头条小程序等
+5. uv-waterfall组件修复在百度/头条小程序显示异常等BUG,增加changeList回调函数处理数据,同步更新示例等
+6. uv-image组件修复在百度/头条小程序等开启observeLazyLoad后显示异常BUG
+7. uv-tabs组件修复上次更新导致的在nvue中不滚动的BUG
+8. uv-qrcode组件修复在部分平台不显示加载的BUG
+9. 修复其他已知问题等
+## 1.0.5(2023-05-17)
+1. 新增uv-qrcode二维码组件
+2. 修复uv-tooltip在vue2模式下的BUG
+3. 优化部分问题
+## 1.0.4(2023-05-16)
+1. 优化组件依赖,修改后无需全局引入,组件导入即可使用
+2. 优化部分功能
+## 1.0.3(2023-05-12)
+1. 修复uv-input在vue3模式下双向绑定问题
+2. 修复uv-textarea在vue3模式下双向绑定问题
+3. 修复uv-rate在vue3模式下双向绑定问题
+## 1.0.2(2023-05-11)
+1. 更新文档
+2. 增加插件下载入口
+## 1.0.1(2023-05-10)
+1. 所有组件依赖
+2. 上传示例项目
+## 1.0.0(2023-05-10)
+1. uv-ui
diff --git a/uni_modules/uv-ui/components/uv-ui/uv-ui.vue b/uni_modules/uv-ui/components/uv-ui/uv-ui.vue
new file mode 100644
index 0000000..e023095
--- /dev/null
+++ b/uni_modules/uv-ui/components/uv-ui/uv-ui.vue
@@ -0,0 +1,7 @@
+
+ 占位组件,请勿使用;如需下载示例项目,请使用【下载插件并导入HBuilderX】或【使用 HBuilderX 导入示例项目】或【下载示例项目ZIP】
+
+
+
diff --git a/uni_modules/uv-ui/package.json b/uni_modules/uv-ui/package.json
new file mode 100644
index 0000000..5e919a2
--- /dev/null
+++ b/uni_modules/uv-ui/package.json
@@ -0,0 +1,162 @@
+{
+ "id": "uv-ui",
+ "displayName": "uv-ui 破釜沉舟之兼容vue3+2、app、h5、小程序等多端,灵活导入,利剑出击",
+ "version": "1.1.20",
+ "description": "uv-ui 是基于uni-app、部分组件基于uView2.x、全端兼容、支持独立导入、内容丰富的UI框架。破釜沉舟之兼容vue3+2、app、h5、小程序等多端,利剑出击,开箱即用。",
+ "keywords": [
+ "uv-ui",
+ "uvui",
+ "UI组件库",
+ "ui框架",
+ "ui库"
+ ],
+ "repository": "https://github.com/climblee/uv-ui",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "无",
+ "permissions": "无"
+ },
+ "npmurl": "https://www.npmjs.com/package/@climblee/uv-ui"
+ },
+ "uni_modules": {
+ "dependencies": [
+ "uv-skeletons",
+ "uv-album",
+ "uv-drop-down",
+ "uv-calendars",
+ "uv-scroll-list",
+ "uv-vtabs",
+ "uv-pick-color",
+ "uv-qrcode",
+ "uv-ui-tools",
+ "uv-action-sheet",
+ "uv-alert",
+ "uv-avatar",
+ "uv-back-top",
+ "uv-badge",
+ "uv-button",
+ "uv-calendar",
+ "uv-cell",
+ "uv-checkbox",
+ "uv-code",
+ "uv-code-input",
+ "uv-collapse",
+ "uv-count-down",
+ "uv-count-to",
+ "uv-datetime-picker",
+ "uv-divider",
+ "uv-empty",
+ "uv-form",
+ "uv-gap",
+ "uv-grid",
+ "uv-icon",
+ "uv-image",
+ "uv-index-list",
+ "uv-input",
+ "uv-keyboard",
+ "uv-line",
+ "uv-line-progress",
+ "uv-link",
+ "uv-list",
+ "uv-loading-icon",
+ "uv-loading-page",
+ "uv-load-more",
+ "uv-modal",
+ "uv-navbar",
+ "uv-no-network",
+ "uv-notice-bar",
+ "uv-notify",
+ "uv-number-box",
+ "uv-overlay",
+ "uv-parse",
+ "uv-picker",
+ "uv-popup",
+ "uv-radio",
+ "uv-rate",
+ "uv-read-more",
+ "uv-row",
+ "uv-safe-bottom",
+ "uv-search",
+ "uv-skeleton",
+ "uv-slider",
+ "uv-status-bar",
+ "uv-steps",
+ "uv-sticky",
+ "uv-subsection",
+ "uv-swipe-action",
+ "uv-swiper",
+ "uv-switch",
+ "uv-tabbar",
+ "uv-tabs",
+ "uv-tags",
+ "uv-text",
+ "uv-textarea",
+ "uv-toast",
+ "uv-tooltip",
+ "uv-transition",
+ "uv-upload",
+ "uv-waterfall"
+ ],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "y",
+ "app-nvue": "y"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "y",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "y",
+ "百度": "y",
+ "字节跳动": "y",
+ "QQ": "y",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/uv-ui/readme.md b/uni_modules/uv-ui/readme.md
new file mode 100644
index 0000000..9dfe1df
--- /dev/null
+++ b/uni_modules/uv-ui/readme.md
@@ -0,0 +1,164 @@
+
+
+
+uv-ui
+兼容vue3+2多平台快速开发的UI框架
+
+[](https://gitee.com/climblee/uv-ui)
+[](https://gitee.com/climblee/uv-ui)
+[](https://github.com/climblee/uv-ui)
+[](https://github.com/climblee/uv-ui/issues)
+[](https://www.uvui.cn)
+[](https://www.uvui.cn/components/changelog.html)
+[](https://en.wikipedia.org/wiki/MIT_License)
+
+## 温馨提示:如需下载uv-ui示例项目,请不要使用【下载插件ZIP】按钮。
+
+### uvui官方群1:549833913
+### uvui官方群2:206060892
+
+## uvui特点
+
+1. **uv-ui的前世今生**,`uv-ui` 是基于 `uview2.x` 版本改造而来。重命名也是为了避开发布冲突和很多组件 `u-`在 `nvue` 中不能使用的情况,所以这才诞生了`uv-ui`。感谢 `uview-ui` 作者的开源奉献,再次为开源点赞。 同时 `uv-ui` 也是无条件开源。
+
+2. **全端兼容**,`uv-ui`支持vue3、vue2、app-vue、app-nvue、h5、小程序等。`uv-ui`的组件都是多端自适应的,底层会抹平很多小程序平台的差异或bug。
+
+3. **扩展配置**,`uv-ui`内置的方法默认不再挂载到`uni`对象之上,也就意味着默认情况下不能在项目中直接使用`uni.$uv.xxx`使用内置方法。但是可以通过扩展可以解决,通过如下方式进行配置即可,使用方式请参考[扩展配置](https://www.uvui.cn/components/setting.html)。其中包括[ JS工具库](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-js%E5%B7%A5%E5%85%B7%E5%BA%93)、[ 自定义主题](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E8%87%AA%E5%AE%9A%E4%B9%89%E4%B8%BB%E9%A2%98)、[ 基础样式](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-%E5%9F%BA%E7%A1%80%E6%A0%B7%E5%BC%8F)、[ setconfig](https://www.uvui.cn/components/setting.html#%E6%89%A9%E5%B1%95%E9%85%8D%E7%BD%AE-setconfig)等。
+
+## 预览
+
+通过微信(APP下载不支持微信扫码)或浏览器扫码查看演示效果。
+
+
+
+## 链接
+
+- [官方文档](https://www.uvui.cn)
+- [演示地址](https://h5.uvui.cn)
+- [更新日志](https://www.uvui.cn/components/changelog.html)
+- [关于我们](https://www.uvui.cn/cooperation/about.html)
+- 组件列表
+
+## 交流反馈
+
+欢迎加入我们的QQ群交流反馈:[点此跳转](https://www.uvui.cn/components/addQQGroup.html)
+
+## 快速开始
+
+方式一:`uv-ui` 强烈建议通过 `下载插件并导入HbuilderX` 导入组件。
+
+方式二:下载完整 [uv-ui项目](https://ext.dcloud.net.cn/plugin?id=12287) 将 `uni_modules` 复制到自己的项目。
+
+方式三:通过 `npm i @climblee/uv-ui` 下载,此方法需要配置 easycom,配置详情可查看[安装](https://www.uvui.cn/components/install.html)。
+
+请通过[快速上手](https://www.uvui.cn/components/quickstart.html)了解更详细的内容。
+
+**注意:导入插件后,建议`HBuilderX`重新运行项目,可能新导入的插件不能实时更新而导致不能运行。**
+
+## 使用方法
+
+组件导入 `uni_modules` 后,直接在项目中使用,无需通过import引入组件。
+
+```html
+
+
+
+```
+
+## 扩展功能
+
+`uv-ui` 内置了强大的工具函数、请求封装等,可以根据自身需求进行扩展配置,详情请查看[扩展配置](https://www.uvui.cn/components/setting.html)。
+
+**注意:只有[扩展配置](https://www.uvui.cn/components/setting.html)后才能在自己的项目页面中使用组件库内置方法和变量等**。
+
+
+
+## 组件列表
+
+下表为 `uv-ui` 的扩展组件清单,点击每个组件**点击下载&安装**即可在详情页面导入组件到项目下,导入后建议重新运行即可直接使用,组件无需import和注册。
+
+| 组件名 | 组件说明 |
+| --- | --- |
+| uv-skeletons | [新版骨架屏(推荐)](https://www.uvui.cn/components/skeletons.html) |
+| uv-calendars | [新版日历(推荐)](https://www.uvui.cn/components/calendars.html) |
+| uv-drop-down | [下拉筛选](https://www.uvui.cn/components/dropDown.html) |
+| uv-scroll-list | [横向滚动列表](https://www.uvui.cn/components/scrollList.html) |
+| uv-vtabs | [垂直选项卡](https://www.uvui.cn/components/vtabs.html) |
+| uv-pick-color | [颜色选择器](https://www.uvui.cn/components/pickColor.html) |
+| uv-qrcode | [二维码](https://www.uvui.cn/components/qrcode.html) |
+| uv-waterfall | [瀑布流](https://www.uvui.cn/components/waterfall.html) |
+| uv-row | [Layout 布局](https://www.uvui.cn/components/layout.html) |
+| uv-icon | [图标](https://www.uvui.cn/components/icon.html) |
+| uv-button | [按钮](https://www.uvui.cn/components/button.html) |
+| uv-text | [文本](https://www.uvui.cn/components/text.html) |
+| uv-link | [超链接](https://www.uvui.cn/components/link.html) |
+| uv-image | [图片](https://www.uvui.cn/components/image.html) |
+| uv-transition | [动画](https://www.uvui.cn/components/transition.html) |
+| uv-form | [表单](https://www.uvui.cn/components/form.html) |
+| uv-input | [增强输入框](https://www.uvui.cn/components/input.html) |
+| uv-textarea | [增强文本域](https://www.uvui.cn/components/textarea.html) |
+| uv-checkbox | [复选框](https://www.uvui.cn/components/checkbox.html) |
+| uv-radio | [单选框](https://www.uvui.cn/components/radio.html) |
+| uv-switch | [开关选择器](https://www.uvui.cn/components/switch.html) |
+| uv-calendar | [日历](https://www.uvui.cn/components/calendar.html) |
+| uv-picker | [选择器](https://www.uvui.cn/components/picker.html) |
+| uv-datetime-picker | [时间选择器](https://www.uvui.cn/components/datetimePicker.html) |
+| uv-code | [验证码倒计时](https://www.uvui.cn/components/code.html) |
+| uv-keyboard | [键盘](https://www.uvui.cn/components/keyboard.html) |
+| uv-rate | [评分](https://www.uvui.cn/components/rate.html) |
+| uv-search | [多功能搜索框](https://www.uvui.cn/components/search.html) |
+| uv-number-box | [步进器](https://www.uvui.cn/components/numberBox.html) |
+| uv-upload | [上传](https://www.uvui.cn/components/upload.html) |
+| uv-slider | [滑动选择器](https://www.uvui.cn/components/slider.html) |
+| uv-list | [列表](https://www.uvui.cn/components/list.html) |
+| uv-index-list | [索引列表](https://www.uvui.cn/components/indexList.html) |
+| uv-tags | [标签](https://www.uvui.cn/components/tag.html) |
+| uv-line-progress | [线形进度条](https://www.uvui.cn/components/lineProgress.html) |
+| uv-badge | [徽标数](https://www.uvui.cn/components/badge.html) |
+| uv-count-down | [倒计时](https://www.uvui.cn/components/countDown.html) |
+| uv-count-to | [数字滚动](https://www.uvui.cn/components/countTo.html) |
+| uv-avatar | [头像](https://www.uvui.cn/components/avatar.html) |
+| uv-skeleton | [骨架屏](https://www.uvui.cn/components/skeleton.html) |
+| uv-loading-icon | [加载动画](https://www.uvui.cn/components/loadingIcon.html) |
+| uv-loading-page | [加载页](https://www.uvui.cn/components/loadingPage.html) |
+| uv-load-more | [加载更多](https://www.uvui.cn/components/loadMore.html) |
+| uv-empty | [内容为空](https://www.uvui.cn/components/empty.html) |
+| uv-tooltip | [长按提示](https://www.uvui.cn/components/tooltip.html) |
+| uv-alert | [警告提示](https://www.uvui.cn/components/alert.html) |
+| uv-toast | [消息提示](https://www.uvui.cn/components/toast.html) |
+| uv-notice-bar | [滚动通知](https://www.uvui.cn/components/noticeBar.html) |
+| uv-notify | [消息提示](https://www.uvui.cn/components/notify.html) |
+| uv-no-network | [无网络提示](https://www.uvui.cn/components/noNetwork.html) |
+| uv-popup | [弹出层](https://www.uvui.cn/components/popup.html) |
+| uv-modal | [模态框](https://www.uvui.cn/components/modal.html) |
+| uv-cell | [单元格](https://www.uvui.cn/components/cell.html) |
+| uv-swipe-action | [滑动单元格](https://www.uvui.cn/components/swipeAction.html) |
+| uv-swiper | [轮播图](https://www.uvui.cn/components/swiper.html) |
+| uv-collapse | [折叠面板](https://www.uvui.cn/components/collapse.html) |
+| uv-grid | [宫格布局](https://www.uvui.cn/components/grid.html) |
+| uv-album | [相册](https://www.uvui.cn/components/album.html) |
+| uv-tabbar | [底部导航栏](https://www.uvui.cn/components/tabbar.html) |
+| uv-back-top | [返回顶部](https://www.uvui.cn/components/backTop.html) |
+| uv-navbar | [自定义导航栏](https://www.uvui.cn/components/navbar.html) |
+| uv-action-sheet | [底部操作菜单](https://www.uvui.cn/components/actionSheet.html) |
+| uv-tabs | [标签选项卡](https://www.uvui.cn/components/tabs.html) |
+| uv-steps | [步骤条](https://www.uvui.cn/components/steps.html) |
+| uv-subsection | [分段器](https://www.uvui.cn/components/subsection.html) |
+| uv-sticky | [吸顶](https://www.uvui.cn/components/sticky.html) |
+| uv-parse | [富文本解析器](https://www.uvui.cn/components/parse.html) |
+| uv-overlay | [遮罩层](https://www.uvui.cn/components/overlay.html) |
+| uv-code-input | [验证码输入](https://www.uvui.cn/components/codeInput.html) |
+| uv-read-more | [展开阅读更多](https://www.uvui.cn/components/readMore.html) |
+| uv-line | [线条](https://www.uvui.cn/components/line.html) |
+| uv-gap | [间隔槽](https://www.uvui.cn/components/gap.html) |
+| uv-divider | [分割线](https://www.uvui.cn/components/divider.html) |
+
+## 版权信息
+uv-ui遵循[MIT](https://en.wikipedia.org/wiki/MIT_License)开源协议,意味着您无需支付任何费用,也无需授权,即可将uv-ui应用到您的产品中。
+
+## 作者想说
+- 开源真的不易,不图大家的钱财,所以希望大家多多鼓励支持,希望不要恶意评论,有问题加群快速解决。
+- 遇到BUG,是一件很正常的事情,是程序肯定就有BUG,所以希望大家能以理解的心态去提出BUG,然后作者才有动力去努力修复。
+- 最后觉得好用的小伙伴,不要吝啬你的双手,给个好评就是给我们最大的鼓励。
+
+# 恶评者手下留情,有事加QQ群解决:549833913
\ No newline at end of file
diff --git a/uni_modules/zero-markdown-view/changelog.md b/uni_modules/zero-markdown-view/changelog.md
new file mode 100644
index 0000000..d13259c
--- /dev/null
+++ b/uni_modules/zero-markdown-view/changelog.md
@@ -0,0 +1,15 @@
+## 2.0.5(2024-04-24)
+## 流式输出代码块解决方案
+## 2.0.4(2023-12-06)
+### 长按复制代码改为点击代码块复制
+## 2.0.3(2023-10-30)
+doc: 文档说明
+## 2.0.2(2023-10-30)
+- 新增长按复制代码-仅小程序可用
+- 新增代码块语言显示
+## 2.0.1(2023-10-27)
+##支持vue2,vue3
+## 2.0.0(2022-11-01)
+使用mp-html自带的插件,重新生成uniapp包,大幅减少插件体积
+## 1.0.0(2022-09-13)
+首次发布
diff --git a/uni_modules/zero-markdown-view/components/mp-html/highlight/config.js b/uni_modules/zero-markdown-view/components/mp-html/highlight/config.js
new file mode 100644
index 0000000..2f81762
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/highlight/config.js
@@ -0,0 +1,5 @@
+export default {
+ copyByClickCode: true, // 点击代码块复制
+ showLanguageName: true, // 是否在代码块右上角显示语言的名称
+ showLineNumber: false // 是否显示行号
+}
diff --git a/uni_modules/zero-markdown-view/components/mp-html/highlight/index.js b/uni_modules/zero-markdown-view/components/mp-html/highlight/index.js
new file mode 100644
index 0000000..7faa633
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/highlight/index.js
@@ -0,0 +1,109 @@
+/**
+ * @fileoverview highlight 插件
+ * Include prismjs (https://prismjs.com)
+ */
+import prism from './prism.min'
+import config from './config'
+import Parser from '../parser'
+
+function Highlight (vm) {
+ this.vm = vm
+}
+
+Highlight.prototype.onParse = function (node, vm) {
+ if (node.name === 'pre') {
+ if (vm.options.editable) {
+ node.attrs.class = (node.attrs.class || '') + ' hl-pre'
+ return
+ }
+ let i
+ for (i = node.children.length; i--;) {
+ if (node.children[i].name === 'code') break
+ }
+ if (i === -1) return
+ const code = node.children[i]
+ let className = code.attrs.class + ' ' + node.attrs.class
+ i = className.indexOf('language-')
+ if (i === -1) {
+ i = className.indexOf('lang-')
+ if (i === -1) {
+ className = 'language-text'
+ i = 9
+ } else {
+ i += 5
+ }
+ } else {
+ i += 9
+ }
+ let j
+ for (j = i; j < className.length; j++) {
+ if (className[j] === ' ') break
+ }
+ const lang = className.substring(i, j)
+ if (code.children.length) {
+ const text = this.vm.getText(code.children).replace(/&/g, '&')
+ if (!text) return
+ if (node.c) {
+ node.c = undefined
+ }
+ if (prism.languages[lang]) {
+ code.children = (new Parser(this.vm).parse(
+ // 加一层 pre 保留空白符
+ '' + prism.highlight(text, prism.languages[lang], lang).replace(/token /g, 'hl-') + '
'))[0].children
+ }
+ node.attrs.class = 'hl-pre'
+ code.attrs.class = 'hl-code'
+ code.attrs.style ='display:block;overflow: auto;'
+ if (config.showLanguageName) {
+ node.children.push({
+ name: 'div',
+ attrs: {
+ class: 'hl-language',
+ style: 'user-select:none;position:absolute;top:0;right:2px;font-size:10px;'
+ },
+ children: [{
+ type: 'text',
+ text: lang
+ }]
+ })
+ }
+ if (config.copyByClickCode) {
+ node.attrs.style += (node.attrs.style || '') + ';user-select:none;'
+ node.attrs['data-content'] = text
+ node.children.push({
+ name: 'div',
+ attrs: {
+ class: 'hl-copy',
+ style: 'user-select:none;position:absolute;top:0;right:3px;font-size:10px;'
+ },
+ // children: [{
+ // type: 'text',
+ // text: '复制'
+ // }]
+ })
+ vm.expose()
+ // console.log('vm',node,vm)
+ }
+ if (config.showLineNumber) {
+ const line = text.split('\n').length; const children = []
+ for (let k = line; k--;) {
+ children.push({
+ name: 'span',
+ attrs: {
+ class: 'span'
+ }
+ })
+ }
+ node.children.push({
+ name: 'span',
+ attrs: {
+ class: 'line-numbers-rows'
+ },
+ children
+ })
+ }
+ }
+ }
+}
+
+export default Highlight
diff --git a/uni_modules/zero-markdown-view/components/mp-html/highlight/prism.min.js b/uni_modules/zero-markdown-view/components/mp-html/highlight/prism.min.js
new file mode 100644
index 0000000..0b67d39
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/highlight/prism.min.js
@@ -0,0 +1,7 @@
+/*! PrismJS 1.22.0
+https://prismjs.com/download.html#themes=prism-tomorrow&languages=markup+css+clike+javascript */
+var _self="undefined"!=typeof window?window:"undefined"!=typeof WorkerGlobalScope&&self instanceof WorkerGlobalScope?self:{},Prism=function(u){var c=/\blang(?:uage)?-([\w-]+)\b/i,n=0,M={manual:u.Prism&&u.Prism.manual,disableWorkerMessageHandler:u.Prism&&u.Prism.disableWorkerMessageHandler,util:{encode:function e(n){return n instanceof W?new W(n.type,e(n.content),n.alias):Array.isArray(n)?n.map(e):n.replace(/&/g,"&").replace(/=l.reach);k+=y.value.length,y=y.next){var b=y.value;if(t.length>n.length)return;if(!(b instanceof W)){var x=1;if(h&&y!=t.tail.prev){m.lastIndex=k;var w=m.exec(n);if(!w)break;var A=w.index+(f&&w[1]?w[1].length:0),P=w.index+w[0].length,S=k;for(S+=y.value.length;S<=A;)y=y.next,S+=y.value.length;if(S-=y.value.length,k=S,y.value instanceof W)continue;for(var E=y;E!==t.tail&&(Sl.reach&&(l.reach=j);var C=y.prev;L&&(C=I(t,C,L),k+=L.length),z(t,C,x);var _=new W(o,g?M.tokenize(O,g):O,v,O);y=I(t,C,_),N&&I(t,y,N),1"+a.content+""+a.tag+">"},!u.document)return u.addEventListener&&(M.disableWorkerMessageHandler||u.addEventListener("message",function(e){var n=JSON.parse(e.data),t=n.language,r=n.code,a=n.immediateClose;u.postMessage(M.highlight(r,M.languages[t],t)),a&&u.close()},!1)),M;var e=M.util.currentScript();function t(){M.manual||M.highlightAll()}if(e&&(M.filename=e.src,e.hasAttribute("data-manual")&&(M.manual=!0)),!M.manual){var r=document.readyState;"loading"===r||"interactive"===r&&e&&e.defer?document.addEventListener("DOMContentLoaded",t):window.requestAnimationFrame?window.requestAnimationFrame(t):window.setTimeout(t,16)}return M}(_self);export default Prism;"undefined"!=typeof global&&(global.Prism=Prism);
+Prism.languages.markup={comment://,prolog:/<\?[\s\S]+?\?>/,doctype:{pattern:/"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i,greedy:!0,inside:{"internal-subset":{pattern:/(\[)[\s\S]+(?=\]>$)/,lookbehind:!0,greedy:!0,inside:null},string:{pattern:/"[^"]*"|'[^']*'/,greedy:!0},punctuation:/^$|[[\]]/,"doctype-tag":/^DOCTYPE/,name:/[^\s<>'"]+/}},cdata://i,tag:{pattern:/<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/,greedy:!0,inside:{tag:{pattern:/^<\/?[^\s>\/]+/,inside:{punctuation:/^<\/?/,namespace:/^[^\s>\/:]+:/}},"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},punctuation:/\/?>/,"attr-name":{pattern:/[^\s>\/]+/,inside:{namespace:/^[^\s>\/:]+:/}}}},entity:[{pattern:/&[\da-z]{1,8};/i,alias:"named-entity"},/?[\da-f]{1,8};/i]},Prism.languages.markup.tag.inside["attr-value"].inside.entity=Prism.languages.markup.entity,Prism.languages.markup.doctype.inside["internal-subset"].inside=Prism.languages.markup,Prism.hooks.add("wrap",function(a){"entity"===a.type&&(a.attributes.title=a.content.replace(/&/,"&"))}),Object.defineProperty(Prism.languages.markup.tag,"addInlined",{value:function(a,e){var s={};s["language-"+e]={pattern:/(^$)/i,lookbehind:!0,inside:Prism.languages[e]},s.cdata=/^$/i;var n={"included-cdata":{pattern://i,inside:s}};n["language-"+e]={pattern:/[\s\S]+/,inside:Prism.languages[e]};var t={};t[a]={pattern:RegExp("(<__[^]*?>)(?:))*\\]\\]>|(?!)".replace(/__/g,function(){return a}),"i"),lookbehind:!0,greedy:!0,inside:n},Prism.languages.insertBefore("markup","cdata",t)}}),Prism.languages.html=Prism.languages.markup,Prism.languages.mathml=Prism.languages.markup,Prism.languages.svg=Prism.languages.markup,Prism.languages.xml=Prism.languages.extend("markup",{}),Prism.languages.ssml=Prism.languages.xml,Prism.languages.atom=Prism.languages.xml,Prism.languages.rss=Prism.languages.xml;
+!function(e){var t=/("|')(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/;e.languages.css={comment:/\/\*[\s\S]*?\*\//,atrule:{pattern:/@[\w-]+[\s\S]*?(?:;|(?=\s*\{))/,inside:{rule:/^@[\w-]+/,"selector-function-argument":{pattern:/(\bselector\s*\((?!\s*\))\s*)(?:[^()]|\((?:[^()]|\([^()]*\))*\))+?(?=\s*\))/,lookbehind:!0,alias:"selector"},keyword:{pattern:/(^|[^\w-])(?:and|not|only|or)(?![\w-])/,lookbehind:!0}}},url:{pattern:RegExp("\\burl\\((?:"+t.source+"|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)","i"),greedy:!0,inside:{function:/^url/i,punctuation:/^\(|\)$/,string:{pattern:RegExp("^"+t.source+"$"),alias:"url"}}},selector:RegExp("[^{}\\s](?:[^{};\"']|"+t.source+")*?(?=\\s*\\{)"),string:{pattern:t,greedy:!0},property:/[-_a-z\xA0-\uFFFF][-\w\xA0-\uFFFF]*(?=\s*:)/i,important:/!important\b/i,function:/[-a-z0-9]+(?=\()/i,punctuation:/[(){};:,]/},e.languages.css.atrule.inside.rest=e.languages.css;var s=e.languages.markup;s&&(s.tag.addInlined("style","css"),e.languages.insertBefore("inside","attr-value",{"style-attr":{pattern:/(^|["'\s])style\s*=\s*(?:"[^"]*"|'[^']*')/i,lookbehind:!0,inside:{"attr-value":{pattern:/=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/,inside:{style:{pattern:/(["'])[\s\S]+(?=["']$)/,lookbehind:!0,alias:"language-css",inside:e.languages.css},punctuation:[{pattern:/^=/,alias:"attr-equals"},/"|'/]}},"attr-name":/^style/i}}},s.tag))}(Prism);
+Prism.languages.clike={comment:[{pattern:/(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/,lookbehind:!0},{pattern:/(^|[^\\:])\/\/.*/,lookbehind:!0,greedy:!0}],string:{pattern:/(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/,greedy:!0},"class-name":{pattern:/(\b(?:class|interface|extends|implements|trait|instanceof|new)\s+|\bcatch\s+\()[\w.\\]+/i,lookbehind:!0,inside:{punctuation:/[.\\]/}},keyword:/\b(?:if|else|while|do|for|return|in|instanceof|function|new|try|throw|catch|finally|null|break|continue)\b/,boolean:/\b(?:true|false)\b/,function:/\w+(?=\()/,number:/\b0x[\da-f]+\b|(?:\b\d+\.?\d*|\B\.\d+)(?:e[+-]?\d+)?/i,operator:/[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/,punctuation:/[{}[\];(),.:]/};
+Prism.languages.javascript=Prism.languages.extend("clike",{"class-name":[Prism.languages.clike["class-name"],{pattern:/(^|[^$\w\xA0-\uFFFF])[_$A-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\.(?:prototype|constructor))/,lookbehind:!0}],keyword:[{pattern:/((?:^|})\s*)(?:catch|finally)\b/,lookbehind:!0},{pattern:/(^|[^.]|\.\.\.\s*)\b(?:as|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|for|from|function|(?:get|set)(?=\s*[\[$\w\xA0-\uFFFF])|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/,lookbehind:!0}],number:/\b(?:(?:0[xX](?:[\dA-Fa-f](?:_[\dA-Fa-f])?)+|0[bB](?:[01](?:_[01])?)+|0[oO](?:[0-7](?:_[0-7])?)+)n?|(?:\d(?:_\d)?)+n|NaN|Infinity)\b|(?:\b(?:\d(?:_\d)?)+\.?(?:\d(?:_\d)?)*|\B\.(?:\d(?:_\d)?)+)(?:[Ee][+-]?(?:\d(?:_\d)?)+)?/,function:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/,operator:/--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/}),Prism.languages.javascript["class-name"][0].pattern=/(\b(?:class|interface|extends|implements|instanceof|new)\s+)[\w.\\]+/,Prism.languages.insertBefore("javascript","keyword",{regex:{pattern:/((?:^|[^$\w\xA0-\uFFFF."'\])\s]|\b(?:return|yield))\s*)\/(?:\[(?:[^\]\\\r\n]|\\.)*]|\\.|[^/\\\[\r\n])+\/[gimyus]{0,6}(?=(?:\s|\/\*(?:[^*]|\*(?!\/))*\*\/)*(?:$|[\r\n,.;:})\]]|\/\/))/,lookbehind:!0,greedy:!0,inside:{"regex-source":{pattern:/^(\/)[\s\S]+(?=\/[a-z]*$)/,lookbehind:!0,alias:"language-regex",inside:Prism.languages.regex},"regex-flags":/[a-z]+$/,"regex-delimiter":/^\/|\/$/}},"function-variable":{pattern:/#?[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|[_$a-zA-Z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)\s*=>))/,alias:"function"},parameter:[{pattern:/(function(?:\s+[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*)?\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\))/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/[_$a-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*(?=\s*=>)/i,inside:Prism.languages.javascript},{pattern:/(\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*=>)/,lookbehind:!0,inside:Prism.languages.javascript},{pattern:/((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:[_$A-Za-z\xA0-\uFFFF][$\w\xA0-\uFFFF]*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()]|\([^()]*\))+?(?=\s*\)\s*\{)/,lookbehind:!0,inside:Prism.languages.javascript}],constant:/\b[A-Z](?:[A-Z_]|\dx?)*\b/}),Prism.languages.insertBefore("javascript","string",{"template-string":{pattern:/`(?:\\[\s\S]|\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}|(?!\${)[^\\`])*`/,greedy:!0,inside:{"template-punctuation":{pattern:/^`|`$/,alias:"string"},interpolation:{pattern:/((?:^|[^\\])(?:\\{2})*)\${(?:[^{}]|{(?:[^{}]|{[^}]*})*})+}/,lookbehind:!0,inside:{"interpolation-punctuation":{pattern:/^\${|}$/,alias:"punctuation"},rest:Prism.languages.javascript}},string:/[\s\S]+/}}}),Prism.languages.markup&&Prism.languages.markup.tag.addInlined("script","javascript"),Prism.languages.js=Prism.languages.javascript;
diff --git a/uni_modules/zero-markdown-view/components/mp-html/markdown/index.js b/uni_modules/zero-markdown-view/components/mp-html/markdown/index.js
new file mode 100644
index 0000000..8900403
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/markdown/index.js
@@ -0,0 +1,34 @@
+/**
+ * @fileoverview markdown 插件
+ * Include marked (https://github.com/markedjs/marked)
+ * Include github-markdown-css (https://github.com/sindresorhus/github-markdown-css)
+ */
+import marked from './marked.min'
+let index = 0
+
+function Markdown (vm) {
+ this.vm = vm
+ vm._ids = {}
+}
+
+Markdown.prototype.onUpdate = function (content) {
+ if (this.vm.markdown) {
+ return marked(content)
+ }
+}
+
+Markdown.prototype.onParse = function (node, vm) {
+ if (vm.options.markdown) {
+ // 中文 id 需要转换,否则无法跳转
+ if (vm.options.useAnchor && node.attrs && /[\u4e00-\u9fa5]/.test(node.attrs.id)) {
+ const id = 't' + index++
+ this.vm._ids[node.attrs.id] = id
+ node.attrs.id = id
+ }
+ if (node.name === 'p' || node.name === 'table' || node.name === 'tr' || node.name === 'th' || node.name === 'td' || node.name === 'blockquote' || node.name === 'pre' || node.name === 'code') {
+ node.attrs.class = `md-${node.name} ${node.attrs.class || ''}`
+ }
+ }
+}
+
+export default Markdown
diff --git a/uni_modules/zero-markdown-view/components/mp-html/markdown/marked.min.js b/uni_modules/zero-markdown-view/components/mp-html/markdown/marked.min.js
new file mode 100644
index 0000000..2efcf53
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/markdown/marked.min.js
@@ -0,0 +1,6 @@
+/*!
+ * marked - a markdown parser
+ * Copyright (c) 2011-2020, Christopher Jeffrey. (MIT Licensed)
+ * https://github.com/markedjs/marked
+ */
+function t(){"use strict";function i(e,t){for(var n=0;ne.length)&&(t=e.length);for(var n=0,r=new Array(t);n=e.length?{done:!0}:{done:!1,value:e[r++]}}}throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function n(e){return c[e]}var e,t=(function(t){function e(){return{baseUrl:null,breaks:!1,gfm:!0,headerIds:!0,headerPrefix:"",highlight:null,langPrefix:"language-",mangle:!0,pedantic:!1,renderer:null,sanitize:!1,sanitizer:null,silent:!1,smartLists:!1,smartypants:!1,tokenizer:null,walkTokens:null,xhtml:!1}}t.exports={defaults:e(),getDefaults:e,changeDefaults:function(e){t.exports.defaults=e}}}(e={exports:{}}),e.exports),r=(t.defaults,t.getDefaults,t.changeDefaults,/[&<>"']/),l=/[&<>"']/g,a=/[<>"']|&(?!#?\w+;)/,o=/[<>"']|&(?!#?\w+;)/g,c={"&":"&","<":"<",">":">",'"':""","'":"'"};var u=/&(#(?:\d+)|(?:#x[0-9A-Fa-f]+)|(?:\w+));?/gi;function h(e){return e.replace(u,function(e,t){return"colon"===(t=t.toLowerCase())?":":"#"===t.charAt(0)?"x"===t.charAt(1)?String.fromCharCode(parseInt(t.substring(2),16)):String.fromCharCode(+t.substring(1)):""})}var g=/(^|[^\[])\^/g;var f=/[^\w:]/g,d=/^$|^[a-z][a-z0-9+.-]*:|^[?#]/i;var k={},b=/^[^:]+:\/*[^/]*$/,m=/^([^:]+:)[\s\S]*$/,x=/^([^:]+:\/*[^/]*)[\s\S]*$/;function w(e,t){k[" "+e]||(b.test(e)?k[" "+e]=e+"/":k[" "+e]=v(e,"/",!0));var n=-1===(e=k[" "+e]).indexOf(":");return"//"===t.substring(0,2)?n?t:e.replace(m,"$1")+t:"/"===t.charAt(0)?n?t:e.replace(x,"$1")+t:e+t}function v(e,t,n){var r=e.length;if(0===r)return"";for(var i=0;it)n.splice(t);else for(;n.length>=1,e+=e;return n+e},q=t.defaults,O=v,C=R,U=_,j=T;function E(e,t,n){var r=t.href,i=t.title?U(t.title):null,t=e[1].replace(/\\([\[\]])/g,"$1");return"!"!==e[0].charAt(0)?{type:"link",raw:n,href:r,title:i,text:t}:{type:"image",raw:n,href:r,title:i,text:U(t)}}var D=function(){function e(e){this.options=e||q}var t=e.prototype;return t.space=function(e){e=this.rules.block.newline.exec(e);if(e)return 1=n.length?e.slice(n.length):e}).join("\n")}(n,t[3]||"");return{type:"code",raw:n,lang:t[2]&&t[2].trim(),text:e}}},t.heading=function(e){e=this.rules.block.heading.exec(e);if(e)return{type:"heading",raw:e[0],depth:e[1].length,text:e[2]}},t.nptable=function(e){e=this.rules.block.nptable.exec(e);if(e){var t={type:"table",header:C(e[1].replace(/^ *| *\| *$/g,"")),align:e[2].replace(/^ *|\| *$/g,"").split(/ *\| */),cells:e[3]?e[3].replace(/\n$/,"").split("\n"):[],raw:e[0]};if(t.header.length===t.align.length){for(var n=t.align.length,r=0;r ?/gm,"");return{type:"blockquote",raw:t[0],text:e}}},t.list=function(e){e=this.rules.block.list.exec(e);if(e){for(var t,n,r,i,s,l=e[0],a=e[2],o=1g[0].length||3/i.test(e[0])&&(t=!1),!n&&/^<(pre|code|kbd|script)(\s|>)/i.test(e[0])?n=!0:n&&/^<\/(pre|code|kbd|script)(\s|>)/i.test(e[0])&&(n=!1),{type:this.options.sanitize?"text":"html",raw:e[0],inLink:t,inRawBlock:n,text:this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):U(e[0]):e[0]}},t.link=function(e){var t=this.rules.inline.link.exec(e);if(t){e=j(t[2],"()");-1$/,"$1"))&&e.replace(this.rules.inline._escapes,"$1"),title:r&&r.replace(this.rules.inline._escapes,"$1")},t[0])}},t.reflink=function(e,t){if((n=this.rules.inline.reflink.exec(e))||(n=this.rules.inline.nolink.exec(e))){e=(n[2]||n[1]).replace(/\s+/g," ");if((e=t[e.toLowerCase()])&&e.href)return E(n,e,n[0]);var n=n[0].charAt(0);return{type:"text",raw:n,text:n}}},t.strong=function(e,t,n){void 0===n&&(n="");var r=this.rules.inline.strong.start.exec(e);if(r&&(!r[1]||r[1]&&(""===n||this.rules.inline.punctuation.exec(n)))){t=t.slice(-1*e.length);var i,s="**"===r[0]?this.rules.inline.strong.endAst:this.rules.inline.strong.endUnd;for(s.lastIndex=0;null!=(r=s.exec(t));)if(i=this.rules.inline.strong.middle.exec(t.slice(0,r.index+3)))return{type:"strong",raw:e.slice(0,i[0].length),text:e.slice(2,i[0].length-2)}}},t.em=function(e,t,n){void 0===n&&(n="");var r=this.rules.inline.em.start.exec(e);if(r&&(!r[1]||r[1]&&(""===n||this.rules.inline.punctuation.exec(n)))){t=t.slice(-1*e.length);var i,s="*"===r[0]?this.rules.inline.em.endAst:this.rules.inline.em.endUnd;for(s.lastIndex=0;null!=(r=s.exec(t));)if(i=this.rules.inline.em.middle.exec(t.slice(0,r.index+2)))return{type:"em",raw:e.slice(0,i[0].length),text:e.slice(1,i[0].length-1)}}},t.codespan=function(e){var t=this.rules.inline.code.exec(e);if(t){var n=t[2].replace(/\n/g," "),r=/[^ ]/.test(n),e=n.startsWith(" ")&&n.endsWith(" ");return r&&e&&(n=n.substring(1,n.length-1)),n=U(n,!0),{type:"codespan",raw:t[0],text:n}}},t.br=function(e){e=this.rules.inline.br.exec(e);if(e)return{type:"br",raw:e[0]}},t.del=function(e){e=this.rules.inline.del.exec(e);if(e)return{type:"del",raw:e[0],text:e[2]}},t.autolink=function(e,t){e=this.rules.inline.autolink.exec(e);if(e){var n,t="@"===e[2]?"mailto:"+(n=U(this.options.mangle?t(e[1]):e[1])):n=U(e[1]);return{type:"link",raw:e[0],text:n,href:t,tokens:[{type:"text",raw:n,text:n}]}}},t.url=function(e,t){var n,r,i,s;if(n=this.rules.inline.url.exec(e)){if("@"===n[2])i="mailto:"+(r=U(this.options.mangle?t(n[0]):n[0]));else{for(;s=n[0],n[0]=this.rules.inline._backpedal.exec(n[0])[0],s!==n[0];);r=U(n[0]),i="www."===n[1]?"http://"+r:r}return{type:"link",raw:n[0],text:r,href:i,tokens:[{type:"text",raw:r,text:r}]}}},t.inlineText=function(e,t,n){e=this.rules.inline.text.exec(e);if(e){n=t?this.options.sanitize?this.options.sanitizer?this.options.sanitizer(e[0]):U(e[0]):e[0]:U(this.options.smartypants?n(e[0]):e[0]);return{type:"text",raw:e[0],text:n}}},e}(),R=$,T=z,$=A,z={newline:/^\n+/,code:/^( {4}[^\n]+\n*)+/,fences:/^ {0,3}(`{3,}(?=[^`\n]*\n)|~{3,})([^\n]*)\n(?:|([\s\S]*?)\n)(?: {0,3}\1[~`]* *(?:\n+|$)|$)/,hr:/^ {0,3}((?:- *){3,}|(?:_ *){3,}|(?:\* *){3,})(?:\n+|$)/,heading:/^ {0,3}(#{1,6}) +([^\n]*?)(?: +#+)? *(?:\n+|$)/,blockquote:/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/,list:/^( {0,3})(bull) [\s\S]+?(?:hr|def|\n{2,}(?! )(?! {0,3}bull )\n*|\s*$)/,html:"^ {0,3}(?:<(script|pre|style)[\\s>][\\s\\S]*?(?:\\1>[^\\n]*\\n+|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>\\n*|$)|\\n*|$)|\\n*|$)|?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:\\n{2,}|$)|<(?!script|pre|style)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$)|(?!script|pre|style)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:\\n{2,}|$))",def:/^ {0,3}\[(label)\]: *\n? *([^\s>]+)>?(?:(?: +\n? *| *\n *)(title))? *(?:\n+|$)/,nptable:R,table:R,lheading:/^([^\n]+)\n {0,3}(=+|-+) *(?:\n+|$)/,_paragraph:/^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html)[^\n]+)*)/,text:/^[^\n]+/,_label:/(?!\s*\])(?:\\[\[\]]|[^\[\]])+/,_title:/(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/};z.def=T(z.def).replace("label",z._label).replace("title",z._title).getRegex(),z.bullet=/(?:[*+-]|\d{1,9}[.)])/,z.item=/^( *)(bull) ?[^\n]*(?:\n(?! *bull ?)[^\n]*)*/,z.item=T(z.item,"gm").replace(/bull/g,z.bullet).getRegex(),z.listItemStart=T(/^( *)(bull)/).replace("bull",z.bullet).getRegex(),z.list=T(z.list).replace(/bull/g,z.bullet).replace("hr","\\n+(?=\\1?(?:(?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$))").replace("def","\\n+(?="+z.def.source+")").getRegex(),z._tag="address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|section|source|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul",z._comment=/|$)/,z.html=T(z.html,"i").replace("comment",z._comment).replace("tag",z._tag).replace("attribute",/ +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex(),z.paragraph=T(z._paragraph).replace("hr",z.hr).replace("heading"," {0,3}#{1,6} ").replace("|lheading","").replace("blockquote"," {0,3}>").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)").replace("tag",z._tag).getRegex(),z.blockquote=T(z.blockquote).replace("paragraph",z.paragraph).getRegex(),z.normal=$({},z),z.gfm=$({},z.normal,{nptable:"^ *([^|\\n ].*\\|.*)\\n {0,3}([-:]+ *\\|[-| :]*)(?:\\n((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)",table:"^ *\\|(.+)\\n {0,3}\\|?( *[-:]+[-| :]*)(?:\\n *((?:(?!\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)"}),z.gfm.nptable=T(z.gfm.nptable).replace("hr",z.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)").replace("tag",z._tag).getRegex(),z.gfm.table=T(z.gfm.table).replace("hr",z.hr).replace("heading"," {0,3}#{1,6} ").replace("blockquote"," {0,3}>").replace("code"," {4}[^\\n]").replace("fences"," {0,3}(?:`{3,}(?=[^`\\n]*\\n)|~{3,})[^\\n]*\\n").replace("list"," {0,3}(?:[*+-]|1[.)]) ").replace("html","?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|!--)").replace("tag",z._tag).getRegex(),z.pedantic=$({},z.normal,{html:T("^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?\\1> *(?:\\n{2,}|\\s*$)|\\s]*)*?/?> *(?:\\n{2,}|\\s*$))").replace("comment",z._comment).replace(/tag/g,"(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(),def:/^ *\[([^\]]+)\]: *([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/,heading:/^ *(#{1,6}) *([^\n]+?) *(?:#+ *)?(?:\n+|$)/,fences:R,paragraph:T(z.normal._paragraph).replace("hr",z.hr).replace("heading"," *#{1,6} *[^\n]").replace("lheading",z.lheading).replace("blockquote"," {0,3}>").replace("|fences","").replace("|list","").replace("|html","").getRegex()});R={escape:/^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/,autolink:/^<(scheme:[^\s\x00-\x1f<>]*|email)>/,url:R,tag:"^comment|^[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^|^",link:/^!?\[(label)\]\(\s*(href)(?:\s+(title))?\s*\)/,reflink:/^!?\[(label)\]\[(?!\s*\])((?:\\[\[\]]?|[^\[\]\\])+)\]/,nolink:/^!?\[(?!\s*\])((?:\[[^\[\]]*\]|\\[\[\]]|[^\[\]])*)\](?:\[\])?/,reflinkSearch:"reflink|nolink(?!\\()",strong:{start:/^(?:(\*\*(?=[*punctuation]))|\*\*)(?![\s])|__/,middle:/^\*\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*\*$|^__(?![\s])((?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?)__$/,endAst:/[^punctuation\s]\*\*(?!\*)|[punctuation]\*\*(?!\*)(?:(?=[punctuation_\s]|$))/,endUnd:/[^\s]__(?!_)(?:(?=[punctuation*\s])|$)/},em:{start:/^(?:(\*(?=[punctuation]))|\*)(?![*\s])|_/,middle:/^\*(?:(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)|\*(?:(?!overlapSkip)(?:[^*]|\\\*)|overlapSkip)*?\*)+?\*$|^_(?![_\s])(?:(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)|_(?:(?!overlapSkip)(?:[^_]|\\_)|overlapSkip)*?_)+?_$/,endAst:/[^punctuation\s]\*(?!\*)|[punctuation]\*(?!\*)(?:(?=[punctuation_\s]|$))/,endUnd:/[^\s]_(?!_)(?:(?=[punctuation*\s])|$)/},code:/^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/,br:/^( {2,}|\\)\n(?!\s*$)/,del:R,text:/^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\?@\\[\\]`^{|}~"};R.punctuation=T(R.punctuation).replace(/punctuation/g,R._punctuation).getRegex(),R._blockSkip="\\[[^\\]]*?\\]\\([^\\)]*?\\)|`[^`]*?`|<[^>]*?>",R._overlapSkip="__[^_]*?__|\\*\\*\\[^\\*\\]*?\\*\\*",R._comment=T(z._comment).replace("(?:--\x3e|$)","--\x3e").getRegex(),R.em.start=T(R.em.start).replace(/punctuation/g,R._punctuation).getRegex(),R.em.middle=T(R.em.middle).replace(/punctuation/g,R._punctuation).replace(/overlapSkip/g,R._overlapSkip).getRegex(),R.em.endAst=T(R.em.endAst,"g").replace(/punctuation/g,R._punctuation).getRegex(),R.em.endUnd=T(R.em.endUnd,"g").replace(/punctuation/g,R._punctuation).getRegex(),R.strong.start=T(R.strong.start).replace(/punctuation/g,R._punctuation).getRegex(),R.strong.middle=T(R.strong.middle).replace(/punctuation/g,R._punctuation).replace(/overlapSkip/g,R._overlapSkip).getRegex(),R.strong.endAst=T(R.strong.endAst,"g").replace(/punctuation/g,R._punctuation).getRegex(),R.strong.endUnd=T(R.strong.endUnd,"g").replace(/punctuation/g,R._punctuation).getRegex(),R.blockSkip=T(R._blockSkip,"g").getRegex(),R.overlapSkip=T(R._overlapSkip,"g").getRegex(),R._escapes=/\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/g,R._scheme=/[a-zA-Z][a-zA-Z0-9+.-]{1,31}/,R._email=/[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/,R.autolink=T(R.autolink).replace("scheme",R._scheme).replace("email",R._email).getRegex(),R._attribute=/\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/,R.tag=T(R.tag).replace("comment",R._comment).replace("attribute",R._attribute).getRegex(),R._label=/(?:\[(?:\\.|[^\[\]\\])*\]|\\.|`[^`]*`|[^\[\]\\`])*?/,R._href=/<(?:\\[<>]?|[^\s<>\\])*>|[^\s\x00-\x1f]*/,R._title=/"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/,R.link=T(R.link).replace("label",R._label).replace("href",R._href).replace("title",R._title).getRegex(),R.reflink=T(R.reflink).replace("label",R._label).getRegex(),R.reflinkSearch=T(R.reflinkSearch,"g").replace("reflink",R.reflink).replace("nolink",R.nolink).getRegex(),R.normal=$({},R),R.pedantic=$({},R.normal,{strong:{start:/^__|\*\*/,middle:/^__(?=\S)([\s\S]*?\S)__(?!_)|^\*\*(?=\S)([\s\S]*?\S)\*\*(?!\*)/,endAst:/\*\*(?!\*)/g,endUnd:/__(?!_)/g},em:{start:/^_|\*/,middle:/^()\*(?=\S)([\s\S]*?\S)\*(?!\*)|^_(?=\S)([\s\S]*?\S)_(?!_)/,endAst:/\*(?!\*)/g,endUnd:/_(?!_)/g},link:T(/^!?\[(label)\]\((.*?)\)/).replace("label",R._label).getRegex(),reflink:T(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label",R._label).getRegex()}),R.gfm=$({},R.normal,{escape:T(R.escape).replace("])","~|])").getRegex(),_extended_email:/[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/,url:/^((?:ftp|https?):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/,_backpedal:/(?:[^?!.,:;*_~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_~)]+(?!$))+/,del:/^(~~?)(?=[^\s~])([\s\S]*?[^\s~])\1(?=[^~]|$)/,text:/^([`~]+|[^`~])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\'+(n?e:V(e,!0))+"\n":""+(n?e:V(e,!0))+"
\n"},t.blockquote=function(e){return"\n"+e+"
\n"},t.html=function(e){return e},t.heading=function(e,t,n,r){return this.options.headerIds?"\n":""+e+"\n"},t.hr=function(){return this.options.xhtml?"
\n":"
\n"},t.list=function(e,t,n){var r=t?"ol":"ul";return"<"+r+(t&&1!==n?' start="'+n+'"':"")+">\n"+e+""+r+">\n"},t.listitem=function(e){return""+e+"\n"},t.checkbox=function(e){return" "},t.paragraph=function(e){return""+e+"
\n"},t.table=function(e,t){return"\n\n"+e+"\n"+(t=t&&""+t+"")+"
\n"},t.tablerow=function(e){return"\n"+e+"
\n"},t.tablecell=function(e,t){var n=t.header?"th":"td";return(t.align?"<"+n+' align="'+t.align+'">':"<"+n+">")+e+""+n+">\n"},t.strong=function(e){return""+e+""},t.em=function(e){return""+e+""},t.codespan=function(e){return""+e+""},t.br=function(){return this.options.xhtml?"
":"
"},t.del=function(e){return""+e+""},t.link=function(e,t,n){if(null===(e=G(this.options.sanitize,this.options.baseUrl,e)))return n;e='"+n+""},t.image=function(e,t,n){if(null===(e=G(this.options.sanitize,this.options.baseUrl,e)))return n;n='
":">"},t.text=function(e){return e},e}(),J=function(){function e(){}var t=e.prototype;return t.strong=function(e){return e},t.em=function(e){return e},t.codespan=function(e){return e},t.del=function(e){return e},t.html=function(e){return e},t.text=function(e){return e},t.link=function(e,t,n){return""+n},t.image=function(e,t,n){return""+n},t.br=function(){return""},e}(),K=function(){function e(){this.seen={}}var t=e.prototype;return t.serialize=function(e){return e.toLowerCase().trim().replace(/<[!\/a-z].*?>/gi,"").replace(/[\u2000-\u206F\u2E00-\u2E7F\\'!"#$%&()*+,./:;<=>?@[\]^`{|}~]/g,"").replace(/\s/g,"-")},t.getNextSafeSlug=function(e,t){var n=e,r=0;if(this.seen.hasOwnProperty(n))for(r=this.seen[e];n=e+"-"+ ++r,this.seen.hasOwnProperty(n););return t||(this.seen[e]=r,this.seen[n]=0),n},t.slug=function(e,t){void 0===t&&(t={});var n=this.serialize(e);return this.getNextSafeSlug(n,t.dryrun)},e}(),Q=t.defaults,Y=y,ee=function(){function n(e){this.options=e||Q,this.options.renderer=this.options.renderer||new H,this.renderer=this.options.renderer,this.renderer.options=this.options,this.textRenderer=new J,this.slugger=new K}n.parse=function(e,t){return new n(t).parse(e)},n.parseInline=function(e,t){return new n(t).parseInline(e)};var e=n.prototype;return e.parse=function(e,t){void 0===t&&(t=!0);for(var n,r,i,s,l,a,o,c,u,p,h,g,f,d,k,b="",m=e.length,x=0;xAn error occurred:
"+re(e.message+"",!0)+"
";throw e}}return se.options=se.setOptions=function(e){return te(se.defaults,e),ie(se.defaults),se},se.getDefaults=_,se.defaults=t,se.use=function(a){var t,n=te({},a);a.renderer&&function(){var e,l=se.defaults.renderer||new H;for(e in a.renderer)!function(i){var s=l[i];l[i]=function(){for(var e=arguments.length,t=new Array(e),n=0;nAn error occurred:"+re(e.message+"",!0)+"
";throw e}},se.Parser=ee,se.parser=ee.parse,se.Renderer=H,se.TextRenderer=J,se.Lexer=W,se.lexer=W.lex,se.Tokenizer=D,se.Slugger=K,se.parse=se};export default t();
\ No newline at end of file
diff --git a/uni_modules/zero-markdown-view/components/mp-html/mp-html.vue b/uni_modules/zero-markdown-view/components/mp-html/mp-html.vue
new file mode 100644
index 0000000..332c3e0
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/mp-html.vue
@@ -0,0 +1,503 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/uni_modules/zero-markdown-view/components/mp-html/node/node.vue b/uni_modules/zero-markdown-view/components/mp-html/node/node.vue
new file mode 100644
index 0000000..3253509
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/node/node.vue
@@ -0,0 +1,678 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{n.text}}
+
+
+ {{n.text}}
+
+ \n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/zero-markdown-view/components/mp-html/parser.js b/uni_modules/zero-markdown-view/components/mp-html/parser.js
new file mode 100644
index 0000000..e2e7a87
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/parser.js
@@ -0,0 +1,1335 @@
+/**
+ * @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'),
+
+ // #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',
+ 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 tagSelector={}
+const {
+ windowWidth,
+ // #ifdef MP-WEIXIN
+ system
+ // #endif
+} = uni.getSystemInfoSync()
+const blankChar = makeMap(' ,\r,\n,\t,\f')
+let idIndex = 0
+
+// #ifdef H5 || APP-PLUS
+config.ignoreTags.iframe = undefined
+config.trustTags.iframe = true
+config.ignoreTags.embed = undefined
+config.trustTags.embed = true
+// #endif
+// #ifdef APP-PLUS-NVUE
+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
+}
+
+/**
+ * @description 解码 html 实体
+ * @param {String} str 要解码的字符串
+ * @param {Boolean} amp 要不要解码 &
+ * @returns {String} 解码后的字符串
+ */
+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] === '#') {
+ // { 形式的实体
+ 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 {
+ // 形式的实体
+ 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
+}
+
+/**
+ * @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.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
+ }
+ }
+
+ 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.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
+ }
+ }
+ return true
+}
+
+/**
+ * @description 将链接拼接上主域名
+ * @param {String} url 需要拼接的链接
+ * @returns {String} 拼接后的链接
+ */
+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 = ''
+
+ 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 = undefined
+ }
+ }
+
+ // 转换 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')) {
+ // 填充链接
+ 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
+}
+
+/**
+ * @description 解析到标签名
+ * @param {String} name 标签名
+ * @private
+ */
+Parser.prototype.onTagName = function (name) {
+ this.tagName = this.xml ? name : name.toLowerCase()
+ if (this.tagName === 'svg') {
+ this.xml = (this.xml || 0) + 1 // svg 标签内大小写敏感
+ config.ignoreTags.style = undefined // svg 标签内 style 可用
+ }
+}
+
+/**
+ * @description 解析到属性名
+ * @param {String} name 属性名
+ * @private
+ */
+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 = 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
+ }
+}
+
+/**
+ * @description 解析到标签开始
+ * @param {Boolean} selfClose 是否有自闭合标识 />
+ * @private
+ */
+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') {
+ // 设置 id 以便获取 context
+ 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 (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
+ }
+
+ // 解析 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
+ }
+ 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%'
+ }
+ }
+ // #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)
+ }
+
+ // 加入节点树
+ 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: {
+ class: tagSelector[name] || '',
+ style: this.tagStyle[name] || ''
+ }
+ })
+ }
+}
+
+/**
+ * @description 处理标签出栈
+ * @private
+ */
+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.pre && this.pre !== 2) {
+ // 是否合并空白符标识
+ 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') {
+ 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
+ }
+ }
+ for (let i = 0; i < (node.children || []).length; i++) {
+ traversal(node.children[i])
+ }
+ }
+ })(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}"`
+ }
+ }
+ if (!node.children) {
+ src += '/>'
+ } else {
+ src += '>'
+ for (let i = 0; i < node.children.length; i++) {
+ traversal(node.children[i])
+ }
+ 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
+ config.ignoreTags.style = true
+ 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
+ }
+ 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 = ''
+ node.html = str
+ /* #endif */
+ } 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 = 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 || [])
+ }
+ }
+ })(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++
+ }
+ 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)
+ }
+ // 设置竖直对齐
+ 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'
+ }
+ // 设置水平对齐
+ 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'
+ }
+ }
+ 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
+ }
+ }
+ }
+ if (style) {
+ td.attrs.style = style
+ }
+ cells.push(td)
+ col++
+ }
+ }
+ if (row === 1) {
+ let temp = ''
+ for (let i = 1; i < col; i++) {
+ temp += (width[i] ? width[i] : 'auto') + ' '
+ }
+ styleObj['grid-template-columns'] = temp
+ }
+ }
+ 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]]
+ }
+ 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
+ }
+ }
+ }
+ // 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) {
+ // 合并空白符
+ 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 === ' ') {
+ 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
+}
+
+/**
+ * @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()
+ }
+}
+
+/**
+ * @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
+ 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
+ }
+ return false
+}
+
+/**
+ * @description 文本状态
+ * @private
+ */
+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
+ }
+ 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
+ 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]]) {
+ // 解析到标签名
+ 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 === '=') {
+ // 解析到属性名
+ 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++
+ }
+}
+
+/**
+ * @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
+ 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
+ }
+}
+
+/**
+ * @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++
+ }
+}
+
+export default Parser
diff --git a/uni_modules/zero-markdown-view/components/mp-html/style/index.js b/uni_modules/zero-markdown-view/components/mp-html/style/index.js
new file mode 100644
index 0000000..abfb371
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/style/index.js
@@ -0,0 +1,129 @@
+/**
+ * @fileoverview style 插件
+ */
+// #ifndef APP-PLUS-NVUE
+import Parser from './parser'
+// #endif
+
+function Style () {
+ this.styles = []
+}
+
+// #ifndef APP-PLUS-NVUE
+Style.prototype.onParse = function (node, vm) {
+ // 获取样式
+ if (node.name === 'style' && node.children.length && node.children[0].type === 'text') {
+ this.styles = this.styles.concat(new Parser().parse(node.children[0].text))
+ } else if (node.name) {
+ // 匹配样式(对非文本标签)
+ // 存储不同优先级的样式 name < class < id < 后代
+ let matched = ['', '', '', '']
+ for (let i = 0, len = this.styles.length; i < len; i++) {
+ const item = this.styles[i]
+ let res = match(node, item.key || item.list[item.list.length - 1])
+ let j
+ if (res) {
+ // 后代选择器
+ if (!item.key) {
+ j = item.list.length - 2
+ for (let k = vm.stack.length; j >= 0 && k--;) {
+ // 子选择器
+ if (item.list[j] === '>') {
+ // 错误情况
+ if (j < 1 || j > item.list.length - 2) break
+ if (match(vm.stack[k], item.list[j - 1])) {
+ j -= 2
+ } else {
+ j++
+ }
+ } else if (match(vm.stack[k], item.list[j])) {
+ j--
+ }
+ }
+ res = 4
+ }
+ if (item.key || j < 0) {
+ // 添加伪类
+ if (item.pseudo && node.children) {
+ let text
+ item.style = item.style.replace(/content:([^;]+)/, (_, $1) => {
+ text = $1.replace(/['"]/g, '')
+ // 处理 attr 函数
+ .replace(/attr\((.+?)\)/, (_, $1) => node.attrs[$1.trim()] || '')
+ // 编码 \xxx
+ .replace(/\\(\w{4})/, (_, $1) => String.fromCharCode(parseInt($1, 16)))
+ return ''
+ })
+ const pseudo = {
+ name: 'span',
+ attrs: {
+ style: item.style
+ },
+ children: [{
+ type: 'text',
+ text
+ }]
+ }
+ if (item.pseudo === 'before') {
+ node.children.unshift(pseudo)
+ } else {
+ node.children.push(pseudo)
+ }
+ } else {
+ matched[res - 1] += item.style + (item.style[item.style.length - 1] === ';' ? '' : ';')
+ }
+ }
+ }
+ }
+ matched = matched.join('')
+ if (matched.length > 2) {
+ node.attrs.style = matched + (node.attrs.style || '')
+ }
+ }
+}
+
+/**
+ * @description 匹配样式
+ * @param {object} node 要匹配的标签
+ * @param {string|string[]} keys 选择器
+ * @returns {number} 0:不匹配;1:name 匹配;2:class 匹配;3:id 匹配
+ */
+function match (node, keys) {
+ function matchItem (key) {
+ if (key[0] === '#') {
+ // 匹配 id
+ if (node.attrs.id && node.attrs.id.trim() === key.substr(1)) return 3
+ } else if (key[0] === '.') {
+ // 匹配 class
+ key = key.substr(1)
+ const selectors = (node.attrs.class || '').split(' ')
+ for (let i = 0; i < selectors.length; i++) {
+ if (selectors[i].trim() === key) return 2
+ }
+ } else if (node.name === key) {
+ // 匹配 name
+ return 1
+ }
+ return 0
+ }
+
+ // 多选择器交集
+ if (keys instanceof Array) {
+ let res = 0
+ for (let j = 0; j < keys.length; j++) {
+ const tmp = matchItem(keys[j])
+ // 任意一个不匹配就失败
+ if (!tmp) return 0
+ // 优先级最大的一个作为最终优先级
+ if (tmp > res) {
+ res = tmp
+ }
+ }
+ return res
+ }
+
+ return matchItem(keys)
+}
+// #endif
+
+export default Style
diff --git a/uni_modules/zero-markdown-view/components/mp-html/style/parser.js b/uni_modules/zero-markdown-view/components/mp-html/style/parser.js
new file mode 100644
index 0000000..b639334
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/mp-html/style/parser.js
@@ -0,0 +1,175 @@
+const blank = {
+ ' ': true,
+ '\n': true,
+ '\t': true,
+ '\r': true,
+ '\f': true
+}
+
+function Parser () {
+ this.styles = []
+ this.selectors = []
+}
+
+/**
+ * @description 解析 css 字符串
+ * @param {string} content css 内容
+ */
+Parser.prototype.parse = function (content) {
+ new Lexer(this).parse(content)
+ return this.styles
+}
+
+/**
+ * @description 解析到一个选择器
+ * @param {string} name 名称
+ */
+Parser.prototype.onSelector = function (name) {
+ // 不支持的选择器
+ if (name.includes('[') || name.includes('*') || name.includes('@')) return
+ const selector = {}
+ // 伪类
+ if (name.includes(':')) {
+ const info = name.split(':')
+ const pseudo = info.pop()
+ if (pseudo === 'before' || pseudo === 'after') {
+ selector.pseudo = pseudo
+ name = info[0]
+ } else return
+ }
+
+ // 分割交集选择器
+ function splitItem (str) {
+ const arr = []
+ let i, start
+ for (i = 1, start = 0; i < str.length; i++) {
+ if (str[i] === '.' || str[i] === '#') {
+ arr.push(str.substring(start, i))
+ start = i
+ }
+ }
+ if (!arr.length) {
+ return str
+ } else {
+ arr.push(str.substring(start, i))
+ return arr
+ }
+ }
+
+ // 后代选择器
+ if (name.includes(' ')) {
+ selector.list = []
+ const list = name.split(' ')
+ for (let i = 0; i < list.length; i++) {
+ if (list[i].length) {
+ // 拆分子选择器
+ const arr = list[i].split('>')
+ for (let j = 0; j < arr.length; j++) {
+ selector.list.push(splitItem(arr[j]))
+ if (j < arr.length - 1) {
+ selector.list.push('>')
+ }
+ }
+ }
+ }
+ } else {
+ selector.key = splitItem(name)
+ }
+
+ this.selectors.push(selector)
+}
+
+/**
+ * @description 解析到选择器内容
+ * @param {string} content 内容
+ */
+Parser.prototype.onContent = function (content) {
+ // 并集选择器
+ for (let i = 0; i < this.selectors.length; i++) {
+ this.selectors[i].style = content
+ }
+ this.styles = this.styles.concat(this.selectors)
+ this.selectors = []
+}
+
+/**
+ * @description css 词法分析器
+ * @param {object} handler 高层处理器
+ */
+function Lexer (handler) {
+ this.selector = ''
+ this.style = ''
+ this.handler = handler
+}
+
+Lexer.prototype.parse = function (content) {
+ this.i = 0
+ this.content = content
+ this.state = this.blank
+ for (let len = content.length; this.i < len; this.i++) {
+ this.state(content[this.i])
+ }
+}
+
+Lexer.prototype.comment = function () {
+ this.i = this.content.indexOf('*/', this.i) + 1
+ if (!this.i) {
+ this.i = this.content.length
+ }
+}
+
+Lexer.prototype.blank = function (c) {
+ if (!blank[c]) {
+ if (c === '/' && this.content[this.i + 1] === '*') {
+ this.comment()
+ return
+ }
+ this.selector += c
+ this.state = this.name
+ }
+}
+
+Lexer.prototype.name = function (c) {
+ if (c === '/' && this.content[this.i + 1] === '*') {
+ this.comment()
+ return
+ }
+ if (c === '{' || c === ',' || c === ';') {
+ this.handler.onSelector(this.selector.trimEnd())
+ this.selector = ''
+ if (c !== '{') {
+ while (blank[this.content[++this.i]]);
+ }
+ if (this.content[this.i] === '{') {
+ this.floor = 1
+ this.state = this.val
+ } else {
+ this.selector += this.content[this.i]
+ }
+ } else if (blank[c]) {
+ this.selector += ' '
+ } else {
+ this.selector += c
+ }
+}
+
+Lexer.prototype.val = function (c) {
+ if (c === '/' && this.content[this.i + 1] === '*') {
+ this.comment()
+ return
+ }
+ if (c === '{') {
+ this.floor++
+ } else if (c === '}') {
+ this.floor--
+ if (!this.floor) {
+ this.handler.onContent(this.style)
+ this.style = ''
+ this.state = this.blank
+ return
+ }
+ }
+ this.style += c
+}
+
+export default Parser
diff --git a/uni_modules/zero-markdown-view/components/zero-markdown-view/zero-markdown-view.vue b/uni_modules/zero-markdown-view/components/zero-markdown-view/zero-markdown-view.vue
new file mode 100644
index 0000000..2e02721
--- /dev/null
+++ b/uni_modules/zero-markdown-view/components/zero-markdown-view/zero-markdown-view.vue
@@ -0,0 +1,177 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/uni_modules/zero-markdown-view/package.json b/uni_modules/zero-markdown-view/package.json
new file mode 100644
index 0000000..4c62dd3
--- /dev/null
+++ b/uni_modules/zero-markdown-view/package.json
@@ -0,0 +1,86 @@
+{
+ "id": "zero-markdown-view",
+ "displayName": "zero-markdown-view(markdown解析)",
+ "version": "2.0.5",
+ "description": "一行代码即可实现markdown解析,支持自定义主题色,支持vue2,vue3.",
+ "keywords": [
+ "markdown",
+ "markdown解析",
+ "代码块",
+ "代码高亮",
+ "mp-html"
+],
+ "repository": "",
+ "engines": {
+ "HBuilderX": "^3.1.0"
+ },
+ "dcloudext": {
+ "type": "component-vue",
+ "sale": {
+ "regular": {
+ "price": "0.00"
+ },
+ "sourcecode": {
+ "price": "0.00"
+ }
+ },
+ "contact": {
+ "qq": ""
+ },
+ "declaration": {
+ "ads": "无",
+ "data": "插件不采集任何数据",
+ "permissions": "无"
+ },
+ "npmurl": ""
+ },
+ "uni_modules": {
+ "dependencies": [],
+ "encrypt": [],
+ "platforms": {
+ "cloud": {
+ "tcb": "y",
+ "aliyun": "y",
+ "alipay": "n"
+ },
+ "client": {
+ "Vue": {
+ "vue2": "y",
+ "vue3": "y"
+ },
+ "App": {
+ "app-vue": "u",
+ "app-nvue": "u"
+ },
+ "H5-mobile": {
+ "Safari": "y",
+ "Android Browser": "y",
+ "微信浏览器(Android)": "y",
+ "QQ浏览器(Android)": "y"
+ },
+ "H5-pc": {
+ "Chrome": "y",
+ "IE": "u",
+ "Edge": "y",
+ "Firefox": "y",
+ "Safari": "y"
+ },
+ "小程序": {
+ "微信": "y",
+ "阿里": "u",
+ "百度": "u",
+ "字节跳动": "u",
+ "QQ": "u",
+ "钉钉": "u",
+ "快手": "u",
+ "飞书": "u",
+ "京东": "u"
+ },
+ "快应用": {
+ "华为": "u",
+ "联盟": "u"
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/uni_modules/zero-markdown-view/readme.md b/uni_modules/zero-markdown-view/readme.md
new file mode 100644
index 0000000..1c93ad3
--- /dev/null
+++ b/uni_modules/zero-markdown-view/readme.md
@@ -0,0 +1,134 @@
+# zero-markdown-view
+
+
+## 一. 重要更新说明
+
+### v2.0.4
+- 新增点击代码块复制代码-仅小程序可用
+
+### v2.0.1
+- 兼容vue2,vue3
+
+### v2.0.0
+- 省去了 npm install marked
+- 省去了 npm install highlight.js
+- 使用mp-html自带的插件,重新生成uniapp包,大幅减少插件体积
+传送门: [优化思路及详细过程](https://juejin.cn/post/7160995270476431373/) https://juejin.cn/post/7160995270476431373/
+
+## 二. 使用方法
+
+**符合`easycom`组件模式, 导入 `uni_modules` 后直接使用即可 **
+
+```html
+
+
+
+
+
+
+
+
+```
+
+## 三. 参数说明
+
+|参数 |类型 |默认值 |描述 |
+|-- |-- |-- |-- |
+|markdown |String | |markdown文本 |
+|themeColor |String |'#007AFF' |主题色 |
+|codeBgColor|String |'#2d2d2d' |代码块背景色 (不建议修改) |
+
+
+
+## 四. 注意事项
+
+### 关于代码块流式输出闪烁,可以尝试 给代码块后增加 `\n`
+
+
+```javascript
+ computed: {
+ // 流式输出时代码块处理 , 这时候请使用 msgContent 传入组件中
+ msgContent() {
+ if (!this.content) {
+ return
+ }
+ let htmlString = ''
+ // 判断markdown中代码块标识符的数量是否为偶数
+ if (this.content.split("```").length % 2) {
+ let content = this.content
+ if (content[content.length - 1] != '\n') {
+ content += '\n'
+ }
+ htmlString = content
+ } else {
+ htmlString = this.content
+ }
+ return htmlString
+ }
+ },
+```
+
+
+
+### 如何关闭点击代码块复制功能?
+
+找到组件文件夹 `zero-markdown-view`-`mp-html`-`highlight`-`config.js`
+
+**把 `copyByClickCode` 改成 false 即可**
+```
+export default {
+ copyByClickCode: true, // 点击代码块复制
+ showLanguageName: true, // 是否在代码块右上角显示语言的名称
+ showLineNumber: false // 是否显示行号
+}
+```
+
+### 感谢 mp-html 插件
+
+插件地址: [https://ext.dcloud.net.cn/plugin?id=805](https://ext.dcloud.net.cn/plugin?id=805)
+
+文档地址: [https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart](https://jin-yufeng.gitee.io/mp-html/#/overview/quickstart)
+
+
+插件预览:
+
+
+
+> 小程序搜索: zerojs零技术
+
+> 预览的小程序不一定能及时更新当前插件
diff --git a/utils/gxmu/config.js b/utils/gxmu/config.js
new file mode 100644
index 0000000..4eec0de
--- /dev/null
+++ b/utils/gxmu/config.js
@@ -0,0 +1,395 @@
+const GENDER_OPTIONS = ['男', '女']
+const YES_NO_OPTIONS = ['是', '否']
+
+export const GXMU_MODULES = [
+ {
+ title: '十佳青年岗位能手',
+ code: 'gxmu_sjqn',
+ icon: '青',
+ group: '个人奖项',
+ desc: '面向青年岗位骨干与先进典型的申报入口。'
+ },
+ {
+ title: '十佳团支部书记',
+ code: 'gxmu_sjtbzbsj',
+ icon: '书',
+ group: '个人奖项',
+ desc: '聚焦基层团支部书记的履职表现与带动成效。'
+ },
+ {
+ title: '五四红旗团委',
+ code: 'gxmu_wshqtw',
+ icon: '委',
+ group: '组织奖项',
+ desc: '用于先进团委集体申报与材料汇总展示。'
+ },
+ {
+ title: '五四红旗团支部',
+ code: 'gxmu_wshqtzb',
+ icon: '支',
+ group: '组织奖项',
+ desc: '聚焦先进团支部建设成果与品牌工作展示。'
+ },
+ {
+ title: '优秀共青团干部',
+ code: 'gxmu_yxgqtdgb',
+ icon: '干',
+ group: '个人奖项',
+ desc: '面向团学骨干和团务干部的申报入口。'
+ },
+ {
+ title: '优秀共青团员',
+ code: 'gxmu_yxgqty',
+ icon: '员',
+ group: '个人奖项',
+ desc: '面向优秀团员个人事迹与成长表现申报。'
+ },
+ {
+ title: '挑战杯',
+ code: 'gxmu_tzbcy_form',
+ icon: '挑',
+ group: '竞赛项目',
+ desc: '面向挑战杯项目材料填报与过程归档。'
+ },
+ {
+ title: '青马工程',
+ code: 'gxmu_qmgc_form',
+ icon: '青',
+ group: '培养项目',
+ desc: '用于青马工程学员培养信息和成果申报。'
+ },
+ {
+ title: '未来学术之星',
+ code: 'gxmu_wxxzx_project',
+ icon: '星',
+ group: '创新项目',
+ desc: '用于未来学术之星项目申报和材料整理。'
+ }
+]
+
+const createFields = (fields) =>
+ fields.map((field) => ({
+ required: false,
+ placeholder: `请输入${field.label}`,
+ ...field
+ }))
+
+export const GXMU_FORM_MAP = {
+ gxmu_sjqn: {
+ title: '十佳青年岗位能手申报表',
+ subtitle: '参考后台 SJQNForm 字段结构,适用于个人申报信息填写。',
+ sections: [
+ {
+ title: '基础信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'applyType', label: '申报类别', required: true },
+ { key: 'name', label: '姓名', required: true },
+ { key: 'gender', label: '性别', type: 'select', options: GENDER_OPTIONS },
+ { key: 'birthMonth', label: '出生年月', type: 'month' },
+ { key: 'nation', label: '民族' },
+ { key: 'politics', label: '政治面貌' },
+ { key: 'education', label: '学历' },
+ { key: 'position', label: '职务' },
+ { key: 'title', label: '职称' },
+ { key: 'unit', label: '所在单位' }
+ ])
+ },
+ {
+ title: '申报内容',
+ fields: createFields([
+ { key: 'awards', label: '近三年曾获校级及以上奖励', type: 'textarea' },
+ { key: 'experience', label: '工作(学习)经历', type: 'textarea' },
+ { key: 'mainStory', label: '主要事迹', type: 'textarea' }
+ ])
+ }
+ ]
+ },
+ gxmu_sjtbzbsj: {
+ title: '十佳团支部书记申报表',
+ subtitle: '参考后台 SJTBZBSJForm 字段结构,适用于个人申报信息填写。',
+ sections: [
+ {
+ title: '基础信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'name', label: '姓名', required: true },
+ { key: 'gender', label: '性别', type: 'select', options: GENDER_OPTIONS },
+ { key: 'birthMonth', label: '出生年月', type: 'month' },
+ { key: 'nation', label: '民族' },
+ { key: 'politics', label: '政治面貌' },
+ { key: 'collegeClass', label: '学院班级/单位科室' },
+ { key: 'branch', label: '所在团支部' },
+ { key: 'eduEval', label: '上一年度团员教育评议等次' }
+ ])
+ },
+ {
+ title: '申报内容',
+ fields: createFields([
+ { key: 'awards', label: '近三年曾获校级及以上奖励', type: 'textarea' },
+ { key: 'experience', label: '工作(学习)经历', type: 'textarea' },
+ { key: 'mainStory', label: '主要事迹', type: 'textarea' }
+ ])
+ }
+ ]
+ },
+ gxmu_wshqtw: {
+ title: '五四红旗团委申报表',
+ subtitle: '参考后台 WSHQTWForm 字段结构,适用于组织集体申报。',
+ sections: [
+ {
+ title: '组织基本信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'orgName', label: '二级团组织全称', required: true },
+ { key: 'leader', label: '负责人' },
+ { key: 'phone', label: '联系电话', type: 'phone' },
+ { key: 'memberTotal', label: '现有团员总数', type: 'number' },
+ { key: 'memberDeveloped2024', label: '上一年度发展团员人数', type: 'number' },
+ { key: 'smartSystemLogin', label: '是否已登录智慧团建系统', type: 'select', options: YES_NO_OPTIONS },
+ { key: 'committeeCount', label: '团委委员人数', type: 'number' },
+ { key: 'fulltimeCadreCount', label: '专职团干部数(其中教师/学生)' },
+ { key: 'parttimeCadreCount', label: '兼职团干部数(其中教师/学生)' },
+ { key: 'lastElectionTime', label: '团委最近一次换届时间', type: 'date' },
+ { key: 'branchCount', label: '团支部数', type: 'number' }
+ ])
+ },
+ {
+ title: '年度建设情况',
+ fields: createFields([
+ { key: 'feeReceivable2024', label: '上一年度应收团费', type: 'number' },
+ { key: 'feeReceived2024', label: '上一年度实收团费', type: 'number' },
+ { key: 'feePayable2024', label: '上一年度应上缴团费', type: 'number' },
+ { key: 'feePaid2024', label: '上一年度实际上缴团费', type: 'number' },
+ { key: 'standardizedWork2024', label: '是否开展规范化建设工作', type: 'select', options: YES_NO_OPTIONS },
+ { key: 'recommendActivist2024', label: '推荐入党积极分子人数', type: 'number' },
+ { key: 'activistConfirmed', label: '确定为入党积极分子数', type: 'number' },
+ { key: 'recommendDevTarget2024', label: '推荐党的发展对象人数', type: 'number' },
+ { key: 'devTargetConfirmed', label: '确定为党的发展对象数', type: 'number' }
+ ])
+ },
+ {
+ title: '主要成果',
+ fields: createFields([
+ { key: 'honorsFiveYears', label: '近五年获得校级及以上荣誉情况', type: 'textarea' },
+ { key: 'workSummaryThreeYears', label: '近三年开展的主要工作及取得的效果', type: 'textarea' }
+ ])
+ }
+ ]
+ },
+ gxmu_wshqtzb: {
+ title: '五四红旗团支部申报表',
+ subtitle: '参考后台 WSHQTZBForm 字段结构,适用于基层团支部集体申报。',
+ sections: [
+ {
+ title: '组织基本信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'branchName', label: '团支部全称', required: true },
+ { key: 'secondOrg', label: '所属二级团组织' },
+ { key: 'secretary', label: '团支部书记' },
+ { key: 'politics', label: '政治面貌' },
+ { key: 'contact', label: '联系方式', type: 'phone' },
+ { key: 'establishTime', label: '成立时间', type: 'date' },
+ { key: 'lastElectionTime', label: '最近一次换届时间', type: 'date' },
+ { key: 'smartSystemLogin', label: '是否已登录智慧团建系统', type: 'select', options: YES_NO_OPTIONS },
+ { key: 'memberTotal', label: '现有团员总数', type: 'number' },
+ { key: 'memberDeveloped2024', label: '上一年度发展团员数', type: 'number' }
+ ])
+ },
+ {
+ title: '年度建设情况',
+ fields: createFields([
+ { key: 'feeReceivable2024', label: '上一年度应收团费', type: 'number' },
+ { key: 'feeReceived2024', label: '上一年度实收团费', type: 'number' },
+ { key: 'feePayable2024', label: '上一年度应上缴团费', type: 'number' },
+ { key: 'feePaid2024', label: '上一年度实际上缴团费', type: 'number' },
+ { key: 'recommendActivist2024', label: '推荐入党积极分子人数', type: 'number' },
+ { key: 'activistConfirmed', label: '确定为入党积极分子数', type: 'number' },
+ { key: 'recommendDevTarget2024', label: '推荐党的发展对象人数', type: 'number' },
+ { key: 'devTargetConfirmed', label: '确定为党的发展对象数', type: 'number' },
+ { key: 'branchCommitteeMeetingCount', label: '团支部委员会会议召开次数', type: 'number' },
+ { key: 'branchMemberMeetingCount', label: '团支部团员大会召开次数', type: 'number' },
+ { key: 'eduEvalDone', label: '是否开展团员教育评议', type: 'select', options: YES_NO_OPTIONS },
+ { key: 'annualRegDone', label: '是否开展团员年度团籍注册', type: 'select', options: YES_NO_OPTIONS },
+ { key: 'classCount', label: '开展团课次数', type: 'number' },
+ { key: 'smartSystem100', label: '是否100%录入智慧团建', type: 'select', options: YES_NO_OPTIONS }
+ ])
+ },
+ {
+ title: '主要成果',
+ fields: createFields([
+ { key: 'honorsFiveYears', label: '近五年获得院级及以上荣誉情况', type: 'textarea' },
+ { key: 'workSummaryThreeYears', label: '近三年开展的主要工作及取得的效果', type: 'textarea' }
+ ])
+ }
+ ]
+ },
+ gxmu_yxgqtdgb: {
+ title: '优秀共青团干部申报表',
+ subtitle: '参考后台 YXGQTDGBForm 字段结构,适用于团干部个人申报。',
+ sections: [
+ {
+ title: '基础信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'name', label: '姓名', required: true },
+ { key: 'gender', label: '性别', type: 'select', options: GENDER_OPTIONS },
+ { key: 'nation', label: '民族' },
+ { key: 'birthMonth', label: '出生年月', type: 'month' },
+ { key: 'politics', label: '政治面貌' },
+ { key: 'position', label: '职务' },
+ { key: 'identity', label: '身份' },
+ { key: 'organization', label: '所在团组织' },
+ { key: 'contact', label: '联系方式', type: 'phone' },
+ { key: 'memberNo', label: '发展团员编号' },
+ { key: 'currentDutyTime', label: '任现团内职务时间', type: 'month' },
+ { key: 'cadreYears', label: '担任团干部年限' },
+ { key: 'assessment2024', label: '上一年度工作考核结果' },
+ { key: 'volunteerRegTime', label: '成为注册志愿者时间', type: 'month' }
+ ])
+ },
+ {
+ title: '申报内容',
+ fields: createFields([
+ { key: 'cadreExperience', label: '从事团干部经历', type: 'textarea' },
+ { key: 'honorsFiveYears', label: '近五年获得校级及以上荣誉情况', type: 'textarea' },
+ { key: 'mainStory', label: '主要事迹', type: 'textarea' }
+ ])
+ }
+ ]
+ },
+ gxmu_yxgqty: {
+ title: '优秀共青团员申报表',
+ subtitle: '参考后台 YXGQTYForm 字段结构,适用于团员个人申报。',
+ sections: [
+ {
+ title: '基础信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'name', label: '姓名', required: true },
+ { key: 'gender', label: '性别', type: 'select', options: GENDER_OPTIONS },
+ { key: 'nation', label: '民族' },
+ { key: 'politics', label: '政治面貌' },
+ { key: 'birthMonth', label: '出生年月', type: 'month' },
+ { key: 'joinTime', label: '入团时间', type: 'month' },
+ { key: 'collegeMajorClass', label: '所在学院、专业、班级' },
+ { key: 'position', label: '职务' },
+ { key: 'volunteerRegTime', label: '成为注册志愿者时间', type: 'month' },
+ { key: 'eduEval2024', label: '上一年度团员教育评议等次' },
+ { key: 'smartSystem', label: '是否已登录智慧团建系统', type: 'select', options: YES_NO_OPTIONS },
+ { key: 'contact', label: '联系电话', type: 'phone' },
+ { key: 'totalVolunteerHours', label: '累计志愿服务时长' },
+ { key: 'volunteerHours2024', label: '上一年度志愿服务时长' },
+ { key: 'memberNo', label: '发展团员编号' }
+ ])
+ },
+ {
+ title: '申报内容',
+ fields: createFields([
+ { key: 'honorsFiveYears', label: '近五年获得荣誉情况', type: 'textarea' },
+ { key: 'mainStory', label: '主要事迹', type: 'textarea' }
+ ])
+ }
+ ]
+ },
+ gxmu_tzbcy_form: {
+ title: '挑战杯申报表',
+ subtitle: '面向挑战杯项目申报、团队信息和成果材料整理。',
+ sections: [
+ {
+ title: '项目信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'projectName', label: '项目名称', required: true },
+ { key: 'projectType', label: '项目类型', required: true },
+ { key: 'projectGroup', label: '项目分组', required: true },
+ { key: 'leaderName', label: '项目负责人', required: true },
+ { key: 'leaderCollege', label: '负责人学院' },
+ { key: 'leaderPhone', label: '负责人电话', type: 'phone' },
+ { key: 'guidanceTeacher', label: '指导老师' }
+ ])
+ },
+ {
+ title: '项目内容',
+ fields: createFields([
+ { key: 'projectSummary', label: '项目简介', type: 'textarea', required: true },
+ { key: 'innovationPoint', label: '创新亮点', type: 'textarea' },
+ { key: 'teamMembers', label: '团队成员', type: 'textarea' },
+ { key: 'honors', label: '已有成果与奖励', type: 'textarea' }
+ ])
+ }
+ ]
+ },
+ gxmu_qmgc_form: {
+ title: '青马工程申报表',
+ subtitle: '用于青马工程学员基础信息、培养情况和成果申报。',
+ sections: [
+ {
+ title: '基础信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'name', label: '姓名', required: true },
+ { key: 'gender', label: '性别', type: 'select', options: GENDER_OPTIONS },
+ { key: 'college', label: '学院', required: true },
+ { key: 'className', label: '班级' },
+ { key: 'phone', label: '联系电话', type: 'phone' },
+ { key: 'politics', label: '政治面貌' }
+ ])
+ },
+ {
+ title: '培养情况',
+ fields: createFields([
+ { key: 'trainingExperience', label: '培养经历', type: 'textarea', required: true },
+ { key: 'practiceExperience', label: '实践经历', type: 'textarea' },
+ { key: 'mainAchievement', label: '主要成果', type: 'textarea' },
+ { key: 'selfEvaluation', label: '个人总结', type: 'textarea' }
+ ])
+ }
+ ]
+ },
+ gxmu_wxxzx_project: {
+ title: '未来学术之星申报表',
+ subtitle: '用于未来学术之星项目申报、课题信息和支撑材料整理。',
+ sections: [
+ {
+ title: '项目基础信息',
+ fields: createFields([
+ { key: 'year', label: '年份', type: 'year', required: true },
+ { key: 'projectName', label: '项目名称', required: true },
+ { key: 'applicantName', label: '申请人', required: true },
+ { key: 'college', label: '学院', required: true },
+ { key: 'major', label: '专业' },
+ { key: 'phone', label: '联系电话', type: 'phone' },
+ { key: 'guidanceTeacher', label: '指导老师' }
+ ])
+ },
+ {
+ title: '申报内容',
+ fields: createFields([
+ { key: 'researchDirection', label: '研究方向', type: 'textarea' },
+ { key: 'projectBasis', label: '项目基础', type: 'textarea', required: true },
+ { key: 'researchPlan', label: '研究计划', type: 'textarea' },
+ { key: 'expectedResult', label: '预期成果', type: 'textarea' }
+ ])
+ }
+ ]
+ }
+}
+
+export const getModuleMeta = (code) => GXMU_MODULES.find((item) => item.code === code)
+
+export const getFormConfig = (code) => GXMU_FORM_MAP[code]
+
+export const createInitialFormData = (code) => {
+ const config = getFormConfig(code)
+ if (!config) {
+ return {}
+ }
+ return config.sections.reduce((result, section) => {
+ section.fields.forEach((field) => {
+ result[field.key] = field.defaultValue || ''
+ })
+ return result
+ }, {})
+}
diff --git a/utils/gxmu/declare-service.js b/utils/gxmu/declare-service.js
new file mode 100644
index 0000000..40c4eab
--- /dev/null
+++ b/utils/gxmu/declare-service.js
@@ -0,0 +1,10 @@
+import { API_BASE_URL } from '../../utils/request'
+import { apiRequest } from '../../pages/assistant/chat-service'
+
+export function listDeclare(params) {
+ return apiRequest({
+ url: `${API_BASE_URL}/gxmu/declare`,
+ method: 'GET',
+ data: params
+ })
+}
diff --git a/utils/gxmu/review-list-service.js b/utils/gxmu/review-list-service.js
new file mode 100644
index 0000000..cd22b47
--- /dev/null
+++ b/utils/gxmu/review-list-service.js
@@ -0,0 +1,439 @@
+import { API_BASE_URL } from '../request'
+import { apiRequest } from '../../pages/assistant/chat-service'
+
+function request(url, method = 'GET', data) {
+ return apiRequest({
+ url: `${API_BASE_URL}${url}`,
+ method,
+ data
+ })
+}
+
+export const REVIEW_AUTHORITY = 'gxmu:reviewList:list'
+
+export const MODULE_OPTIONS = [
+ { label: '全部模块', value: '' },
+ { label: '稿件', value: 'cms_manuscript' },
+ { label: '十佳青年岗位能手', value: 'gxmu_sjqn' },
+ { label: '十佳团支部书记', value: 'gxmu_sjtbzbsj' },
+ { label: '五四红旗团委', value: 'gxmu_wshqtw' },
+ { label: '五四红旗团支部', value: 'gxmu_wshqtzb' },
+ { label: '优秀共青团干部', value: 'gxmu_yxgqtdgb' },
+ { label: '优秀共青团员', value: 'gxmu_yxgqty' },
+ { label: '挑战杯', value: 'gxmu_tzbcy_form' },
+ { label: '青马工程', value: 'gxmu_qmgc_form' },
+ { label: '未来学术之星', value: 'gxmu_wxxzx_project' },
+ { label: '团员管理', value: 'gxmu_tygl_form' },
+ { label: '团员管理', value: 'gxmu_tygl' }
+]
+
+const COMMON_LABELS = {
+ year: '年份',
+ id: 'ID',
+ title: '标题',
+ content: '内容',
+ cover: '封面',
+ createTime: '创建时间',
+ updateTime: '更新时间',
+ status: '状态',
+ sortNumber: '排序',
+ provinceCity: '省市',
+ schoolName: '学校名称',
+ projectName: '项目名称',
+ projectType: '项目类型',
+ projectGroup: '项目分组',
+ publicProjectType: '公开展示项目类型',
+ publicProjectGroup: '公开展示项目分组',
+ leader: '负责人',
+ phone: '联系电话',
+ teamMembers: '团队成员',
+ advisors: '指导老师',
+ projectBrief: '项目简介',
+ socialValue: '社会价值',
+ practiceProcess: '实践过程',
+ innovationMeaning: '创新意义',
+ developmentProspect: '发展前景',
+ teamCooperation: '团队协作情况',
+ projectMaterials: '项目佐证材料',
+ otherProofs: '其他证明材料',
+ projectSummary: '项目总结',
+ teamIntro: '团队介绍',
+ teamSlogan: '团队口号',
+ practiceLog: '实践日志',
+ name: '姓名',
+ gender: '性别',
+ nation: '民族',
+ photo: '电子版照片',
+ birthMonth: '出生年月',
+ politics: '政治面貌',
+ nativePlace: '籍贯',
+ wechat: '微信号',
+ email: '邮箱',
+ qq: 'QQ',
+ idCardNo: '身份证号',
+ hobby: '兴趣爱好',
+ willingToWorkInGuangxi: '是否愿意留桂工作',
+ schoolInfo: '学校信息',
+ leaguePosition: '团内职务',
+ resume: '个人简历',
+ awards: '获得奖励',
+ academicPerformance: '学习成绩',
+ secondaryLeagueOpinion: '二级团组织意见',
+ secondaryLeagueOpinionDate: '二级团组织意见日期',
+ secondaryPartyOpinion: '二级党组织意见',
+ secondaryPartyOpinionDate: '二级党组织意见日期',
+ schoolLeagueOpinion: '学校团组织意见',
+ schoolLeagueOpinionDate: '学校团组织意见日期',
+ applyType: '申报类别',
+ education: '学历',
+ position: '职务',
+ unit: '工作单位',
+ experience: '主要经历',
+ mainStory: '主要事迹',
+ leagueOpinion: '团组织意见',
+ partyOpinion: '党组织意见',
+ schoolOpinion: '学校意见',
+ collegeClass: '学院班级',
+ branch: '团支部名称',
+ eduEval: '教育评议情况',
+ serialNo: '序号',
+ archiveInCurrentOrg: '团籍是否在本组织',
+ joinMonth: '入团时间',
+ memberRecords: '团员记录',
+ growthArchives: '成长档案',
+ orgName: '组织名称',
+ memberTotal: '团员总数',
+ memberDeveloped2024: '上一年度发展团员数',
+ smartSystemLogin: '智慧团建系统录入情况',
+ committeeCount: '团委委员数',
+ fulltimeCadreCount: '专职团干部数',
+ parttimeCadreCount: '兼职团干部数',
+ lastElectionTime: '最近一次换届时间',
+ feeReceivable2024: '上一年度应收团费',
+ feeReceived2024: '上一年度实收团费',
+ feePayable2024: '上一年度应缴团费',
+ feePaid2024: '上一年度实缴团费',
+ branchCount: '团支部数量',
+ standardizedWork2024: '上一年度标准化建设开展情况',
+ recommendActivist2024: '上一年度推优入党人数',
+ activistConfirmed: '入党积极分子确定情况',
+ recommendDevTarget2024: '上一年度推荐发展对象人数',
+ devTargetConfirmed: '发展对象确定情况',
+ honorsFiveYears: '近五年获奖情况',
+ workSummaryThreeYears: '近三年工作总结',
+ branchName: '团支部名称',
+ secondOrg: '所属二级党组织',
+ secretary: '团支部书记',
+ contact: '联系方式',
+ establishTime: '成立时间',
+ branchCommitteeMeetingCount: '支委会次数',
+ branchMemberMeetingCount: '支部大会次数',
+ eduEvalDone: '教育评议是否完成',
+ annualRegDone: '年度团籍注册是否完成',
+ classCount: '班级数量',
+ smartSystem100: '智慧团建系统覆盖率',
+ topicName: '课题名称',
+ collegeGradeClass: '学院年级班级',
+ advisor: '指导老师',
+ topicType: '课题类别',
+ guidanceTeachers: '指导教师组',
+ applicants: '申请人信息',
+ budgets: '经费预算',
+ rationale: '立项依据',
+ teacherOpinion: '指导教师意见',
+ teacherSign: '指导教师签名',
+ reviewOpinion: '评审意见',
+ reviewScore: '评审成绩',
+ reviewLeaderSign: '评审负责人签名',
+ collegeOpinion: '学院意见',
+ promiseSigner: '承诺签字人',
+ promiseSignature: '承诺书签名',
+ reviewCollege: '评审学院',
+ reviewReporter: '汇报人',
+ reviewDate: '评审日期',
+ reviewMembers: '评审成员',
+ fundingBudgetFile: '经费预算表',
+ fundingAllocationFile: '经费划拨明细表',
+ midtermRemark: '中期检查说明',
+ midtermMaterials: '中期检查材料',
+ finalRemark: '结题说明',
+ finalMaterials: '结题材料',
+ archiveAwards: '获奖情况',
+ archiveProofs: '佐证材料',
+ archiveRecords: '成果归档',
+ grade: '年级',
+ className: '班级',
+ major: '专业',
+ role: '角色',
+ item: '项目',
+ amount: '金额',
+ reason: '原因',
+ dept: '部门',
+ sign: '签名',
+ department: '部门',
+ remark: '备注',
+ type: '类型',
+ recordTime: '时间',
+ archiveTime: '时间',
+ identity: '身份',
+ organization: '所在组织',
+ memberNo: '团员编号',
+ currentDutyTime: '现任职务时间',
+ cadreYears: '从事团学工作年限',
+ assessment2024: '上一年度考核情况',
+ volunteerRegTime: '志愿汇注册时间',
+ cadreExperience: '团学工作经历',
+ joinTime: '入团时间',
+ collegeMajorClass: '学院专业班级',
+ eduEval2024: '上一年度教育评议情况',
+ smartSystem: '智慧团建系统录入情况',
+ totalVolunteerHours: '累计志愿服务时长',
+ volunteerHours2024: '上一年度志愿服务时长',
+ duty: '职务',
+ college: '学院',
+ gradeMajor: '年级专业',
+ birthDate: '出生日期',
+ branchOrganizationName: '所属团支部'
+}
+
+export const MODULE_CONFIGS = {
+ cms_manuscript: { title: '稿件', path: '/cms/manuscript', labels: COMMON_LABELS },
+ gxmu_sjqn: { title: '十佳青年岗位能手', path: '/gxmu/sjqn-form', labels: COMMON_LABELS },
+ gxmu_sjtbzbsj: { title: '十佳团支部书记', path: '/gxmu/sjtbzbsj-form', labels: COMMON_LABELS },
+ gxmu_wshqtw: { title: '五四红旗团委', path: '/gxmu/wshqtw-form', labels: COMMON_LABELS },
+ gxmu_wshqtzb: { title: '五四红旗团支部', path: '/gxmu/wshqtzb-form', labels: COMMON_LABELS },
+ gxmu_yxgqtdgb: { title: '优秀共青团干部', path: '/gxmu/yxgqtdgb-form', labels: COMMON_LABELS },
+ gxmu_yxgqty: { title: '优秀共青团员', path: '/gxmu/yxgqty-form', labels: COMMON_LABELS },
+ gxmu_tzbcy_form: { title: '挑战杯', path: '/gxmu/tzbcy-form', labels: COMMON_LABELS },
+ gxmu_qmgc_form: { title: '青马工程', path: '/gxmu/qmgc-form', labels: COMMON_LABELS },
+ gxmu_wxxzx_project: { title: '未来学术之星', path: '/gxmu/wxxzx-form', labels: { ...COMMON_LABELS, title: '职称' } },
+ gxmu_tygl_form: { title: '团员管理', path: '/gxmu/tygl-form', labels: COMMON_LABELS },
+ gxmu_tygl: { title: '团员管理', path: '/gxmu/tygl-form', labels: COMMON_LABELS }
+}
+
+export const HIDDEN_FIELDS = new Set([
+ 'reviewList',
+ 'deleted',
+ 'tenantId',
+ 'userId',
+ 'branchOrganizationId'
+])
+
+export const IMAGE_FIELDS = new Set(['photo', 'cover', 'promiseSignature'])
+export const FILE_FIELDS = new Set([
+ 'fundingBudgetFile',
+ 'fundingAllocationFile',
+ 'midtermMaterials',
+ 'finalMaterials',
+ 'archiveProofs'
+])
+
+export function pageGroupReviewList(params) {
+ return request('/gxmu/review-list/groupPage', 'GET', {
+ ...(params || {}),
+ backendAccess: true
+ })
+}
+
+export function listReviewList(params) {
+ return request('/gxmu/review-list', 'GET', params)
+}
+
+export function updateReviewList(data) {
+ return request('/gxmu/review-list', 'PUT', data)
+}
+
+export function getModuleDetail(module, id) {
+ const config = MODULE_CONFIGS[module]
+ if (!config || !id) {
+ return Promise.resolve(null)
+ }
+ return request(`${config.path}/${id}`, 'GET')
+}
+
+export function formatDateTime(value) {
+ if (!value) {
+ return '-'
+ }
+ const text = String(value).trim()
+ const normalizedText = text.replace('T', ' ')
+ if (/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}/.test(normalizedText)) {
+ return normalizedText.slice(0, 19)
+ }
+ const timestamp = typeof value === 'number' || /^\d+$/.test(text) ? Number(value) : null
+ const date = timestamp
+ ? new Date(String(timestamp).length === 10 ? timestamp * 1000 : timestamp)
+ : new Date(text.includes('T') ? text : text.replace(/-/g, '/'))
+ if (Number.isNaN(date.getTime())) {
+ return value
+ }
+ const pad = (item) => String(item).padStart(2, '0')
+ return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`
+}
+
+export function getModuleName(module) {
+ const option = MODULE_OPTIONS.find((item) => item.value === module)
+ return (option && option.label) || (MODULE_CONFIGS[module] && MODULE_CONFIGS[module].title) || module || '-'
+}
+
+export function getReviewStatusMeta(status) {
+ if (Number(status) === 1) {
+ return { text: '通过', tone: 'success' }
+ }
+ if (Number(status) === 2) {
+ return { text: '拒绝', tone: 'danger' }
+ }
+ return { text: '待审核', tone: 'warning' }
+}
+
+export function getAuthorityValue(item) {
+ if (!item) {
+ return ''
+ }
+ if (typeof item === 'string') {
+ return item
+ }
+ return item.authority || item.permission || item.code || ''
+}
+
+export function hasReviewListAuthority(profile) {
+ const authorities = Array.isArray(profile && profile.authorities) ? profile.authorities : []
+ return authorities.some((item) => getAuthorityValue(item) === REVIEW_AUTHORITY)
+}
+
+export function isPlainObject(value) {
+ return Object.prototype.toString.call(value) === '[object Object]'
+}
+
+export function hasVisibleValue(value) {
+ if (value === null || value === undefined) {
+ return false
+ }
+ if (typeof value === 'string') {
+ return value.trim().length > 0
+ }
+ if (Array.isArray(value)) {
+ return value.length > 0
+ }
+ if (isPlainObject(value)) {
+ return Object.values(value).some((item) => hasVisibleValue(item))
+ }
+ return true
+}
+
+export function getFieldLabel(module, key) {
+ const config = MODULE_CONFIGS[module]
+ const label = config && config.labels && config.labels[key]
+ if (label) {
+ return label
+ }
+ return String(key || '')
+ .replace(/([A-Z])/g, ' $1')
+ .replace(/^./, (text) => text.toUpperCase())
+ .trim()
+}
+
+export function normalizeDetailValue(key, value) {
+ if (!FILE_FIELDS.has(key) || typeof value !== 'string') {
+ return value
+ }
+ const text = value.trim()
+ if (!text) {
+ return value
+ }
+ if (key === 'fundingBudgetFile' || key === 'fundingAllocationFile') {
+ return text
+ }
+ try {
+ const parsed = JSON.parse(text)
+ return Array.isArray(parsed) ? parsed : text
+ } catch (error) {
+ return text
+ }
+}
+
+export function formatSimpleValue(value) {
+ if (value === null || value === undefined || value === '') {
+ return '-'
+ }
+ return String(value)
+}
+
+export function formatDisplayValue(module, key, value) {
+ if (key === 'createTime' || key === 'updateTime') {
+ return formatDateTime(value)
+ }
+ if (key === 'status' && module === 'cms_manuscript') {
+ if (Number(value) === 0) {
+ return '待审核'
+ }
+ if (Number(value) === 1) {
+ return '显示'
+ }
+ }
+ return formatSimpleValue(value)
+}
+
+export function getFileName(url) {
+ if (!url) {
+ return '未命名文件'
+ }
+ const cleanUrl = String(url).split('?')[0]
+ return decodeURIComponent(cleanUrl.split('/').pop() || cleanUrl)
+}
+
+export function normalizeFileEntries(value) {
+ if (Array.isArray(value)) {
+ return value
+ .map((item) => {
+ if (typeof item === 'string' && item.trim()) {
+ return { name: getFileName(item), url: item }
+ }
+ if (isPlainObject(item) && typeof item.url === 'string' && item.url.trim()) {
+ return {
+ name: (typeof item.name === 'string' && item.name.trim()) || getFileName(item.url),
+ url: item.url
+ }
+ }
+ return null
+ })
+ .filter(Boolean)
+ }
+ if (typeof value === 'string' && value.trim()) {
+ return [{ name: getFileName(value), url: value }]
+ }
+ return []
+}
+
+export function formatObjectLines(module, value) {
+ return Object.keys(value || {})
+ .filter((key) => !HIDDEN_FIELDS.has(key) && hasVisibleValue(value[key]))
+ .map((key) => `${getFieldLabel(module, key)}:${formatDisplayValue(module, key, value[key])}`)
+}
+
+export function formatArrayEntry(module, entry) {
+ if (isPlainObject(entry)) {
+ const lines = formatObjectLines(module, entry)
+ return lines.length ? lines : ['-']
+ }
+ return [formatSimpleValue(entry)]
+}
+
+export function buildDescriptionItems(module, detail) {
+ const source = detail || {}
+ const labels = (MODULE_CONFIGS[module] && MODULE_CONFIGS[module].labels) || {}
+ const orderedKeys = Object.keys(labels).filter((key) => (
+ !HIDDEN_FIELDS.has(key) && hasVisibleValue(source[key])
+ ))
+ const extraKeys = Object.keys(source).filter((key) => (
+ !HIDDEN_FIELDS.has(key) &&
+ hasVisibleValue(source[key]) &&
+ !orderedKeys.includes(key)
+ ))
+
+ return [...orderedKeys, ...extraKeys].map((key) => ({
+ key,
+ label: getFieldLabel(module, key),
+ value: normalizeDetailValue(key, source[key])
+ }))
+}
diff --git a/utils/markdown.js b/utils/markdown.js
new file mode 100644
index 0000000..90dc7b2
--- /dev/null
+++ b/utils/markdown.js
@@ -0,0 +1,125 @@
+/**
+ * Lightweight markdown to HTML converter for uni-app rich-text component.
+ * Supports: headings, bold, italic, inline code, code blocks,
+ * unordered lists, ordered lists, blockquotes, horizontal rules, line breaks.
+ */
+
+function escapeHtml(str) {
+ return str
+ .replace(/&/g, '&')
+ .replace(//g, '>')
+ .replace(/"/g, '"')
+}
+
+/**
+ * Convert markdown string to HTML string.
+ * @param {string} text
+ * @returns {string}
+ */
+export function renderMarkdown(text) {
+ if (!text) return ''
+
+ // 1. Protect code blocks first (```)
+ const codeBlocks = []
+ let html = text.replace(/```([\w]*)\n?([\s\S]*?)```/g, (_, lang, code) => {
+ const idx = codeBlocks.length
+ const escaped = escapeHtml(code.replace(/^\n/, '').replace(/\n$/, ''))
+ const langAttr = lang ? ` class="language-${lang}"` : ''
+ codeBlocks.push(`${escaped}
`)
+ return `\x00CODE${idx}\x00`
+ })
+
+ // 2. Escape remaining HTML to prevent injection
+ html = html.replace(/&/g, '&').replace(//g, '>')
+
+ // 3. Protect inline code (`)
+ const inlineCodes = []
+ html = html.replace(/`([^`\n]+)`/g, (_, code) => {
+ const idx = inlineCodes.length
+ inlineCodes.push(`${escapeHtml(code)}`)
+ return `\x00INLINE${idx}\x00`
+ })
+
+ // 4. Headings (must be at line start)
+ html = html.replace(/^#{4,6} (.+)$/gm, '$1
')
+ html = html.replace(/^### (.+)$/gm, '$1
')
+ html = html.replace(/^## (.+)$/gm, '$1
')
+ html = html.replace(/^# (.+)$/gm, '$1
')
+
+ // 5. Horizontal rules
+ html = html.replace(/^[ \t]*(?:---+|===+|\*\*\*+)[ \t]*$/gm, '
')
+
+ // 6. Blockquotes
+ html = html.replace(/^> ?(.*)$/gm, '$1
')
+
+ // 7. Bold + Italic
+ html = html.replace(/\*\*\*(.+?)\*\*\*/g, '$1')
+ html = html.replace(/___(.+?)___/g, '$1')
+
+ // 8. Bold
+ html = html.replace(/\*\*(.+?)\*\*/g, '$1')
+ html = html.replace(/__(.+?)__/g, '$1')
+
+ // 9. Italic
+ html = html.replace(/\*(.+?)\*/g, '$1')
+ html = html.replace(/_(.+?)_/g, '$1')
+
+ // 10. Lists — collect consecutive list lines into or
+ const lines = html.split('\n')
+ const result = []
+ let inUl = false
+ let inOl = false
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i]
+ const ulMatch = line.match(/^[ \t]*[-*+] (.+)$/)
+ const olMatch = line.match(/^[ \t]*\d+\. (.+)$/)
+
+ if (ulMatch) {
+ if (!inUl) {
+ if (inOl) { result.push('
'); inOl = false }
+ result.push('')
+ inUl = true
+ }
+ result.push(`- ${ulMatch[1]}
`)
+ } else if (olMatch) {
+ if (!inOl) {
+ if (inUl) { result.push('
'); inUl = false }
+ result.push('')
+ inOl = true
+ }
+ result.push(`- ${olMatch[1]}
`)
+ } else {
+ if (inUl) { result.push('
'); inUl = false }
+ if (inOl) { result.push(''); inOl = false }
+ result.push(line)
+ }
+ }
+ if (inUl) result.push('')
+ if (inOl) result.push('')
+
+ html = result.join('\n')
+
+ // 11. Paragraphs — split by blank lines, wrap plain text blocks in
+ const blocks = html.split(/\n{2,}/)
+ html = blocks.map(block => {
+ block = block.trim()
+ if (!block) return ''
+ // Already wrapped in a block-level tag — leave as-is
+ if (/^<(h[1-6]|ul|ol|li|pre|blockquote|hr)[\s/>]/.test(block)) return block
+ // Has multiple lines → join with
+ block = block.replace(/\n/g, '
')
+ return `
${block}
`
+ }).join('\n')
+
+ // 12. Restore inline codes and code blocks
+ inlineCodes.forEach((code, idx) => {
+ html = html.replace(`\x00INLINE${idx}\x00`, code)
+ })
+ codeBlocks.forEach((code, idx) => {
+ html = html.replace(`\x00CODE${idx}\x00`, code)
+ })
+
+ return html
+}
diff --git a/utils/request.js b/utils/request.js
new file mode 100644
index 0000000..6ac71a0
--- /dev/null
+++ b/utils/request.js
@@ -0,0 +1,139 @@
+export const API_BASE_URL = (() => {
+ if (process.env.NODE_ENV === 'development') {
+ return 'http://localhost:10051/api'
+ }
+ return 'https://tuanwei-api.gxwebsoft.com/api'
+})()
+
+export const TOKEN_STORAGE_KEYS = ['access_token', 'Authorization', 'uni_id_token', 'token']
+export const DEFAULT_TENANT_ID = 10049
+export const TOKEN_HEADER_NAME = 'Authorization'
+export const LOGIN_PAGE_URL = '/pages/login/index'
+
+export function getToken() {
+ for (let index = 0; index < TOKEN_STORAGE_KEYS.length; index += 1) {
+ const value = uni.getStorageSync(TOKEN_STORAGE_KEYS[index])
+ if (value) {
+ return value
+ }
+ }
+ return ''
+}
+
+export function hasToken() {
+ return !!getToken()
+}
+
+export function clearAuthStorage() {
+ TOKEN_STORAGE_KEYS.forEach((key) => {
+ try {
+ uni.removeStorageSync(key)
+ } catch (error) {}
+ })
+ try {
+ uni.removeStorageSync('user_info')
+ } catch (error) {}
+}
+
+export function setAuthInfo(loginResult) {
+ const result = loginResult || {}
+ const token = result.access_token || result.accessToken || result.token || ''
+ const user = result.user || {}
+ if (token) {
+ uni.setStorageSync('access_token', token)
+ uni.setStorageSync('Authorization', token)
+ uni.setStorageSync('token', token)
+ }
+ uni.setStorageSync('user_info', user)
+ return {
+ token,
+ user
+ }
+}
+
+export function getStoredUserInfo() {
+ return uni.getStorageSync('user_info') || {}
+}
+
+export function redirectToLogin(redirectUrl) {
+ const query = redirectUrl ? `?redirect=${encodeURIComponent(redirectUrl)}` : ''
+ uni.reLaunch({
+ url: `${LOGIN_PAGE_URL}${query}`
+ })
+}
+
+export function ensureLoggedIn(options = {}) {
+ const { redirectUrl } = options
+ if (hasToken()) {
+ return true
+ }
+ redirectToLogin(redirectUrl)
+ return false
+}
+
+export function request({ url, method = 'GET', data, header = {}, auth = true }) {
+ const token = getToken()
+ return new Promise((resolve, reject) => {
+ uni.request({
+ url: /^https?:\/\//.test(url) ? url : `${API_BASE_URL}${url}`,
+ method,
+ data,
+ header: {
+ 'Content-Type': 'application/json',
+ ...(auth && token ? { [TOKEN_HEADER_NAME]: token } : {}),
+ ...header
+ },
+ success: (response) => {
+ const responseHeader = (response && response.header) || {}
+ const nextToken =
+ responseHeader[TOKEN_HEADER_NAME] ||
+ responseHeader[TOKEN_HEADER_NAME.toLowerCase()]
+ if (nextToken) {
+ uni.setStorageSync('access_token', nextToken)
+ uni.setStorageSync('Authorization', nextToken)
+ uni.setStorageSync('token', nextToken)
+ }
+ const body = (response && response.data) || {}
+ const code = Number(body.code)
+ if (code === 0 || code === 200) {
+ resolve(body.data !== undefined ? body.data : body)
+ return
+ }
+ if (code === 401) {
+ clearAuthStorage()
+ }
+ reject(new Error(body.message || '请求失败'))
+ },
+ fail: (error) => {
+ reject(new Error((error && error.errMsg) || '网络请求失败'))
+ }
+ })
+ })
+}
+
+export function get(url, data, options = {}) {
+ return request({
+ url,
+ method: 'GET',
+ data,
+ ...options
+ })
+}
+
+export function post(url, data, options = {}) {
+ return request({
+ url,
+ method: 'POST',
+ data,
+ ...options
+ })
+}
+
+export function put(url, data, options = {}) {
+ return request({
+ url,
+ method: 'PUT',
+ data,
+ ...options
+ })
+}