xhj 2 rokov pred
rodič
commit
2b42d240c3

+ 1 - 1
card/create/create.vue

@@ -20,7 +20,7 @@
 			
 			<view class="li ddflex">
 				<view class="label">个人简介</view>
-				<input v-if="!brief" @tap="jumpUrl('/card/editDesc/editDesc')" :disabled="true"  maxlength="11" type="number" placeholder="请作简单自我介绍" placeholder-class="placeholder" class="ipt flex" />
+				<input v-if="!brief" @tap="jumpUrl('/card/editDesc/editDesc')" maxlength="11" type="number" placeholder="请作简单自我介绍" placeholder-class="placeholder" class="ipt flex" />
 				<view class="fflex" v-else style="color: #47C776;" @tap="jumpUrl('/card/editDesc/editDesc')">已完善</view>
 				<image src="../../static/images/rico.png" class="rico"></image>
 			</view>

+ 409 - 0
components/mp-html/components/mp-html/mp-html.vue

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

+ 515 - 0
components/mp-html/components/mp-html/node/node.vue

@@ -0,0 +1,515 @@
+<template>
+	<view :id="attrs.id" :class="'_'+name+' '+attrs.class" :style="attrs.style">
+		<block v-for="(n, i) in childs" v-bind:key="i">
+			<!-- 图片 -->
+			<!-- 占位图 -->
+			<image v-if="n.name=='img'&&((opts[1]&&!ctrl[i])||ctrl[i]<0)" class="_img" :style="n.attrs.style"
+				:src="ctrl[i]<0?opts[2]:opts[1]" mode="widthFix" />
+			<!-- 显示图片 -->
+			<!-- #ifdef H5 || APP-PLUS -->
+			<img v-if="n.name=='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class"
+				:style="(ctrl[i]==-1?'display:none;':'')+n.attrs.style"
+				:src="n.attrs.src||(ctrl.load?n.attrs['data-src']:'')" :data-i="i" @load="imgLoad" @error="mediaError"
+				@tap.stop="imgTap" @longpress="imgLongTap" />
+			<!-- #endif -->
+			<!-- #ifndef H5 || APP-PLUS -->
+			<image v-if="n.name=='img'" :id="n.attrs.id" :class="'_img '+n.attrs.class"
+				:style="(ctrl[i]==-1?'display:none;':'')+'width:'+(ctrl[i]||1)+'px;height:1px;'+n.attrs.style"
+				:src="n.attrs.src" :mode="n.h?'':'widthFix'" :lazy-load="opts[0]" :webp="n.webp"
+				:show-menu-by-longpress="opts[3]&&!n.attrs.ignore" :image-menu-prevent="!opts[3]||n.attrs.ignore"
+				:data-i="i" @load="imgLoad" @error="mediaError" @tap.stop="imgTap" @longpress="imgLongTap" />
+			<!-- #endif -->
+			<!-- 文本 -->
+			<!-- #ifndef MP-BAIDU -->
+			<text v-else-if="n.type=='text'" decode>{{n.text}}</text>
+			<!-- #endif -->
+			<text v-else-if="n.name=='br'">\n</text>
+			<!-- 链接 -->
+			<view v-else-if="n.name=='a'" :id="n.attrs.id" :class="(n.attrs.href?'_a ':'')+n.attrs.class"
+				hover-class="_hover" :style="'display:inline;'+n.attrs.style" :data-i="i" @click.capture.stop="linkTap">
+				<!-- #ifdef MP-ALIPAY || MP-TOUTIAO -->
+				<rich-text :nodes="n.children" style="display:inline" />
+				<!-- #endif -->
+				<!-- #ifndef MP-ALIPAY || MP-TOUTIAO -->
+				<node name="span" :childs="n.children" :opts="opts" />
+				<!-- #endif -->
+			</view>
+			<!-- 视频 -->
+			<!-- #ifdef APP-PLUS -->
+			<view v-else-if="n.html" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style" v-html="n.html" />
+			<!-- #endif -->
+			<!-- #ifndef APP-PLUS -->
+			<video v-else-if="n.name=='video'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style"
+				:autoplay="n.attrs.autoplay" :controls="n.attrs.controls" :loop="n.attrs.loop" :muted="n.attrs.muted"
+				:poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+			<!-- #endif -->
+			<!-- #ifdef H5 || APP-PLUS -->
+			<iframe v-else-if="n.name=='iframe'" :style="n.attrs.style" :allowfullscreen="n.attrs.allowfullscreen"
+				:frameborder="n.attrs.frameborder" :src="n.attrs.src" />
+			<embed v-else-if="n.name=='embed'" :style="n.attrs.style" :src="n.attrs.src" />
+			<!-- #endif -->
+			<!-- #ifndef MP-TOUTIAO -->
+			<!-- 音频 -->
+			<audio v-else-if="n.name=='audio'" :id="n.attrs.id" :class="n.attrs.class" :style="n.attrs.style"
+				:author="n.attrs.author" :controls="n.attrs.controls" :loop="n.attrs.loop" :name="n.attrs.name"
+				:poster="n.attrs.poster" :src="n.src[ctrl[i]||0]" :data-i="i" @play="play" @error="mediaError" />
+			<!-- #endif -->
+			<view v-else-if="(n.name=='table'&&n.c)||n.name=='li'" :id="n.attrs.id"
+				:class="'_'+n.name+' '+n.attrs.class" :style="n.attrs.style">
+				<node v-if="n.name=='li'" :childs="n.children" :opts="opts" />
+				<view v-else v-for="(tbody, x) in n.children" v-bind:key="x"
+					:class="'_'+tbody.name+' '+tbody.attrs.class" :style="tbody.attrs.style">
+					<node v-if="tbody.name=='td'||tbody.name=='th'" :childs="tbody.children" :opts="opts" />
+					<block v-else v-for="(tr, y) in tbody.children" v-bind:key="y">
+						<view v-if="tr.name=='td'||tr.name=='th'" :class="'_'+tr.name+' '+tr.attrs.class"
+							:style="tr.attrs.style">
+							<node :childs="tr.children" :opts="opts" />
+						</view>
+						<view v-else :class="'_'+tr.name+' '+tr.attrs.class" :style="tr.attrs.style">
+							<view v-for="(td, z) in tr.children" v-bind:key="z" :class="'_'+td.name+' '+td.attrs.class"
+								:style="td.attrs.style">
+								<node :childs="td.children" :opts="opts" />
+							</view>
+						</view>
+					</block>
+				</view>
+			</view>
+
+			<!-- 富文本 -->
+			<!-- #ifdef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->
+			<rich-text id="richext" v-else-if="handler.use(n)" :id="n.attrs.id" :style="n.f" :nodes="[n]" selectable="true" />
+			<!-- #endif -->
+			<!-- #ifndef H5 || MP-WEIXIN || MP-QQ || APP-PLUS || MP-360 -->
+			<rich-text v-else-if="!n.c" :id="n.attrs.id" :style="n.f+';display:inline'" :preview="false" :nodes="[n]" />
+			<!-- #endif -->
+			<!-- 继续递归 -->
+			<view v-else-if="n.c==2" :id="n.attrs.id" :class="'_'+n.name+' '+n.attrs.class"
+				:style="n.f+';'+n.attrs.style">
+				<node v-for="(n2, j) in n.children" v-bind:key="j" :style="n2.f" :name="n2.name" :attrs="n2.attrs"
+					:childs="n2.children" :opts="opts" />
+			</view>
+			<node v-else :style="n.f" :name="n.name" :attrs="n.attrs" :childs="n.children" :opts="opts" />
+		</block>
+	</view>
+</template>
+<script module="handler" lang="wxs">
+	// 行内标签列表
+	var inlineTags = {
+		abbr: true,
+		b: true,
+		big: true,
+		code: true,
+		del: true,
+		em: true,
+		i: true,
+		ins: true,
+		label: true,
+		q: true,
+		small: true,
+		span: true,
+		strong: true,
+		sub: true,
+		sup: true
+	}
+	/**
+	 * @description 是否使用 rich-text 显示剩余内容
+	 */
+	module.exports = {
+		use: function(item) {
+			// 微信和 QQ 的 rich-text inline 布局无效
+			if (inlineTags[item.name] || (item.attrs.style || '').indexOf('display:inline') != -1)
+				return false
+			return !item.c
+		}
+	}
+</script>
+<script>
+	import node from './node'
+	export default {
+		name: 'node',
+		// #ifdef MP-WEIXIN
+		options: {
+			virtualHost: true
+		},
+		// #endif
+		data() {
+			return {
+				ctrl: {}
+			}
+		},
+		props: {
+			name: String,
+			attrs: {
+				type: Object,
+				default () {
+					return {}
+				}
+			},
+			childs: Array,
+			opts: Array
+		},
+		components: {
+			node
+		},
+		mounted() {
+			for (this.root = this.$parent; this.root.$options.name != 'mp-html'; this.root = this.root.$parent);
+			// #ifdef H5 || APP-PLUS
+			if (this.opts[0]) {
+				for (var i = this.childs.length; i--;)
+					if (this.childs[i].name == 'img')
+						break
+				if (i != -1) {
+					this.observer = uni.createIntersectionObserver(this).relativeToViewport({
+						top: 500,
+						bottom: 500
+					})
+					this.observer.observe('._img', res => {
+						if (res.intersectionRatio) {
+							this.$set(this.ctrl, 'load', 1)
+							this.observer.disconnect()
+						}
+					})
+				}
+			}
+			// #endif
+		},
+		beforeDestroy() {
+			// #ifdef H5 || APP-PLUS
+			if (this.observer)
+				this.observer.disconnect()
+			// #endif
+		},
+		methods: {
+			// #ifdef MP-WEIXIN
+			toJSON() {},
+			// #endif
+			/**
+			 * @description 播放视频事件
+			 * @param {Event} e 
+			 */
+			play(e) {
+				// #ifndef APP-PLUS
+				if (this.root.pauseVideo) {
+					var flag = false,
+						id = e.target.id
+					for (var i = this.root._videos.length; i--;) {
+						if (this.root._videos[i].id == id)
+							flag = true
+						else
+							this.root._videos[i].pause() // 自动暂停其他视频
+					}
+					// 将自己加入列表
+					if (!flag) {
+						var ctx = uni.createVideoContext(id
+							// #ifndef MP-BAIDU
+							, this
+							// #endif
+						)
+						ctx.id = id
+						this.root._videos.push(ctx)
+					}
+				}
+				// #endif
+			},
+
+			/**
+			 * @description 图片点击事件
+			 * @param {Event} e 
+			 */
+			imgTap(e) {
+				var attrs = this.childs[e.currentTarget.dataset.i].attrs
+				if (attrs.ignore)
+					return
+				attrs.src = attrs['data-src'] || attrs.src
+				this.root.$emit('imgtap', attrs)
+				// 自动预览图片
+				if (this.root.previewImg)
+					uni.previewImage({
+						current: parseInt(attrs.i),
+						urls: this.root.imgList
+					})
+			},
+
+			/**
+			 * @description 图片长按
+			 */
+			imgLongTap() {
+				// #ifdef APP-PLUS
+				var attrs = this.childs[e.currentTarget.dataset.i].attrs
+				if (!attrs.ignore)
+					uni.showActionSheet({
+						itemList: ['保存图片'],
+						success: () => {
+							uni.downloadFile({
+								url: this.root.imgList[attrs.i],
+								success: res => {
+									uni.saveImageToPhotosAlbum({
+										filePath: res.tempFilePath,
+										success() {
+											uni.showToast({
+												title: '保存成功'
+											})
+										}
+									})
+								}
+							})
+						}
+					})
+				// #endif
+			},
+
+			/**
+			 * @description 图片加载完成事件
+			 * @param {Event} e 
+			 */
+			imgLoad(e) {
+				var i = e.currentTarget.dataset.i
+				// #ifndef H5 || APP-PLUS
+				// 设置原宽度
+				if (!this.childs[i].w)
+					this.$set(this.ctrl, i, e.detail.width)
+				else
+					// #endif
+					// 加载完毕,取消加载中占位图
+					if ((this.opts[1] && !this.ctrl[i]) || this.ctrl[i] == -1)
+						this.$set(this.ctrl, i, 1)
+			},
+
+			/**
+			 * @description 链接点击事件
+			 * @param {Event} e 
+			 */
+			linkTap(e) {
+				var attrs = this.childs[e.currentTarget.dataset.i].attrs,
+					href = attrs.href
+				this.root.$emit('linktap', attrs)
+				if (href) {
+					// 跳转锚点
+					if (href[0] == '#')
+						this.root.navigateTo(href.substring(1)).catch(() => {})
+					// 复制外部链接
+					else if (href.includes('://')) {
+						if (this.root.copyLink) {
+							// #ifdef H5
+							window.open(href)
+							// #endif
+							// #ifdef MP
+							// uni.setClipboardData({
+							// 	data: href,
+							// 	success: () =>
+							// 		uni.showToast({
+							// 			title: '链接已复制'
+							// 		})
+							// })
+							uni.navigateTo({
+								url:'/pages/web/web?url='+href
+							})
+							// #endif
+							// #ifdef APP-PLUS
+							plus.runtime.openWeb(href)
+							// #endif
+						}
+					}
+					// 跳转页面
+					else
+						uni.navigateTo({
+							url: href,
+							fail() {
+								uni.switchTab({
+									url: href,
+									fail() {}
+								})
+							}
+						})
+				}
+			},
+
+			/**
+			 * @description 错误事件
+			 * @param {Event} e 
+			 */
+			mediaError(e) {
+				var i = e.currentTarget.dataset.i,
+					node = this.childs[i]
+				// 加载其他源
+				if (node.name == 'video' || node.name == 'audio') {
+					var index = (this.ctrl[i] || 0) + 1
+					if (index > node.src.length)
+						index = 0
+					if (index < node.src.length)
+						return this.$set(this.ctrl, i, index)
+				}
+				// 显示错误占位图
+				else if (node.name == 'img' && this.opts[2])
+					this.$set(this.ctrl, i, -1)
+				if (this.root)
+					this.root.$emit('error', {
+						source: node.name,
+						attrs: node.attrs,
+						errMsg: e.detail.errMsg
+					})
+			}
+		}
+	}
+</script>
+<style>
+	/* a 标签默认效果 */
+	._a {
+		padding: 1.5px 0 1.5px 0;
+		color: #366092;
+		word-break: break-all;
+	}
+
+	/* a 标签点击态效果 */
+	._hover {
+		text-decoration: underline;
+		opacity: 0.7;
+	}
+
+	/* 图片默认效果 */
+	._img {
+		max-width: 100%;
+		-webkit-touch-callout: none;
+	}
+
+	/* 内部样式 */
+
+	._b,
+	._strong {
+		font-weight: bold;
+	}
+
+	._code {
+		font-family: monospace;
+	}
+
+	._del {
+		text-decoration: line-through;
+	}
+
+	._em,
+	._i {
+		font-style: italic;
+	}
+
+	._h1 {
+		font-size: 2em;
+	}
+
+	._h2 {
+		font-size: 1.5em;
+	}
+
+	._h3 {
+		font-size: 1.17em;
+	}
+
+	._h5 {
+		font-size: 0.83em;
+	}
+
+	._h6 {
+		font-size: 0.67em;
+	}
+
+	._h1,
+	._h2,
+	._h3,
+	._h4,
+	._h5,
+	._h6 {
+		display: block;
+		font-weight: bold;
+	}
+
+	._image {
+		height: 1px;
+	}
+
+	._ins {
+		text-decoration: underline;
+	}
+
+	._li {
+		display: list-item;
+	}
+
+	._ol {
+		list-style-type: decimal;
+	}
+
+	._ol,
+	._ul {
+		display: block;
+		padding-left: 40px;
+		margin: 1em 0;
+	}
+
+	._q::before {
+		content: '"';
+	}
+
+	._q::after {
+		content: '"';
+	}
+
+	._sub {
+		font-size: smaller;
+		vertical-align: sub;
+	}
+
+	._sup {
+		font-size: smaller;
+		vertical-align: super;
+	}
+
+	._thead,
+	._tbody,
+	._tfoot {
+		display: table-row-group;
+	}
+
+	._tr {
+		display: table-row;
+	}
+
+	._td,
+	._th {
+		display: table-cell;
+		vertical-align: middle;
+	}
+
+	._th {
+		font-weight: bold;
+		text-align: center;
+	}
+
+	._ul {
+		list-style-type: disc;
+	}
+
+	._ul ._ul {
+		margin: 0;
+		list-style-type: circle;
+	}
+
+	._ul ._ul ._ul {
+		list-style-type: square;
+	}
+
+	._abbr,
+	._b,
+	._code,
+	._del,
+	._em,
+	._i,
+	._ins,
+	._label,
+	._q,
+	._span,
+	._strong,
+	._sub,
+	._sup {
+		display: inline;
+	}
+</style>

+ 1095 - 0
components/mp-html/components/mp-html/parser.js

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

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
components/mp-html/static/app-plus/mp-html/js/handler.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 0
components/mp-html/static/app-plus/mp-html/js/uni.webview.min.js


+ 1 - 0
components/mp-html/static/app-plus/mp-html/local.html

@@ -0,0 +1 @@
+<head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no"><style>body,html{width:100%;height:100%;overflow:hidden}body{margin:0}video{max-width:100%}img{max-width:100%;-webkit-touch-callout:none}@keyframes show{0%{opacity:0}100%{opacity:1}}</style></head><body><div id="content"></div><script type="text/javascript" src="./js/uni.webview.min.js"></script><script type="text/javascript" src="./js/handler.js"></script></body>

+ 1 - 1
library/longPoster/index.vue

@@ -138,7 +138,7 @@
 				if(item.cateCode=='nianduzhangdan'){
 					this.jumpUrl('/library/longPosterService/longPosterService?id='+item.id)
 				}
-				if(item.cateCode=='riyuenianbao'){
+				if(item.cateCode=='lipeiribao'){
 					this.jumpUrl('/library/longPosterService/longPosterService2?id='+item.id)
 				}
 			}

+ 2 - 2
library/longPosterService/longPosterService2.vue

@@ -109,7 +109,7 @@
 		},
 		onLoad(options) {
 			this.posterBg = 'https://img.zhiqiyun.com/test/2023/08/23/d2ae45f898abdb12559da237f3c92188.png'
-			this.getCodeDetail('riyuenianbao')
+			this.getCodeDetail('lipeiribao')
 			if(options.id){
 				this.id = options.id
 				this.getPosterDetail()
@@ -170,7 +170,7 @@
 					req.postRequest('/api/nocheck/uploadBase64',{fileString:this.base64.replace('data:image/png;base64,','')},data=>{
 						let dataP = {
 							title:this.title,
-							code:'riyuenianbao',
+							code:'lipeiribao',
 							pic:data.src,
 							extForm:{
 								posterBg:this.posterBg,

+ 1 - 1
mine/clientDetail/clientDetail.vue

@@ -46,7 +46,7 @@
 				<view class="form-data fflex">
 					<block v-if="info.customTypeName&&info.customTypeName.length>0">{{info.customTypeName.join(',')}}
 					</block>
-					<block>-</block>
+					<block v-else>-</block>
 				</view>
 			</view>
 			<view class="ddflex form-item">

+ 9 - 0
pages.json

@@ -127,6 +127,15 @@
 					"navigationBarTitleText": "客户动态"
 				}
 			}]
+		},
+		{
+			"root": "topic",
+			"pages": [{
+				"path": "content/content",
+				"style": {
+					"navigationBarTitleText": "内容管理"
+				}
+			}]
 		}
 	],
 	"globalStyle": {

BIN
static/images/like.png


BIN
static/images/like_h.png


+ 89 - 0
topic/content/content.css

@@ -0,0 +1,89 @@
+/* content.css */
+page{background: #fff;padding: 30rpx 0;box-sizing: border-box;}
+.title{font-size: 40rpx;color: #282828;font-weight: bold;line-height: 60rpx;padding: 0 30rpx;}
+.det-sta{padding: 0 30rpx;margin-top: 20rpx;font-size: 24rpx;}
+.sitename{color: var(--mina);}
+.time{color: #999;margin-left: 15rpx;}
+.time text{font-size: 28rpx;color: #333;margin-right: 23rpx;}
+.content{font-size: 34rpx;color: #333;padding: 30rpx 0;line-height: 1.7;}
+.con-vheight{max-height: 120vh;position: relative;overflow: hidden;}
+.con-vheight::after{content: '';display: block;position: absolute;left: 0;right: 0;bottom: 0;height: 15vh;background: linear-gradient(rgba(255, 255, 255, 0), #fff);z-index: 5;}
+.content video{width: 100%;height: 400rpx;object-fit:fill; margin-bottom: 20rpx;}
+.content image{width: 100%;margin: 30rpx auto;}
+._p{padding: 0 30rpx;}
+.view-more{width: 230rpx;height: 70rpx;font-size: 30rpx;color: #999;margin: 30rpx auto 0;text-align: center;line-height: 70rpx;}
+.bot{padding: 40rpx 35rpx 0;}
+.sbanner{overflow: hidden;position: relative;}
+.sbanner .swiper,.sbanner .swiper image{width: 100%;height: 160rpx;border-radius: 12rpx;overflow: hidden;}
+.dots{position: absolute;left: 0;right: 0;bottom: 20rpx;align-items: center;justify-content: center;}
+.dot{background: rgba(0,0,0,.5);width: 12rpx;height: 12rpx;border-radius: 6rpx;margin: 0 4rpx;}
+.dot.active{width: 30rpx;background: #fff;}
+.tits{font-size: 36rpx;color: #333;font-weight: bold;margin: 30rpx 0 0;}
+.zxlist .li{padding: 40rpx 0;border-bottom: 1rpx solid #EBEBEB;}
+.zximg{width: 230rpx;height: 150rpx;border-radius: 12rpx;margin-right: 23rpx;}
+.zxtit{font-size: 30rpx;color: #333;line-height: 41rpx;height: 82rpx;}
+.datas{font-size: 24rpx;color: #999;margin-top: 30rpx;}
+.adContainer{margin: 30rpx 0 0;border-radius: 10rpx;overflow: hidden;}
+.operate{padding: 30rpx 30rpx 50rpx;justify-content: space-between;font-size: 24rpx;color: #999;}
+.zan image{width: 30rpx;height: 30rpx;margin-right: 15rpx;}
+.zan.active{color: #108FFF;}
+.share{font-size: 24rpx;color: #999;}
+.share image{width: 30rpx;height: 30rpx;margin-right: 15rpx;}
+.box{padding: 40rpx 30rpx 0;border-top: 20rpx solid #f9f9f9;}
+.tit{font-size: 32rpx;color: #333;font-weight: bold;align-items: center;position: relative;padding-left: 18rpx;}
+.tit::before{
+	position: absolute;
+	content: '';
+	width: 8rpx;
+	height: 36rpx;
+	background: #27D699;
+	border-radius: 50rpx 50rpx 50rpx 50rpx;
+	opacity: 1;
+	left: 0;
+}
+.pro{padding-bottom: 10rpx;}
+.pro .li{padding: 30rpx 0;border-bottom: 1rpx solid #e5e5e5;}
+.pro .li:last-child{border-bottom: none;}
+.proimg{box-sizing: border-box;width: 210rpx;height: 210rpx;border-radius: 10rpx;margin-right: 20rpx;border: 2rpx solid #E5E5E5;}
+.proname{font-size: 28rpx;color: #333;overflow : hidden;line-height: 36rpx;height: 72rpx;}
+.infos {font-size: 24rpx;color: #999;margin: 10rpx 0 20rpx;text-overflow: ellipsis;white-space: nowrap;overflow: hidden;}
+.Ninfos{padding: 16rpx 0;}
+.ope{justify-content: space-between;align-items: center;}
+.price{font-size: 25rpx;color: #FE0419;align-items: center;}
+.price text{font-size: 36rpx;font-weight: bold;}
+.price text.del{font-size: 24rpx !important;color: #999;text-decoration: line-through;margin-left: 15rpx;font-weight: normal !important;}
+.price .vip{margin-left: 12rpx;color: var(--vip);border: 1rpx solid;padding: 0 8rpx 2rpx 8rpx;border-radius: 5px;}
+.price .vip text{font-size: 24rpx;font-weight: normal;}
+.btn{width: 150rpx;height: 50rpx;background: linear-gradient(90deg, rgba(254, 147, 76, 1) 0%, rgba(253, 58, 49, 1) 100%);font-size: 24rpx;color: #fff;text-align: center;line-height: 50rpx;border-radius: 25rpx;}
+.rec .li{padding: 30rpx 0;border-bottom: 1rpx solid #EDEDED;align-items: center;}
+.rec .li:last-child{border-bottom: none;}
+.rec-tit{font-size: 34rpx;color: #333;font-weight: bold;line-height: 48rpx;height: 96rpx;margin-bottom: 25rpx;}
+.author{font-size: 28rpx;color: #999;margin-right: 24rpx;}
+.author image{width: 32rpx;height: 32rpx;border-radius: 2rpx;margin-right: 10rpx;}
+.rec-time{font-size: 28rpx;color: #999;}
+.rec-img{width: 218rpx;height: 152rpx;margin-left: 55rpx;border-radius: 10rpx;}
+
+/* 广告 */
+.banner{height: 200rpx;position: relative;padding: 30rpx;border-top: 20rpx solid #f9f9f9;}
+.swiper{width: 100%;height: 200rpx;border-radius: 10rpx;}
+.swiper image{width: 100%;height: 100%;border-radius: 10rpx;}
+.dots{position: absolute;left: 50%;transform: translateX(-50%);bottom: 50rpx;z-index: 3;justify-content: center;}
+.dot{width: 46rpx;height: 4rpx;background: rgba(255,255,255,.5);margin: 0 3rpx;}
+.dot.active{background: #fff;}
+
+.video{margin: 20rpx 30rpx 0;position: relative;}
+.video video{width: 100%;height: 397rpx;border-radius: 15rpx;display: block;}
+.video-play{position: absolute;top: 0;left: 0;right: 0;bottom: 0;justify-content: center;background: rgba(0,0,0,.5);border-radius: 15rpx;}
+.video-play image{width: 82rpx;height: 82rpx;}
+.imgbox{flex-wrap: wrap;margin-top: 20rpx;padding: 0 30rpx;}
+.imgbox image{width: 222rpx;height: 222rpx;border-radius: 16rpx;margin: 0 12rpx 12rpx 0;}
+.imgbox image:nth-child(3n){margin-right: 0;}
+
+.end-note{
+	font-size: 24rpx;
+	font-family: Source Han Sans CN-Regular, Source Han Sans CN;
+	font-weight: 400;
+	color: #999999;
+	line-height: 36rpx;
+	margin:50rpx 30rpx 55rpx;
+}

+ 223 - 0
topic/content/content.vue

@@ -0,0 +1,223 @@
+<template>
+	<view v-if="detaile">
+		<view v-if="detaile&&detaile.contentType!=3">
+			<view class="title">{{ detaile.title }}</view>
+			<view class="det-sta ddflex">
+				<image style="width: 48rpx;height: 48rpx;margin-right: 10rpx;" :src="config.CONFIG_PROJECT_LOGO"></image>
+				<view class="sitename" @click="toIndex('')">{{config.CONFIG_PROJECT_TITLE}}</view>
+				<view class="time">{{getDateTimeStamp(detaile.time)}}</view>
+			</view>
+			<view class="video" v-if="detaile.resourceUri&&JSON.parse(detaile.resourceUri).uri">
+				<video :src="JSON.parse(detaile.resourceUri).uri" :show-center-play-btn="false" :controls="true" :autoplay="true" id="myVideo" @pause="videoPause" @ended="videoEnded"></video>
+				<view class="video-play ddflex" @click="videoPlay()" v-if="detaile.isShowPlayBtn"><image src="../../static/images/play.png"></image></view>
+			</view>
+			<view class="content">
+				<view class="viewMore">
+					<mp-html :content="detaile.text" :lazy-load="true" @imgtap="choose"></mp-html>
+				</view>
+			</view>
+			<view style="font-size: 24rpx;color: #999999;line-height: 36rpx;padding: 0 30rpx;">
+				部分素材来源:中国人寿寿险app、国寿e店、云助理等。
+			</view>
+			<view class="operate dflex">
+				<view class="zan dflex">
+					共{{detaile.browse}}次浏览</view>
+				<view class="ddflex">
+					<button style="margin-right: 20rpx !important;" class="share ddflex" hover-class="none" @click="userBehavior()"><image :src="detaile.isThumbs?'/static/images/like_h.png':'/static/images/like.png'"></image>{{detaile.thumbsNumber}}</button>
+					<view style="margin-left: 30rpx;">
+						<button class="share ddflex" hover-class="none" open-type="share"><image src="/static/images/share.png"></image>分享</button>
+					</view>
+				</view>
+			</view>
+			<view class="box" v-if="contentList && contentList.length > 0">
+				<view class="tit dflex">为您推荐</view>
+				<view class="rec">
+					<navigator :url="'/topic/content/content?id=' + item.id" hover-class="none" class="li dflex" v-for="(item,index) in contentList" :key="index" v-if="index < 3">
+						<view class="flex">
+							<view class="rec-tit tovers">{{item.title}}</view>
+							<view class="dflex">
+								<!-- <view class="author ddflex"><image src="../../static/pages/images/zbgw.png" mode="aspectFill"></image>欧衡</view> -->
+								<view class="rec-time">{{item.createDate}}</view>
+							</view>
+						</view>
+						<image :src="item.pic" mode="aspectFill" class="rec-img"></image>
+					</navigator>
+				</view>
+			</view>
+		</view>
+		<view v-if="detaile&&detaile.contentType==3">
+			<web-view :src="webUrl" v-if="webUrl!==''"></web-view>
+		</view>
+		
+	</view>
+</template>
+
+<script>
+const app = getApp();
+const req = require('../../utils/request.js');
+const util = require('../../utils/util.js');
+import mpHtml from "../../components/mp-html/components/mp-html/mp-html";
+export default {
+	components: {
+		mpHtml,
+	},
+	data() {
+		return {
+			detaile: '',
+			type: 2, //详情类型:1 普通内容 2 自定义页面内容
+			form: {
+				page: 1,
+				limit: 4,
+			},
+			contentList: [],
+			viewMore: false,
+			config: {},
+			userInfo:{},
+			query:{},
+			id:null,
+			
+			webUrl:'',
+			
+			tempFilePath:'',
+			
+			
+			isShowUpdate: false, //是否显示更新信息弹窗
+			isUpdateInfo: false,//更新用户信息
+			isUpdateMobile: false,//绑定手机号
+			
+			
+			enterTime : '',//进入页面时间
+			outTime : '',//页面卸载时间
+			stayTime : 0//页面停留时间
+		};
+	},
+
+	onLoad: async function(options) {
+		this.query = options
+		this.id = options.id;
+		await this.getDetail();
+		this.getContentList();
+	},
+	async onShow(){
+		this.getConfig()
+	},
+	onHide(){
+	},
+	methods: {
+		getConfig() {
+			var _this = this;
+			return new Promise((res, rej) => {
+				req.getRequest(
+					'/api/other/config',{},
+					data => {
+						this.config = data;
+						res(data);
+					},
+					true
+				);
+			});
+		},
+		
+		monitor() {
+			let that = this;
+			let system = uni.getSystemInfoSync();
+			let query = uni.createSelectorQuery();
+			query.select('.viewMore').boundingClientRect(data=>{
+				let height = data.height;
+				if(height > system.screenHeight) {
+					this.viewMore = true
+				}
+			}).exec();
+		},
+		viewMores(){
+			this.viewMore = true;
+		},
+		choose: function () {
+		  let freshen = false;
+		  this.$emit('freshen', {
+		    detail: freshen
+		  });
+		},
+		toIndex(){
+			// uni.switchTab({
+			// 	url: '/pages/index/index'
+			// })
+		},
+		getDateTimeStamp(dateStr){
+		 return util.getDateDiff(Date.parse(dateStr.replace(/-/gi,"/")));
+		},
+		getDetail() {
+			let that = this;
+			let apiUrl = '/api/content/detail';
+			return new Promise((resolve,reject)=>{
+				req.getRequest(apiUrl,{ id: that.id },async res => {
+					if(res.productCategory){
+						res.productCategory = res.productCategory.split(',')
+					}
+					that.detaile = res;
+					that.webUrl = that.detaile.linkUrl?that.detaile.linkUrl:''
+					let arr = [];
+					if(res.productCategory && res.productCategory.length > 0){
+						for (var i = 0; i < res.productCategory.length; i++) {
+							await that.getProductList(res.productCategory[i], i).then(res=>{
+								arr = arr.concat(res);
+								that.productList = arr;
+								console.log('相关产品=='+JSON.stringify(arr),arr.length)
+							});
+						}
+					}
+					resolve();
+				},true);
+			})
+		},
+		getContentList() {
+			let form = this.form;
+			form.cid = this.detaile.cid;
+			if(req.getStorage("shareId")){
+				form.shareSaleNo = req.getStorage("shareId")
+			}
+			req.getRequest('/api/content/list',form,res => {
+				this.contentList = res;
+				for (var i = 0; i < this.contentList.length; i++) {
+					if (this.contentList[i].id == this.id){
+						this.contentList.splice(i,1);
+					}
+				}
+			});
+		},
+	},
+	filters: {
+		/**
+		 * 处理富文本里的图片宽度自适应
+		 * 1.去掉img标签里的style、width、height属性
+		 * 2.img标签添加style属性:max-width:100%;height:auto
+		 * 3.修改所有style里的width属性为max-width:100%
+		 * 4.去掉<br/>标签
+		 * @param html
+		 * @returns {void|string|*}
+		 */
+		formatRichText(html) {
+			//控制小程序中图片大小
+			let newContent = html.replace(/<img[^>]*>/gi, function(match, capture) {
+				match = match.replace(/style="[^"]+"/gi, '').replace(/style='[^']+'/gi, '');
+				match = match.replace(/width="[^"]+"/gi, '').replace(/width='[^']+'/gi, '');
+				match = match.replace(/height="[^"]+"/gi, '').replace(/height='[^']+'/gi, '');
+				return match;
+			});
+			newContent = newContent.replace(/style="[^"]+"/gi, function(match, capture) {
+				match = match.replace(/width:[^;]+;/gi, 'max-width:100%;').replace(/width:[^;]+;/gi, 'max-width:100%;');
+				return match;
+			});
+			// newContent = newContent.replace(/<br[^>]*\/>/gi, '');
+			newContent = newContent.replace(/<br[^>]*\/>/gi, '<p style="margin: 10px 0;"></p>');
+			newContent = newContent.replace(/<br[^>]*\>/gi, '<p style="margin: 10px 0;"></p>');
+			newContent = newContent.replace(/font-size:[^;]+;?/g,'');
+			newContent = newContent.replace(/\<img/gi, '<img style="max-width:100%;height:auto;display:inline-block;margin:12rpx auto;"');
+			return newContent;
+		}
+	}
+};
+</script>
+<style>
+@import './content.css';
+</style>

+ 127 - 0
topic/contentList/contentList.css

@@ -0,0 +1,127 @@
+/* contentList.css */
+page{background: #fff;}
+.top-fixed{
+	position: fixed;
+	top: 0;
+	right: 0;
+	left: 0;
+	background-color: #fff;
+	z-index: 101;
+}
+.search-box{
+	padding: 30rpx;
+	background-color: #fff;
+}
+
+.search-text{
+	color: #999999;
+}
+
+
+.search-input{
+	height: 76rpx;
+	line-height: 76rpx;;
+	background: #F5F5F5;
+	border-radius: 60rpx;
+	padding: 0 26rpx;
+	font-size: 24rpx;
+}
+.search-input input{
+	font-size: 24rpx;
+}
+.search-input image{
+	width: 34rpx;
+	height: 34rpx;
+	margin-right: 26rpx;
+}
+.search-all{
+	color: #2a82fd;
+	font-size: 30rpx;
+	margin-left: 46rpx;
+}
+.ban{height: 275rpx;position: relative;margin: 40rpx 32rpx 15rpx;}
+.swiper{width: 100%;height: 275rpx;border-radius: 10rpx;}
+.swiper image{width: 100%;height: 100%;border-radius: 10rpx;}
+.dots{position: absolute;left: 50%;transform: translateX(-50%);bottom: 20rpx;z-index: 3;justify-content: center;}
+.dot{width: 46rpx;height: 4rpx;background: rgba(255,255,255,.5);margin: 0 3rpx;}
+.dot.active{background: #fff;}
+.title{font-size: 36rpx;color: #333;font-weight: bold;align-items: center;}
+.title::before{content: '';display: block;width: 6rpx;height: 38rpx;background: #108FFF;margin-right: 14rpx;}
+.box{padding: 30rpx 32rpx 0;}
+.bobo{border-bottom: 20rpx solid #F8F8F8;}
+.rec .li{padding: 30rpx 0;border-bottom: 1rpx solid #EDEDED;align-items: center;}
+.rec-tit{font-size: 34rpx;color: #333;font-weight: bold;line-height: 47rpx;height: 141rpx;margin-bottom: 25rpx;}
+.author{font-size: 28rpx;color: #999;margin-right: 24rpx;}
+.author image{width: 32rpx;height: 32rpx;border-radius: 2rpx;margin-right: 10rpx;}
+.rec-time{font-size: 28rpx;color: #999;}
+.rec-img{width: 240rpx;height: 180rpx;margin-left: 55rpx;border-radius: 12rpx 12rpx 12rpx 12rpx;}
+
+
+/* 行业资讯 */
+.rec-association {
+	padding: 0rpx 30rpx 0;
+	opacity: 1;
+	border-bottom: 1px solid #E8E8E8;
+	position: fixed;
+	width: 100%;
+	box-sizing: border-box;
+	height: 70rpx;
+	z-index: 5;
+	background: #fff;
+}
+.rec-association-nav{
+	position: relative;
+}
+.rec-association-nav-m{
+	position: absolute;
+	right: 0;
+	top: 0;
+	bottom:0;
+	width: 55rpx;
+	background: linear-gradient(90deg, rgba(255,255,255,0) 0%, #FFFFFF 100%);
+	/* background: #000; */
+	opacity: 1;
+}
+.rec-association-scroll {
+	white-space: nowrap;
+	overflow: hidden;
+	height: 70rpx;
+}
+
+.rec-association-scroll ::-webkit-scrollbar {
+	width: 0;
+	height: 0;
+	color: transparent;
+	display: none;
+}
+.rec-association-scroll-item{
+	/* max-width: 128rpx; */
+	overflow: hidden;
+	font-size: 32rpx;
+	display: inline-block;
+	margin-right:56rpx;
+	color: #666;
+	height: 100%;
+}
+.rec-association-scroll-item-active{
+	color: var(--main);
+	font-size: 32rpx;
+	font-weight: 500;
+	position: relative;
+}
+.rec-association-scroll-item-active::after{
+	content: '';
+	position: absolute;
+	bottom: 0px;
+	left: 50%;
+	transform: translateX(-50%);
+	width: 50%;
+	height: 6rpx;
+	background: var(--main);
+}
+.rec-association-nav-icon{
+	width: 38rpx;
+	height: 32rpx;
+	margin-left: 40rpx;
+	margin-top: 4rpx;
+}

+ 148 - 0
topic/contentList/contentList.vue

@@ -0,0 +1,148 @@
+<template>
+	<view>
+		<view class="top-fixed">
+			<view class="search-box ddflex">
+				<view class="search-input ddflex fflex">
+					<image src="/static/images/ssico.png"></image>
+					<input @confirm="searchFn" confirm-type="search" @input="searchFn" v-model="searchVal" class="fflex"
+						placeholder="请输入关键词" />
+				</view>
+			</view>
+			<view class="rec-association" v-if="cateList.length>0" style="">
+				<view class="dflex">
+					<view class="rec-association-nav fflex">
+						<scroll-view scroll-x="true" class="rec-association-scroll ddflex">
+							<view :class="'rec-association-scroll-item tover '+(cIndex == index?'rec-association-scroll-item-active':'')" @click="getCon(item,index)" v-for="item,index in cateList" :key="index">{{item.name}}</view>
+						</scroll-view>
+						<view class="rec-association-nav-m"></view>
+					</view>
+				</view>
+			</view>
+		</view>
+		
+		<view :style="cateList.length>0?'height: 180rpx;':'height: 100rpx;'"></view>
+		
+		<view class="box">
+			<!-- <view class="title dflex">{{title}}</view> -->
+			<view class="rec" v-if="contentList.length>0">
+				<navigator :url="'/topic/content/content?id=' + item.id" hover-class="none" class="li dflex" v-for="(item,index) in contentList" :key="index">
+					<view class="flex">
+						<view class="rec-tit tovers3">{{item.title}}</view>
+						<view class="dflex">
+							<!-- <view class="author ddflex"><image src="../../static/pages/images/zbgw.png" mode="aspectFill"></image>欧衡</view> -->
+							<view class="rec-time">{{item.createDate}}</view>
+						</view>
+					</view>
+					<image :src="item.pic" mode="aspectFill" class="rec-img"></image>
+				</navigator>
+			</view>
+			<view class="nodata" v-if="!contentList||contentList.length==0">
+				<image :src="picUrlss+'empty_zb.png'"></image>
+				<text>暂无资讯</text>
+			</view>
+		</view>
+	</view>
+</template>
+
+<script>
+const req = require('../../utils/request.js');
+const api = require('../../utils/api.js');
+const util = require('../../utils/util.js');
+const app = getApp();
+export default {
+	data() {
+		return {
+			picUrlss: req.public.picUrls,
+			title: '',
+			swiperCurrent: 0,
+			bannerList: [],
+			form: {
+				page: 1,
+				limit: 10
+			},
+			contentList: [],
+			isLoad: true,
+			code:null,
+			cid:null,
+			cateList:[],
+			cIndex:0,
+			searchVal:''
+		};
+	},
+	async onLoad(options) {
+		this.title = options.title;
+		this.code = options.code;
+		uni.setNavigationBarTitle({
+			title: options.title
+		})
+		if(this.code)
+			await this.getCate()
+		this.getContentList();
+	},
+	onShow() {
+	},
+	onReachBottom(){
+		this.form.page++;
+		this.getContentList();
+	},
+
+	methods: {
+		async getCon(item,index){
+			this.cIndex = index;
+			this.cid = item.id
+			this.form.page = 1
+			this.isLoad = true;
+			this.getContentList();
+		},
+		// 获取分类
+		getCate(){
+			return new Promise((r,j)=>{
+				req.getRequest('/api/content/category/list',{parentCode: this.code},res=>{
+					this.cateList = res;
+					// 如果有二级分类,清处code和noCode
+					if(this.cateList&&this.cateList.length>0){
+						this.cid = res[0].id
+						this.code = null
+					}
+					r()
+				})
+			})
+		},
+		//为您推荐
+		getContentList() {
+			if (!this.isLoad) return false;
+			this.isLoad = false;
+			let form = this.form;
+			form.code = this.code;
+			// 如果有二级分类用分类id查
+			if(this.cid){
+				form.cid = this.cid
+			}
+			if(this.searchVal) form.title = this.searchVal
+			else form.title = ''
+			if(req.getStorage("shareId")){
+				form.shareSaleNo = req.getStorage("shareId")
+			}
+			req.getRequest('/api/content/list', form,res => {
+				res = res?res:[]
+				if (res && res.length == 10) {
+					this.isLoad = true;
+				}
+				if (this.form.page > 1) {
+					res = this.contentList.concat(res);
+				}
+				this.contentList = res;
+			});
+		},
+		searchFn(){
+			this.form.page = 1
+			this.isLoad = true;
+			this.getContentList();
+		}
+	}
+};
+</script>
+
+<style>
+	@import "./contentList.css";
+</style>

BIN
topic/static/images/back.png


BIN
topic/static/images/back_black.png


BIN
topic/static/images/backtop.png


BIN
topic/static/images/guan.png


BIN
topic/static/images/gz_black.png


BIN
topic/static/images/gz_h.png


BIN
topic/static/images/gz_white.png


BIN
topic/static/images/hot.png


BIN
topic/static/images/hui.png


BIN
topic/static/images/liwu.png


BIN
topic/static/images/quan.png


BIN
topic/static/images/share.png


BIN
topic/static/images/ssico.png


BIN
topic/static/images/view.png


BIN
topic/static/images/wen.png


BIN
topic/static/images/yimai.png


Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov